88 * @fileoverview Schematics for ng-new project that builds with Bazel.
99 */
1010
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' ;
1313import { 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' ;
1616import { Builders } from '@schematics/angular/utility/workspace-models' ;
1717
1818import { Schema } from './schema' ;
1919
20- export const localizePolyfill = `import ' @angular/localize/init'; ` ;
20+ export const localizePolyfill = `@angular/localize/init` ;
2121
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 } )` ) ;
2828 }
29- } ) ;
30- return definitions ;
31- }
3229
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 ;
6634 }
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 ) ;
7635
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+ }
8241 }
42+ }
8343
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 ) ) {
8747 // If the file already contains the polyfill (or variations), ignore it too.
88- return ;
48+ continue ;
8949 }
9050
9151 // Add string at the start of the file.
9252 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 ) ;
9461 host . commitUpdate ( recorder ) ;
95- } ) ;
62+ }
9663 } ;
9764}
9865
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 {
10094 if ( host . exists ( 'package.json' ) ) {
10195 // Remove the previous dependency and add in a new one under the desired type.
10296 removePackageJsonDependency ( host , '@angular/localize' ) ;
10397 addPackageJsonDependency ( host , {
10498 name : '@angular/localize' ,
10599 type : NodeDependencyType . Default ,
106- version : `~0.0.0-PLACEHOLDER`
100+ version : `~0.0.0-PLACEHOLDER` ,
107101 } ) ;
108102
109103 // Add a task to run the package manager. This is necessary because we updated
@@ -113,7 +107,7 @@ function moveToDependencies(host: Tree, context: SchematicContext) {
113107}
114108
115109export default function ( options : Schema ) : Rule {
116- return async ( host : Tree ) => {
110+ return ( ) => {
117111 // We favor the name option because the project option has a
118112 // smart default which can be populated even when unspecified by the user.
119113 const projectName = options . name ?? options . project ;
@@ -122,22 +116,9 @@ export default function(options: Schema): Rule {
122116 throw new SchematicsException ( 'Option "project" is required.' ) ;
123117 }
124118
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-
138119 return chain ( [
139- prependToTargetFiles ( project , Builders . Browser , 'polyfills' , localizeStr ) ,
140- prependToTargetFiles ( project , Builders . Server , 'main' , localizeStr ) ,
120+ prependToMainFiles ( projectName ) ,
121+ addToPolyfillsOption ( projectName ) ,
141122 // If `$localize` will be used at runtime then must install `@angular/localize`
142123 // into `dependencies`, rather than the default of `devDependencies`.
143124 options . useAtRuntime ? moveToDependencies : noop ( ) ,
0 commit comments