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' ;
2
5
import { promises } from 'fs' ;
3
6
import { join } from 'path' ;
4
7
5
- import { DeployConfig , exec , PathFactory , spawn } from '../../utils' ;
8
+ import { DeployConfig , exec , findDependency , PathFactory , spawn } from '../../utils' ;
6
9
7
10
const { mkdir } = promises ;
8
11
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
-
16
12
export const build = async ( config : DeployConfig | Required < DeployConfig > , getProjectPath : PathFactory ) => {
17
13
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 ) ) ;
22
17
23
18
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 ) ) ;
27
19
const { workspace } = await workspaces . readWorkspace ( getProjectPath ( ) , host ) ;
28
20
const architectHost = new WorkspaceNodeModulesArchitectHost ( workspace , getProjectPath ( ) ) ;
29
21
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
32
34
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
+
39
85
if ( prerenderTarget ) {
40
86
// TODO fix once we can migrate to ESM. Spawn for now.
41
87
// ERR require() of ES Module .../node_modules/@nguniversal/express-engine/fesm2015/express-engine.mjs not supported.
42
88
// Instead change the require of .../node_modules/@nguniversal /express-engine/fesm2015/express-engine.mjs to a dynamic
43
89
// 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);
48
91
await spawn (
49
92
'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
52
96
out => console . log ( out . toString ( ) ) ,
53
97
err => console . error ( err . toString ( ) )
54
98
) ;
55
99
} 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 ) ;
64
102
}
65
103
66
104
const deployPath = ( ...args : string [ ] ) => join ( config . dist , ...args ) ;
67
105
const getHostingPath = ( ...args : string [ ] ) => deployPath ( 'hosting' , ...args ) ;
68
106
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 ;
70
110
await mkdir ( getHostingPath ( ) , { recursive : true } ) ;
71
111
await exec ( `cp -r ${ getProjectPath ( browserOutputPath ) } /* ${ getHostingPath ( ) } ` ) ;
72
112
113
+ const usingCloudFunctions = ! ! serverTarget ;
114
+
73
115
let bootstrapScript = '' ;
74
116
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 ;
77
121
await mkdir ( deployPath ( 'functions' , serverOutputPath ) , { recursive : true } ) ;
78
122
await mkdir ( deployPath ( 'functions' , browserOutputPath ) , { recursive : true } ) ;
79
123
await exec ( `cp -r ${ getProjectPath ( serverOutputPath ) } /* ${ deployPath ( 'functions' , serverOutputPath ) } ` ) ;
80
124
await exec ( `cp -r ${ getProjectPath ( browserOutputPath ) } /* ${ deployPath ( 'functions' , browserOutputPath ) } ` ) ;
81
125
bootstrapScript = `exports.handle = require('./${ serverOutputPath } /main.js').app();\n` ;
82
- const bundleDependencies = serverTarget . options ? .bundleDependencies ?? true ;
126
+ const bundleDependencies = serverTargetOptions . bundleDependencies ?? true ;
83
127
if ( bundleDependencies ) {
84
- const packageManager = angularJson . cli ?. packageManager ?? 'npm' ;
85
128
const dependencies : Record < string , string > = { } ;
86
- const externalDependencies : string [ ] = serverTarget . options ? .externalDependencies as any || [ ] ;
129
+ const externalDependencies : string [ ] = serverTargetOptions . externalDependencies as any || [ ] ;
87
130
externalDependencies . forEach ( externalDependency => {
88
- const packageVersion = findPackageVersion ( packageManager , externalDependency ) ;
131
+ const packageVersion = findDependency ( externalDependency ) ?. version ;
89
132
if ( packageVersion ) { dependencies [ externalDependency ] = packageVersion ; }
90
133
} ) ;
91
134
packageJson . dependencies = dependencies ;
92
135
}
93
136
}
94
137
138
+ // TODO add immutable header on static assets
139
+
95
140
return { usingCloudFunctions, rewrites : [ ] , redirects : [ ] , headers : [ ] , framework : 'express' , packageJson, bootstrapScript } ;
96
141
} ;
0 commit comments