6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { workspaces } from '@angular-devkit/core' ;
10
- import { Rule , SchematicsException , chain , externalSchematic } from '@angular-devkit/schematics' ;
9
+ import type { workspaces } from '@angular-devkit/core' ;
10
+ import {
11
+ Rule ,
12
+ SchematicContext ,
13
+ SchematicsException ,
14
+ Tree ,
15
+ chain ,
16
+ externalSchematic ,
17
+ } from '@angular-devkit/schematics' ;
11
18
import { dirname , join } from 'node:path/posix' ;
12
19
import { JSONFile } from '../../utility/json-file' ;
13
- import { TreeWorkspaceHost , allTargetOptions , getWorkspace } from '../../utility/workspace' ;
20
+ import { allTargetOptions , updateWorkspace } from '../../utility/workspace' ;
14
21
import { Builders , ProjectType } from '../../utility/workspace-models' ;
15
22
16
- export default function ( ) : Rule {
17
- return async ( tree , context ) => {
23
+ function * updateBuildTarget (
24
+ projectName : string ,
25
+ buildTarget : workspaces . TargetDefinition ,
26
+ serverTarget : workspaces . TargetDefinition | undefined ,
27
+ tree : Tree ,
28
+ context : SchematicContext ,
29
+ ) : Iterable < Rule > {
30
+ // Update builder target and options
31
+ buildTarget . builder = Builders . Application ;
32
+
33
+ for ( const [ , options ] of allTargetOptions ( buildTarget , false ) ) {
34
+ // Show warnings for using no longer supported options
35
+ if ( usesNoLongerSupportedOptions ( options , context , projectName ) ) {
36
+ continue ;
37
+ }
38
+
39
+ if ( options [ 'index' ] === '' ) {
40
+ options [ 'index' ] = false ;
41
+ }
42
+
43
+ // Rename and transform options
44
+ options [ 'browser' ] = options [ 'main' ] ;
45
+ if ( serverTarget && typeof options [ 'browser' ] === 'string' ) {
46
+ options [ 'server' ] = dirname ( options [ 'browser' ] ) + '/main.server.ts' ;
47
+ }
48
+ options [ 'serviceWorker' ] = options [ 'ngswConfigPath' ] ?? options [ 'serviceWorker' ] ;
49
+
50
+ if ( typeof options [ 'polyfills' ] === 'string' ) {
51
+ options [ 'polyfills' ] = [ options [ 'polyfills' ] ] ;
52
+ }
53
+
54
+ let outputPath = options [ 'outputPath' ] ;
55
+ if ( typeof outputPath === 'string' ) {
56
+ if ( ! / \/ b r o w s e r \/ ? $ / . test ( outputPath ) ) {
57
+ // TODO: add prompt.
58
+ context . logger . warn (
59
+ `The output location of the browser build has been updated from "${ outputPath } " to ` +
60
+ `"${ join ( outputPath , 'browser' ) } ". ` +
61
+ 'You might need to adjust your deployment pipeline or, as an alternative, ' +
62
+ 'set outputPath.browser to "" in order to maintain the previous functionality.' ,
63
+ ) ;
64
+ } else {
65
+ outputPath = outputPath . replace ( / \/ b r o w s e r \/ ? $ / , '' ) ;
66
+ }
67
+
68
+ options [ 'outputPath' ] = {
69
+ base : outputPath ,
70
+ } ;
71
+
72
+ if ( typeof options [ 'resourcesOutputPath' ] === 'string' ) {
73
+ const media = options [ 'resourcesOutputPath' ] . replaceAll ( '/' , '' ) ;
74
+ if ( media && media !== 'media' ) {
75
+ options [ 'outputPath' ] = {
76
+ base : outputPath ,
77
+ media,
78
+ } ;
79
+ }
80
+ }
81
+ }
82
+
83
+ // Delete removed options
84
+ delete options [ 'deployUrl' ] ;
85
+ delete options [ 'vendorChunk' ] ;
86
+ delete options [ 'commonChunk' ] ;
87
+ delete options [ 'resourcesOutputPath' ] ;
88
+ delete options [ 'buildOptimizer' ] ;
89
+ delete options [ 'main' ] ;
90
+ delete options [ 'ngswConfigPath' ] ;
91
+ }
92
+
93
+ // Merge browser and server tsconfig
94
+ if ( serverTarget ) {
95
+ const browserTsConfig = buildTarget . options ?. tsConfig ;
96
+ const serverTsConfig = serverTarget . options ?. tsConfig ;
97
+
98
+ if ( typeof browserTsConfig !== 'string' ) {
99
+ throw new SchematicsException (
100
+ `Cannot update project "${ projectName } " to use the application builder` +
101
+ ` as the browser tsconfig cannot be located.` ,
102
+ ) ;
103
+ }
104
+
105
+ if ( typeof serverTsConfig !== 'string' ) {
106
+ throw new SchematicsException (
107
+ `Cannot update project "${ projectName } " to use the application builder` +
108
+ ` as the server tsconfig cannot be located.` ,
109
+ ) ;
110
+ }
111
+
112
+ const browserJson = new JSONFile ( tree , browserTsConfig ) ;
113
+ const serverJson = new JSONFile ( tree , serverTsConfig ) ;
114
+
115
+ const filesPath = [ 'files' ] ;
116
+
117
+ const files = new Set ( [
118
+ ...( ( browserJson . get ( filesPath ) as string [ ] | undefined ) ?? [ ] ) ,
119
+ ...( ( serverJson . get ( filesPath ) as string [ ] | undefined ) ?? [ ] ) ,
120
+ ] ) ;
121
+
122
+ // Server file will be added later by the means of the ssr schematic.
123
+ files . delete ( 'server.ts' ) ;
124
+
125
+ browserJson . modify ( filesPath , Array . from ( files ) ) ;
126
+
127
+ const typesPath = [ 'compilerOptions' , 'types' ] ;
128
+ browserJson . modify (
129
+ typesPath ,
130
+ Array . from (
131
+ new Set ( [
132
+ ...( ( browserJson . get ( typesPath ) as string [ ] | undefined ) ?? [ ] ) ,
133
+ ...( ( serverJson . get ( typesPath ) as string [ ] | undefined ) ?? [ ] ) ,
134
+ ] ) ,
135
+ ) ,
136
+ ) ;
137
+
138
+ // Delete server tsconfig
139
+ yield deleteFile ( serverTsConfig ) ;
140
+ }
141
+
142
+ // Update server file
143
+ const ssrMainFile = serverTarget ?. options ?. [ 'main' ] ;
144
+ if ( typeof ssrMainFile === 'string' ) {
145
+ yield deleteFile ( ssrMainFile ) ;
146
+
147
+ yield externalSchematic ( '@schematics/angular' , 'ssr' , {
148
+ project : projectName ,
149
+ skipInstall : true ,
150
+ } ) ;
151
+ }
152
+ }
153
+
154
+ function updateProjects ( tree : Tree , context : SchematicContext ) {
155
+ return updateWorkspace ( ( workspace ) => {
18
156
const rules : Rule [ ] = [ ] ;
19
- const workspace = await getWorkspace ( tree ) ;
20
157
21
158
for ( const [ name , project ] of workspace . projects ) {
22
159
if ( project . extensions . projectType !== ProjectType . Application ) {
@@ -41,137 +178,9 @@ export default function (): Rule {
41
178
continue ;
42
179
}
43
180
44
- // Update builder target and options
45
- buildTarget . builder = Builders . Application ;
46
- const hasServerTarget = project . targets . has ( 'server' ) ;
47
-
48
- for ( const [ , options ] of allTargetOptions ( buildTarget , false ) ) {
49
- if ( options [ 'index' ] === '' ) {
50
- options [ 'index' ] = false ;
51
- }
52
-
53
- // Rename and transform options
54
- options [ 'browser' ] = options [ 'main' ] ;
55
- if ( hasServerTarget && typeof options [ 'browser' ] === 'string' ) {
56
- options [ 'server' ] = dirname ( options [ 'browser' ] ) + '/main.server.ts' ;
57
- }
58
- options [ 'serviceWorker' ] = options [ 'ngswConfigPath' ] ?? options [ 'serviceWorker' ] ;
59
-
60
- if ( typeof options [ 'polyfills' ] === 'string' ) {
61
- options [ 'polyfills' ] = [ options [ 'polyfills' ] ] ;
62
- }
63
-
64
- let outputPath = options [ 'outputPath' ] ;
65
- if ( typeof outputPath === 'string' ) {
66
- if ( ! / \/ b r o w s e r \/ ? $ / . test ( outputPath ) ) {
67
- // TODO: add prompt.
68
- context . logger . warn (
69
- `The output location of the browser build has been updated from "${ outputPath } " to ` +
70
- `"${ join ( outputPath , 'browser' ) } ". ` +
71
- 'You might need to adjust your deployment pipeline or, as an alternative, ' +
72
- 'set outputPath.browser to "" in order to maintain the previous functionality.' ,
73
- ) ;
74
- } else {
75
- outputPath = outputPath . replace ( / \/ b r o w s e r \/ ? $ / , '' ) ;
76
- }
77
-
78
- options [ 'outputPath' ] = {
79
- base : outputPath ,
80
- } ;
81
-
82
- if ( typeof options [ 'resourcesOutputPath' ] === 'string' ) {
83
- const media = options [ 'resourcesOutputPath' ] . replaceAll ( '/' , '' ) ;
84
- if ( media && media !== 'media' ) {
85
- options [ 'outputPath' ] = {
86
- base : outputPath ,
87
- media : media ,
88
- } ;
89
- }
90
- }
91
- }
92
-
93
- // Delete removed options
94
- delete options [ 'vendorChunk' ] ;
95
- delete options [ 'commonChunk' ] ;
96
- delete options [ 'resourcesOutputPath' ] ;
97
- delete options [ 'buildOptimizer' ] ;
98
- delete options [ 'main' ] ;
99
- delete options [ 'ngswConfigPath' ] ;
100
- }
101
-
102
- // Merge browser and server tsconfig
103
- if ( hasServerTarget ) {
104
- const browserTsConfig = buildTarget ?. options ?. tsConfig ;
105
- const serverTsConfig = project . targets . get ( 'server' ) ?. options ?. tsConfig ;
106
-
107
- if ( typeof browserTsConfig !== 'string' ) {
108
- throw new SchematicsException (
109
- `Cannot update project "${ name } " to use the application builder` +
110
- ` as the browser tsconfig cannot be located.` ,
111
- ) ;
112
- }
113
-
114
- if ( typeof serverTsConfig !== 'string' ) {
115
- throw new SchematicsException (
116
- `Cannot update project "${ name } " to use the application builder` +
117
- ` as the server tsconfig cannot be located.` ,
118
- ) ;
119
- }
120
-
121
- const browserJson = new JSONFile ( tree , browserTsConfig ) ;
122
- const serverJson = new JSONFile ( tree , serverTsConfig ) ;
123
-
124
- const filesPath = [ 'files' ] ;
125
-
126
- const files = new Set ( [
127
- ...( ( browserJson . get ( filesPath ) as string [ ] | undefined ) ?? [ ] ) ,
128
- ...( ( serverJson . get ( filesPath ) as string [ ] | undefined ) ?? [ ] ) ,
129
- ] ) ;
130
-
131
- // Server file will be added later by the means of the ssr schematic.
132
- files . delete ( 'server.ts' ) ;
133
-
134
- browserJson . modify ( filesPath , Array . from ( files ) ) ;
135
-
136
- const typesPath = [ 'compilerOptions' , 'types' ] ;
137
- browserJson . modify (
138
- typesPath ,
139
- Array . from (
140
- new Set ( [
141
- ...( ( browserJson . get ( typesPath ) as string [ ] | undefined ) ?? [ ] ) ,
142
- ...( ( serverJson . get ( typesPath ) as string [ ] | undefined ) ?? [ ] ) ,
143
- ] ) ,
144
- ) ,
145
- ) ;
146
-
147
- // Delete server tsconfig
148
- tree . delete ( serverTsConfig ) ;
149
- }
150
-
151
- // Update main tsconfig
152
- const rootJson = new JSONFile ( tree , 'tsconfig.json' ) ;
153
- rootJson . modify ( [ 'compilerOptions' , 'esModuleInterop' ] , true ) ;
154
- rootJson . modify ( [ 'compilerOptions' , 'downlevelIteration' ] , undefined ) ;
155
- rootJson . modify ( [ 'compilerOptions' , 'allowSyntheticDefaultImports' ] , undefined ) ;
181
+ const serverTarget = project . targets . get ( 'server' ) ;
156
182
157
- // Update server file
158
- const ssrMainFile = project . targets . get ( 'server' ) ?. options ?. [ 'main' ] ;
159
- if ( typeof ssrMainFile === 'string' ) {
160
- tree . delete ( ssrMainFile ) ;
161
-
162
- rules . push (
163
- externalSchematic ( '@schematics/angular' , 'ssr' , {
164
- project : name ,
165
- skipInstall : true ,
166
- } ) ,
167
- ) ;
168
- }
169
-
170
- // Delete package.json helper scripts
171
- const pkgJson = new JSONFile ( tree , 'package.json' ) ;
172
- [ 'build:ssr' , 'dev:ssr' , 'serve:ssr' , 'prerender' ] . forEach ( ( s ) =>
173
- pkgJson . remove ( [ 'scripts' , s ] ) ,
174
- ) ;
183
+ rules . push ( ...updateBuildTarget ( name , buildTarget , serverTarget , tree , context ) ) ;
175
184
176
185
// Delete all redundant targets
177
186
for ( const [ key , target ] of project . targets ) {
@@ -186,9 +195,55 @@ export default function (): Rule {
186
195
}
187
196
}
188
197
189
- // Save workspace changes
190
- await workspaces . writeWorkspace ( workspace , new TreeWorkspaceHost ( tree ) ) ;
191
-
192
198
return chain ( rules ) ;
199
+ } ) ;
200
+ }
201
+
202
+ function deleteFile ( path : string ) : Rule {
203
+ return ( tree ) => {
204
+ tree . delete ( path ) ;
193
205
} ;
194
206
}
207
+
208
+ function updateJsonFile ( path : string , updater : ( json : JSONFile ) => void ) : Rule {
209
+ return ( tree ) => {
210
+ updater ( new JSONFile ( tree , path ) ) ;
211
+ } ;
212
+ }
213
+
214
+ /**
215
+ * Migration main entrypoint
216
+ */
217
+ export default function ( ) : Rule {
218
+ return chain ( [
219
+ updateProjects ,
220
+ // Delete package.json helper scripts
221
+ updateJsonFile ( 'package.json' , ( pkgJson ) =>
222
+ [ 'build:ssr' , 'dev:ssr' , 'serve:ssr' , 'prerender' ] . forEach ( ( s ) =>
223
+ pkgJson . remove ( [ 'scripts' , s ] ) ,
224
+ ) ,
225
+ ) ,
226
+ // Update main tsconfig
227
+ updateJsonFile ( 'tsconfig.json' , ( rootJson ) => {
228
+ rootJson . modify ( [ 'compilerOptions' , 'esModuleInterop' ] , true ) ;
229
+ rootJson . modify ( [ 'compilerOptions' , 'downlevelIteration' ] , undefined ) ;
230
+ rootJson . modify ( [ 'compilerOptions' , 'allowSyntheticDefaultImports' ] , undefined ) ;
231
+ } ) ,
232
+ ] ) ;
233
+ }
234
+
235
+ function usesNoLongerSupportedOptions (
236
+ { deployUrl } : Record < string , unknown > ,
237
+ context : SchematicContext ,
238
+ projectName : string ,
239
+ ) : boolean {
240
+ let hasUsage = false ;
241
+ if ( typeof deployUrl === 'string' ) {
242
+ hasUsage = true ;
243
+ context . logger . warn (
244
+ `Skipping migration for project "${ projectName } ". "deployUrl" option is not available in the application builder.` ,
245
+ ) ;
246
+ }
247
+
248
+ return hasUsage ;
249
+ }
0 commit comments