8
8
* @fileoverview Schematics for ng-new project that builds with Bazel.
9
9
*/
10
10
11
- import { virtualFs , workspaces } from '@angular-devkit/core' ;
12
- import { chain , noop , Rule , SchematicContext , SchematicsException , Tree } from '@angular-devkit/schematics' ;
11
+ import { tags } from '@angular-devkit/core' ;
12
+ import { chain , noop , Rule , SchematicContext , SchematicsException , Tree , } from '@angular-devkit/schematics' ;
13
13
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks' ;
14
- import { addPackageJsonDependency , NodeDependencyType , removePackageJsonDependency } from '@schematics/angular/utility/dependencies' ;
15
- import { getWorkspace } from '@schematics/angular/utility/workspace' ;
14
+ import { addPackageJsonDependency , NodeDependencyType , removePackageJsonDependency , } from '@schematics/angular/utility/dependencies' ;
15
+ import { allTargetOptions , getWorkspace , updateWorkspace , } from '@schematics/angular/utility/workspace' ;
16
16
import { Builders } from '@schematics/angular/utility/workspace-models' ;
17
17
18
18
import { Schema } from './schema' ;
19
19
20
- export const localizePolyfill = `import ' @angular/localize/init'; ` ;
20
+ export const localizePolyfill = `@angular/localize/init` ;
21
21
22
- function getRelevantTargetDefinitions (
23
- project : workspaces . ProjectDefinition , builderName : Builders ) : workspaces . TargetDefinition [ ] {
24
- const definitions : workspaces . TargetDefinition [ ] = [ ] ;
25
- project . targets . forEach ( ( target : workspaces . TargetDefinition ) : void => {
26
- if ( target . builder === builderName ) {
27
- definitions . push ( target ) ;
22
+ function prependToMainFiles ( projectName : string ) : Rule {
23
+ return async ( host : Tree ) => {
24
+ const workspace = await getWorkspace ( host ) ;
25
+ const project = workspace . projects . get ( projectName ) ;
26
+ if ( ! project ) {
27
+ throw new SchematicsException ( `Invalid project name ( ${ projectName } )` ) ;
28
28
}
29
- } ) ;
30
- return definitions ;
31
- }
32
29
33
- function getOptionValuesForTargetDefinition (
34
- definition : workspaces . TargetDefinition , optionName : string ) : string [ ] {
35
- const optionValues : string [ ] = [ ] ;
36
- if ( definition . options && optionName in definition . options ) {
37
- let optionValue : unknown = definition . options [ optionName ] ;
38
- if ( typeof optionValue === 'string' ) {
39
- optionValues . push ( optionValue ) ;
40
- }
41
- }
42
- if ( ! definition . configurations ) {
43
- return optionValues ;
44
- }
45
- Object . values ( definition . configurations )
46
- . forEach ( ( configuration : Record < string , unknown > | undefined ) : void => {
47
- if ( configuration && optionName in configuration ) {
48
- const optionValue : unknown = configuration [ optionName ] ;
49
- if ( typeof optionValue === 'string' ) {
50
- optionValues . push ( optionValue ) ;
51
- }
52
- }
53
- } ) ;
54
- return optionValues ;
55
- }
56
-
57
- function getFileListForRelevantTargetDefinitions (
58
- project : workspaces . ProjectDefinition , builderName : Builders , optionName : string ) : string [ ] {
59
- const fileList : string [ ] = [ ] ;
60
- const definitions = getRelevantTargetDefinitions ( project , builderName ) ;
61
- definitions . forEach ( ( definition : workspaces . TargetDefinition ) : void => {
62
- const optionValues = getOptionValuesForTargetDefinition ( definition , optionName ) ;
63
- optionValues . forEach ( ( filePath : string ) : void => {
64
- if ( fileList . indexOf ( filePath ) === - 1 ) {
65
- fileList . push ( filePath ) ;
30
+ const fileList = new Set < string > ( ) ;
31
+ for ( const target of project . targets . values ( ) ) {
32
+ if ( target . builder !== Builders . Server ) {
33
+ continue ;
66
34
}
67
- } ) ;
68
- } ) ;
69
- return fileList ;
70
- }
71
-
72
- function prependToTargetFiles (
73
- project : workspaces . ProjectDefinition , builderName : Builders , optionName : string , str : string ) {
74
- return ( host : Tree ) => {
75
- const fileList = getFileListForRelevantTargetDefinitions ( project , builderName , optionName ) ;
76
35
77
- fileList . forEach ( ( path : string ) : void => {
78
- const data = host . read ( path ) ;
79
- if ( ! data ) {
80
- // If the file doesn't exist, just ignore it.
81
- return ;
36
+ for ( const [ , options ] of allTargetOptions ( target ) ) {
37
+ const value = options [ 'main' ] ;
38
+ if ( typeof value === 'string' ) {
39
+ fileList . add ( value ) ;
40
+ }
82
41
}
42
+ }
83
43
84
- const content = virtualFs . fileBufferToString ( data ) ;
85
- if ( content . includes ( localizePolyfill ) ||
86
- content . includes ( localizePolyfill . replace ( / ' / g , '"' ) ) ) {
44
+ for ( const path of fileList ) {
45
+ const content = host . readText ( path ) ;
46
+ if ( content . includes ( localizePolyfill ) ) {
87
47
// If the file already contains the polyfill (or variations), ignore it too.
88
- return ;
48
+ continue ;
89
49
}
90
50
91
51
// Add string at the start of the file.
92
52
const recorder = host . beginUpdate ( path ) ;
93
- recorder . insertLeft ( 0 , str ) ;
53
+
54
+ const localizeStr =
55
+ tags . stripIndents `/***************************************************************************************************
56
+ * Load \`$localize\` onto the global scope - used if i18n tags appear in Angular templates.
57
+ */
58
+ import '${ localizePolyfill } ';
59
+ ` ;
60
+ recorder . insertLeft ( 0 , localizeStr ) ;
94
61
host . commitUpdate ( recorder ) ;
95
- } ) ;
62
+ }
96
63
} ;
97
64
}
98
65
99
- function moveToDependencies ( host : Tree , context : SchematicContext ) {
66
+ function addToPolyfillsOption ( projectName : string ) : Rule {
67
+ return updateWorkspace ( ( workspace ) => {
68
+ const project = workspace . projects . get ( projectName ) ;
69
+ if ( ! project ) {
70
+ throw new SchematicsException ( `Invalid project name (${ projectName } )` ) ;
71
+ }
72
+
73
+ for ( const target of project . targets . values ( ) ) {
74
+ if ( target . builder !== Builders . Browser && target . builder !== Builders . Karma ) {
75
+ continue ;
76
+ }
77
+
78
+ target . options ??= { } ;
79
+ target . options [ 'polyfills' ] ??= [ localizePolyfill ] ;
80
+
81
+ for ( const [ , options ] of allTargetOptions ( target ) ) {
82
+ // Convert polyfills option to array.
83
+ const polyfillsValue = typeof options [ 'polyfills' ] === 'string' ? [ options [ 'polyfills' ] ] :
84
+ options [ 'polyfills' ] ;
85
+ if ( Array . isArray ( polyfillsValue ) && ! polyfillsValue . includes ( localizePolyfill ) ) {
86
+ options [ 'polyfills' ] = [ ...polyfillsValue , localizePolyfill ] ;
87
+ }
88
+ }
89
+ }
90
+ } ) ;
91
+ }
92
+
93
+ function moveToDependencies ( host : Tree , context : SchematicContext ) : void {
100
94
if ( host . exists ( 'package.json' ) ) {
101
95
// Remove the previous dependency and add in a new one under the desired type.
102
96
removePackageJsonDependency ( host , '@angular/localize' ) ;
103
97
addPackageJsonDependency ( host , {
104
98
name : '@angular/localize' ,
105
99
type : NodeDependencyType . Default ,
106
- version : `~0.0.0-PLACEHOLDER`
100
+ version : `~0.0.0-PLACEHOLDER` ,
107
101
} ) ;
108
102
109
103
// Add a task to run the package manager. This is necessary because we updated
@@ -113,7 +107,7 @@ function moveToDependencies(host: Tree, context: SchematicContext) {
113
107
}
114
108
115
109
export default function ( options : Schema ) : Rule {
116
- return async ( host : Tree ) => {
110
+ return ( ) => {
117
111
// We favor the name option because the project option has a
118
112
// smart default which can be populated even when unspecified by the user.
119
113
const projectName = options . name ?? options . project ;
@@ -122,22 +116,9 @@ export default function(options: Schema): Rule {
122
116
throw new SchematicsException ( 'Option "project" is required.' ) ;
123
117
}
124
118
125
- const workspace = await getWorkspace ( host ) ;
126
- const project : workspaces . ProjectDefinition | undefined = workspace . projects . get ( projectName ) ;
127
- if ( ! project ) {
128
- throw new SchematicsException ( `Invalid project name (${ projectName } )` ) ;
129
- }
130
-
131
- const localizeStr =
132
- `/***************************************************************************************************
133
- * Load \`$localize\` onto the global scope - used if i18n tags appear in Angular templates.
134
- */
135
- ${ localizePolyfill }
136
- ` ;
137
-
138
119
return chain ( [
139
- prependToTargetFiles ( project , Builders . Browser , 'polyfills' , localizeStr ) ,
140
- prependToTargetFiles ( project , Builders . Server , 'main' , localizeStr ) ,
120
+ prependToMainFiles ( projectName ) ,
121
+ addToPolyfillsOption ( projectName ) ,
141
122
// If `$localize` will be used at runtime then must install `@angular/localize`
142
123
// into `dependencies`, rather than the default of `devDependencies`.
143
124
options . useAtRuntime ? moveToDependencies : noop ( ) ,
0 commit comments