Skip to content

Commit 93c275c

Browse files
committed
Angular now respects AngularFire config
1 parent 0ea74ad commit 93c275c

File tree

7 files changed

+109
-80
lines changed

7 files changed

+109
-80
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"clean:build": "rm -rf dist",
3232
"clean:e2e": "rm -rf e2e",
3333
"clean:dev": "npm uninstall -g firebase-tools && rm -rf firebase-tools",
34-
"dev": "npm run build && ./tools/dev-tools.sh",
3534
"test:angular": ". ./tools/dev-tools.sh && npm run e2e:build-angular && npm run test:e2e",
3635
"test:next": ". ./tools/dev-tools.sh && npm run e2e:build-next && npm run test:e2e",
3736
"test:nuxt": ". ./tools/dev-tools.sh && npm run e2e:build-next && npm run test:e2e"

src/frameworks/angular/index.ts

Lines changed: 90 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,141 @@
1-
import { spawnSync } from 'child_process';
1+
import { NodeJsAsyncHost } from '@angular-devkit/core/node';
2+
import { workspaces, logging } from '@angular-devkit/core';
3+
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
4+
import { Target, Architect, targetFromTargetString, targetStringFromTarget } from '@angular-devkit/architect';
25
import { promises } from 'fs';
36
import { join } from 'path';
47

5-
import { DeployConfig, exec, PathFactory, spawn } from '../../utils';
8+
import { DeployConfig, exec, findDependency, PathFactory, spawn } from '../../utils';
69

710
const { mkdir } = promises;
811

9-
const escapeRegExp = (str: string) => str.replace(/[\-\[\]\/{}()*+?.\\^$|]/g, '\\$&');
10-
11-
const findPackageVersion = (packageManager: string, name: string) => {
12-
const match = spawnSync(packageManager, ['list', name], { cwd: process.cwd() }).output.toString().match(`[^|\s]${escapeRegExp(name)}[@| ][^\s]+(\s.+)?$`);
13-
return match ? match[0].split(new RegExp(`${escapeRegExp(name)}[@| ]`))[1].split(/\s/)[0] : null;
14-
};
15-
1612
export const build = async (config: DeployConfig | Required<DeployConfig>, getProjectPath: PathFactory) => {
1713

18-
const { NodeJsAsyncHost } = await import('@angular-devkit/core/node');
19-
const { workspaces, logging } = await import('@angular-devkit/core');
20-
const { WorkspaceNodeModulesArchitectHost } = await import('@angular-devkit/architect/node');
21-
const { Architect } = await import('@angular-devkit/architect');
14+
// TODO log to firebase-tools
15+
const logger = new logging.Logger('firebase-tools');
16+
logger.subscribe(it => console.log(it));
2217

2318
const host = workspaces.createWorkspaceHost(new NodeJsAsyncHost());
24-
const logger = new logging.Logger('foo');
25-
// TODO pipe to firebase-tools log
26-
logger.subscribe(it => console.log(it));
2719
const { workspace } = await workspaces.readWorkspace(getProjectPath(), host);
2820
const architectHost = new WorkspaceNodeModulesArchitectHost(workspace, getProjectPath());
2921
const architect = new Architect(architectHost);
30-
const angularJson = JSON.parse(await host.readFile('angular.json'));
31-
const project = angularJson.defaultProject;
22+
23+
let project: string|undefined = (globalThis as any).NG_DEPLOY_PROJECT;
24+
let browserTarget: Target|undefined;
25+
let serverTarget: Target|undefined;;
26+
let prerenderTarget: Target|undefined;
27+
28+
if (!project) {
29+
const angularJson = JSON.parse(await host.readFile('angular.json'));
30+
project = angularJson.defaultProject;
31+
if (!project) throw `angular.json missing defaultProject`;
32+
}
33+
// TODO if there are multiple projects warn
3234
const workspaceProject = workspace.projects.get(project);
33-
if (!workspaceProject) throw 'foo';
34-
const buildTarget = workspaceProject.targets.get('build');
35-
if (!buildTarget) throw 'bar';
36-
const serverTarget = workspaceProject.targets.get('server');
37-
const usingCloudFunctions = !!serverTarget;
38-
const prerenderTarget = workspaceProject.targets.get('prerender');
35+
if (!workspaceProject) throw `No project ${project} found.`;
36+
const deployTargetDefinition = workspaceProject.targets.get('deploy');
37+
if (deployTargetDefinition?.builder === '@angular/fire:deploy') {
38+
const options = deployTargetDefinition.options;
39+
if (typeof options?.prerenderTarget === 'string')
40+
prerenderTarget = targetFromTargetString(options.prerenderTarget);
41+
if (typeof options?.browserTarget === 'string')
42+
browserTarget = targetFromTargetString(options.browserTarget);
43+
if (typeof options?.serverTarget === 'string')
44+
serverTarget = targetFromTargetString(options.serverTarget);
45+
if (prerenderTarget) {
46+
const prerenderOptions = await architectHost.getOptionsForTarget(prerenderTarget);
47+
if (!prerenderOptions) throw 'foo';
48+
if (typeof prerenderOptions.browserTarget !== 'string') throw 'foo';
49+
if (typeof prerenderOptions.serverTarget !== 'string') throw 'foo';
50+
if (browserTarget) {
51+
if (targetStringFromTarget(browserTarget) !== prerenderOptions.browserTarget)
52+
throw 'foo';
53+
} else {
54+
browserTarget = targetFromTargetString(prerenderOptions.browserTarget);
55+
}
56+
if (serverTarget && targetStringFromTarget(serverTarget) !== prerenderOptions.serverTarget)
57+
throw 'foo';
58+
}
59+
} else if (workspaceProject.targets.has('prerender')) {
60+
// TODO test and warn if production doesn't exist, fallback to default
61+
prerenderTarget = { project, target: 'prerender', configuration: 'production' };
62+
const production = await architectHost.getOptionsForTarget(prerenderTarget);
63+
if (!production) throw 'foo';
64+
if (typeof production.browserTarget !== 'string') throw 'foo';
65+
if (typeof production.serverTarget !== 'string') throw 'foo';
66+
browserTarget = targetFromTargetString(production.browserTarget);
67+
serverTarget = targetFromTargetString(production.serverTarget);
68+
} else {
69+
// TODO test and warn if production doesn't exist, fallback to default
70+
const configuration = 'production';
71+
if (workspaceProject.targets.has('build'))
72+
browserTarget = { project, target: 'built', configuration };
73+
if (workspaceProject.targets.has('server'))
74+
serverTarget = { project, target: 'server', configuration };
75+
}
76+
77+
const scheduleTarget = async (target: Target) => {
78+
const run = await architect.scheduleTarget(target, undefined, { logger });
79+
const { success, error } = await run.output.toPromise();
80+
if (!success) throw new Error(error);
81+
}
82+
83+
if (!browserTarget) throw 'No build target...';
84+
3985
if (prerenderTarget) {
4086
// TODO fix once we can migrate to ESM. Spawn for now.
4187
// ERR require() of ES Module .../node_modules/@nguniversal/express-engine/fesm2015/express-engine.mjs not supported.
4288
// Instead change the require of .../node_modules/@nguniversal/express-engine/fesm2015/express-engine.mjs to a dynamic
4389
// import() which is available in all CommonJS modules.
44-
// const run = await architect.scheduleTarget({ project, target: 'prerender', configuration: 'production' }, undefined, { logger });
45-
// const result = await run.output.toPromise();
46-
// console.log(result);
47-
// if (!result.success) throw result.error;
90+
// await scheduleTarget(prerenderTarget);
4891
await spawn(
4992
'node_modules/.bin/ng',
50-
['run', [project, 'prerender'].join(':')],
51-
{cwd: process.cwd() },
93+
['run', targetStringFromTarget(prerenderTarget)],
94+
{ cwd: process.cwd() },
95+
// TODO log to firebase-tools
5296
out => console.log(out.toString()),
5397
err => console.error(err.toString())
5498
);
5599
} else {
56-
const run = await architect.scheduleTarget({ project, target: 'build' }, undefined, { logger });
57-
const { error, success } = await run.output.toPromise();
58-
if (!success) throw error;
59-
if (serverTarget) {
60-
const run = await architect.scheduleTarget({ project, target: 'server' }, undefined, { logger });
61-
const { error, success } = await run.output.toPromise();
62-
if (!success) throw error;
63-
}
100+
await scheduleTarget(browserTarget);
101+
if (serverTarget) await scheduleTarget(serverTarget);
64102
}
65103

66104
const deployPath = (...args: string[]) => join(config.dist, ...args);
67105
const getHostingPath = (...args: string[]) => deployPath('hosting', ...args);
68106

69-
const browserOutputPath = buildTarget.options!.outputPath as string;
107+
const browserTargetOptions = await architectHost.getOptionsForTarget(browserTarget);
108+
if (typeof browserTargetOptions?.outputPath !== 'string') throw 'foo';
109+
const browserOutputPath = browserTargetOptions.outputPath;
70110
await mkdir(getHostingPath(), { recursive: true });
71111
await exec(`cp -r ${getProjectPath(browserOutputPath)}/* ${getHostingPath()}`);
72112

113+
const usingCloudFunctions = !!serverTarget;
114+
73115
let bootstrapScript = '';
74116
const packageJson = JSON.parse(await host.readFile('package.json'));
75-
if (usingCloudFunctions) {
76-
const serverOutputPath = serverTarget.options!.outputPath as string;
117+
if (serverTarget) {
118+
const serverTargetOptions = await architectHost.getOptionsForTarget(serverTarget);
119+
if (typeof serverTargetOptions?.outputPath !== 'string') throw 'foo';
120+
const serverOutputPath = serverTargetOptions.outputPath;
77121
await mkdir(deployPath('functions', serverOutputPath), { recursive: true });
78122
await mkdir(deployPath('functions', browserOutputPath), { recursive: true });
79123
await exec(`cp -r ${getProjectPath(serverOutputPath)}/* ${deployPath('functions', serverOutputPath)}`);
80124
await exec(`cp -r ${getProjectPath(browserOutputPath)}/* ${deployPath('functions', browserOutputPath)}`);
81125
bootstrapScript = `exports.handle = require('./${serverOutputPath}/main.js').app();\n`;
82-
const bundleDependencies = serverTarget.options?.bundleDependencies ?? true;
126+
const bundleDependencies = serverTargetOptions.bundleDependencies ?? true;
83127
if (bundleDependencies) {
84-
const packageManager = angularJson.cli?.packageManager ?? 'npm';
85128
const dependencies: Record<string, string> = {};
86-
const externalDependencies: string[] = serverTarget.options?.externalDependencies as any || [];
129+
const externalDependencies: string[] = serverTargetOptions.externalDependencies as any || [];
87130
externalDependencies.forEach(externalDependency => {
88-
const packageVersion = findPackageVersion(packageManager, externalDependency);
131+
const packageVersion = findDependency(externalDependency)?.version;
89132
if (packageVersion) { dependencies[externalDependency] = packageVersion; }
90133
});
91134
packageJson.dependencies = dependencies;
92135
}
93136
}
94137

138+
// TODO add immutable header on static assets
139+
95140
return { usingCloudFunctions, rewrites: [], redirects: [], headers: [], framework: 'express', packageJson, bootstrapScript };
96141
};

src/index.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { basename, join } from 'path';
15+
import { join } from 'path';
1616
import { exit } from 'process';
17-
import { getFirebaseTools, normalizedHostingConfigs, getInquirer, needProjectId } from './firebase';
1817
import { promises as fs } from 'fs';
1918

2019
import { build } from './frameworks';
2120
import { defaultFirebaseToolsOptions, DEFAULT_REGION, shortSiteName, spawn } from './utils';
21+
import { getFirebaseTools, normalizedHostingConfigs, getInquirer, needProjectId } from './firebase';
2222

2323
const { writeFile, copyFile } = fs;
2424

@@ -27,8 +27,7 @@ const FIREBASE_ADMIN_VERSION = '__FIREBASE_ADMIN_VERSION__';
2727
const FIREBASE_FUNCTIONS_VERSION = '__FIREBASE_FUNCTIONS_VERSION__';
2828
const COOKIE_VERSION = '__COOKIE_VERSION__';
2929
const LRU_CACHE_VERSION = '__LRU_CACHE_VERSION__';
30-
// Aloow the version to be overriden this is stripped out before deploying to NPM (tools/prepare.ts)
31-
const FIREBASE_FRAMEWORKS_VERSION = process.env.FIREBASE_FRAMEWORKS_TARBALL || '__FIREBASE_FRAMEWORKS_VERSION__';
30+
const FIREBASE_FRAMEWORKS_VERSION = '__FIREBASE_FRAMEWORKS_VERSION__';
3231

3332
export const prepare = async (targetNames: string[], context: any, options: any) => {
3433
const firebaseTools = await getFirebaseTools();
@@ -123,14 +122,7 @@ export const prepare = async (targetNames: string[], context: any, options: any)
123122

124123
packageJson.main = 'server.js';
125124
packageJson.dependencies ||= {};
126-
// TODO dev mode override to local directory
127-
if (FIREBASE_FRAMEWORKS_VERSION.startsWith('/')) {
128-
const filename = basename(FIREBASE_FRAMEWORKS_VERSION);
129-
await copyFile(FIREBASE_FRAMEWORKS_VERSION, join(functionsDist, filename));
130-
packageJson.dependencies['firebase-frameworks'] = `./${filename}`;
131-
} else {
132-
packageJson.dependencies['firebase-frameworks'] = FIREBASE_FRAMEWORKS_VERSION;
133-
}
125+
packageJson.dependencies['firebase-frameworks'] = FIREBASE_FRAMEWORKS_VERSION;
134126
// TODO test these with semver, error if already set out of range
135127
packageJson.dependencies['firebase-admin'] ||= FIREBASE_ADMIN_VERSION;
136128
packageJson.dependencies['firebase-functions'] ||= FIREBASE_FUNCTIONS_VERSION;
@@ -145,13 +137,11 @@ export const prepare = async (targetNames: string[], context: any, options: any)
145137

146138
await copyFile(getProjectPath('package-lock.json'), join(functionsDist, 'package-lock.json')).catch(() => {});
147139

148-
// TODO support yarn?
149-
// await copyFile(getProjectPath('yarn.lock'), join(functionsDist, 'yarn.lock')).catch(() => {});
150-
151140
// Only need to do this in dev, since we have a functions.yaml, so discovery isn't needed
152141
// Welp, that didn't work, since firebase-tools checks that they have a minimum firebase-frameworks SDK installed...
153142
// TODO explore symlinks and ways to make this faster, better, stronger
154-
await spawn('npm', ['i', '--prefix', functionsDist, '--only', 'production', '--no-audit', '--no-fund', '--silent'], {}, stdoutChunk => {
143+
// log to firebase-tools
144+
await spawn('npm', ['i', '--only', 'production', '--no-audit', '--no-fund', '--silent'], { cwd: functionsDist }, stdoutChunk => {
155145
console.log(stdoutChunk.toString());
156146
}, errChunk => {
157147
console.error(errChunk.toString());

src/utils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
import { exec as execCallback, spawn as spawnCallback, ExecOptions, SpawnOptionsWithoutStdio } from 'child_process';
15+
import { exec as execCallback, spawn as spawnCallback, ExecOptions, SpawnOptionsWithoutStdio, spawnSync } from 'child_process';
1616
import { FirebaseHostingSite } from 'firebase-tools';
1717
import { join } from 'path';
1818

@@ -72,3 +72,9 @@ export const defaultFirebaseToolsOptions = (projectRoot: string) => ({
7272
export type PathFactory = (...args: string[]) => string;
7373

7474
export const getProjectPathFactory = (config: DeployConfig): PathFactory => (...args) => join(process.cwd(), config.prefix ?? '.', ...args);
75+
76+
export const findDependency = (name: string, cwd=process.cwd()) => {
77+
const result = spawnSync('npm', ['list', name, '--json'], { cwd });
78+
if (!result.stdout) return undefined;
79+
return JSON.parse(result.stdout.toString()).dependencies?.[name];
80+
}

tools/build.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,16 @@ import { spawnSync } from 'child_process';
22
import { replaceInFile } from 'replace-in-file';
33

44
const LOCAL_NODE_MODULES = [
5-
'@angular-devkit/core/node/index.js',
6-
'@angular-devkit/core/index.js',
7-
'@angular-devkit/architect/node/index.js',
8-
'@angular-devkit/architect/index.js',
5+
'@angular-devkit/core/node',
6+
'@angular-devkit/core',
7+
'@angular-devkit/architect/node',
8+
'@angular-devkit/architect',
99
'next/dist/build',
1010
'nuxt',
1111
'@nuxt/kit/dist/index.mjs'
1212
];
1313

1414
const ES_MODULES = [
15-
['@angular-devkit/core/node', 'index.js'],
16-
['@angular-devkit/core', 'src/index.js' ],
17-
['@angular-devkit/architect/node', 'index.js' ],
18-
['@angular-devkit/architect', 'src/index.js' ],
1915
['@nuxt/kit', 'dist/index.mjs' ],
2016
];
2117

@@ -53,7 +49,7 @@ const main = async () => {
5349
const { version } = require('../package.json');
5450
const npmList = JSON.parse(spawnSync('npm', ['list', '--json=true'], { encoding: 'utf8' }).stdout.toString());
5551
const from = ['__FIREBASE_FRAMEWORKS_VERSION__'];
56-
const to = [`^${version}`];
52+
const to = [`file:${process.cwd()}/firebase-frameworks-${version}.tgz`];
5753
for (const [dep, { version }] of Object.entries<Record<string, string>>(npmList.dependencies)) {
5854
from.push(`__${dep.toUpperCase().replace(/[^A-Z]/g, '_')}_VERSION__`);
5955
to.push(`^${version}`);

tools/dev-tools.sh

Lines changed: 0 additions & 7 deletions
This file was deleted.

tools/prepare.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ npm --no-git-tag-version --allow-same-version -f version $OVERRIDE_VERSION
1818

1919
npm run build &&
2020
echo "npm --no-git-tag-version --allow-same-version -f version $OVERRIDE_VERSION &&
21-
sed -i 's/process.env.FIREBASE_FRAMEWORKS_TARBALL/undefined/' ./dist/index.js &&
21+
sed -i \"/const FIREBASE_FRAMEWORKS_VERSION =/c\const FIREBASE_FRAMEWORKS_VERSION = '$OVERRIDE_VERSION'/\" ./dist/index.js &&
2222
npm publish . --tag $NPM_TAG" > ./dist/publish.sh &&
2323
chmod +x ./dist/publish.sh

0 commit comments

Comments
 (0)