6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
import {
9
+ JsonAstObject ,
9
10
JsonObject ,
10
11
JsonParseMode ,
11
12
join ,
12
13
normalize ,
13
14
parseJsonAst ,
14
- relative ,
15
15
strings ,
16
16
} from '@angular-devkit/core' ;
17
17
import {
@@ -103,6 +103,78 @@ function addDependenciesToPackageJson(options: ApplicationOptions) {
103
103
} ;
104
104
}
105
105
106
+ function readTsLintConfig ( host : Tree , path : string ) : JsonAstObject {
107
+ const buffer = host . read ( path ) ;
108
+ if ( ! buffer ) {
109
+ throw new SchematicsException ( `Could not read ${ path } .` ) ;
110
+ }
111
+
112
+ const config = parseJsonAst ( buffer . toString ( ) , JsonParseMode . Loose ) ;
113
+ if ( config . kind !== 'object' ) {
114
+ throw new SchematicsException ( `Invalid ${ path } . Was expecting an object.` ) ;
115
+ }
116
+
117
+ return config ;
118
+ }
119
+
120
+ /**
121
+ * Merges the application tslint.json with the workspace tslint.json
122
+ * when the application being created is a root application
123
+ *
124
+ * @param {Tree } parentHost The root host of the schematic
125
+ */
126
+ function mergeWithRootTsLint ( parentHost : Tree ) {
127
+ return ( host : Tree ) => {
128
+ const tsLintPath = '/tslint.json' ;
129
+ if ( ! host . exists ( tsLintPath ) ) {
130
+ return ;
131
+ }
132
+
133
+ const rootTslintConfig = readTsLintConfig ( parentHost , tsLintPath ) ;
134
+ const appTslintConfig = readTsLintConfig ( host , tsLintPath ) ;
135
+
136
+ const recorder = host . beginUpdate ( tsLintPath ) ;
137
+ rootTslintConfig . properties . forEach ( prop => {
138
+ if ( findPropertyInAstObject ( appTslintConfig , prop . key . value ) ) {
139
+ // property already exists. Skip!
140
+ return ;
141
+ }
142
+
143
+ insertPropertyInAstObjectInOrder (
144
+ recorder ,
145
+ appTslintConfig ,
146
+ prop . key . value ,
147
+ prop . value . value ,
148
+ 2 ,
149
+ ) ;
150
+ } ) ;
151
+
152
+ const rootRules = findPropertyInAstObject ( rootTslintConfig , 'rules' ) ;
153
+ const appRules = findPropertyInAstObject ( appTslintConfig , 'rules' ) ;
154
+
155
+ if ( ! appRules || appRules . kind !== 'object' || ! rootRules || rootRules . kind !== 'object' ) {
156
+ // rules are not valid. Skip!
157
+ return ;
158
+ }
159
+
160
+ rootRules . properties . forEach ( prop => {
161
+ insertPropertyInAstObjectInOrder (
162
+ recorder ,
163
+ appRules ,
164
+ prop . key . value ,
165
+ prop . value . value ,
166
+ 4 ,
167
+ ) ;
168
+ } ) ;
169
+
170
+ host . commitUpdate ( recorder ) ;
171
+
172
+ // this shouldn't be needed but at the moment without this formatting is not correct.
173
+ const content = readTsLintConfig ( host , tsLintPath ) ;
174
+ host . overwrite ( tsLintPath , JSON . stringify ( content . value , undefined , 2 ) ) ;
175
+ } ;
176
+ }
177
+
106
178
function addPostInstallScript ( ) {
107
179
return ( host : Tree ) => {
108
180
const pkgJsonPath = '/package.json' ;
@@ -148,15 +220,14 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
148
220
// if (workspaceJson.value === null) {
149
221
// throw new SchematicsException(`Unable to parse configuration file (${workspacePath}).`);
150
222
// }
223
+
151
224
let projectRoot = options . projectRoot !== undefined
152
225
? options . projectRoot
153
- : `${ workspace . newProjectRoot || '' } /${ options . name } ` ;
226
+ : `${ workspace . newProjectRoot } /${ options . name } ` ;
227
+
154
228
if ( projectRoot !== '' && ! projectRoot . endsWith ( '/' ) ) {
155
229
projectRoot += '/' ;
156
230
}
157
- const rootFilesRoot = options . projectRoot === undefined
158
- ? projectRoot
159
- : projectRoot + 'src/' ;
160
231
161
232
const schematics : JsonObject = { } ;
162
233
@@ -186,9 +257,10 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
186
257
} ) ;
187
258
}
188
259
260
+ const sourceRoot = join ( normalize ( projectRoot ) , 'src' ) ;
189
261
const project : WorkspaceProject = {
190
262
root : projectRoot ,
191
- sourceRoot : join ( normalize ( projectRoot ) , 'src' ) ,
263
+ sourceRoot,
192
264
projectType : ProjectType . Application ,
193
265
prefix : options . prefix || 'app' ,
194
266
schematics,
@@ -197,25 +269,25 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
197
269
builder : Builders . Browser ,
198
270
options : {
199
271
outputPath : `dist/${ options . name } ` ,
200
- index : `${ projectRoot } src /index.html` ,
201
- main : `${ projectRoot } src /main.ts` ,
202
- polyfills : `${ projectRoot } src /polyfills.ts` ,
203
- tsConfig : `${ rootFilesRoot } tsconfig.app.json` ,
272
+ index : `${ sourceRoot } /index.html` ,
273
+ main : `${ sourceRoot } /main.ts` ,
274
+ polyfills : `${ sourceRoot } /polyfills.ts` ,
275
+ tsConfig : `${ projectRoot } tsconfig.app.json` ,
204
276
assets : [
205
- join ( normalize ( projectRoot ) , 'src' , ' favicon.ico' ) ,
206
- join ( normalize ( projectRoot ) , 'src' , ' assets' ) ,
277
+ ` ${ sourceRoot } / favicon.ico` ,
278
+ ` ${ sourceRoot } / assets` ,
207
279
] ,
208
280
styles : [
209
- `${ projectRoot } src /styles.${ options . style } ` ,
281
+ `${ sourceRoot } /styles.${ options . style } ` ,
210
282
] ,
211
283
scripts : [ ] ,
212
284
es5BrowserSupport : true ,
213
285
} ,
214
286
configurations : {
215
287
production : {
216
288
fileReplacements : [ {
217
- replace : `${ projectRoot } src /environments/environment.ts` ,
218
- with : `${ projectRoot } src /environments/environment.prod.ts` ,
289
+ replace : `${ sourceRoot } /environments/environment.ts` ,
290
+ with : `${ sourceRoot } /environments/environment.prod.ts` ,
219
291
} ] ,
220
292
optimization : true ,
221
293
outputHashing : 'all' ,
@@ -254,26 +326,26 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
254
326
test : {
255
327
builder : Builders . Karma ,
256
328
options : {
257
- main : `${ projectRoot } src/test.ts` ,
258
- polyfills : `${ projectRoot } src/polyfills.ts` ,
259
- tsConfig : `${ rootFilesRoot } tsconfig.spec.json` ,
260
- karmaConfig : `${ rootFilesRoot } karma.conf.js` ,
329
+ main : `${ sourceRoot } /test.ts` ,
330
+ polyfills : `${ sourceRoot } /polyfills.ts` ,
331
+ tsConfig : `${ projectRoot } tsconfig.spec.json` ,
332
+ karmaConfig : `${ projectRoot } karma.conf.js` ,
333
+ assets : [
334
+ `${ sourceRoot } /favicon.ico` ,
335
+ `${ sourceRoot } /assets` ,
336
+ ] ,
261
337
styles : [
262
- `${ projectRoot } src /styles.${ options . style } ` ,
338
+ `${ sourceRoot } /styles.${ options . style } ` ,
263
339
] ,
264
340
scripts : [ ] ,
265
- assets : [
266
- join ( normalize ( projectRoot ) , 'src' , 'favicon.ico' ) ,
267
- join ( normalize ( projectRoot ) , 'src' , 'assets' ) ,
268
- ] ,
269
341
} ,
270
342
} ,
271
343
lint : {
272
344
builder : Builders . TsLint ,
273
345
options : {
274
346
tsConfig : [
275
- `${ rootFilesRoot } tsconfig.app.json` ,
276
- `${ rootFilesRoot } tsconfig.spec.json` ,
347
+ `${ projectRoot } tsconfig.app.json` ,
348
+ `${ projectRoot } tsconfig.spec.json` ,
277
349
] ,
278
350
exclude : [
279
351
'**/node_modules/**' ,
@@ -282,19 +354,12 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
282
354
} ,
283
355
} ,
284
356
} ;
285
- // tslint:disable-next-line:no-any
286
- // const projects: JsonObject = (<any> workspaceAst.value).projects || {};
287
- // tslint:disable-next-line:no-any
288
- // if (!(<any> workspaceAst.value).projects) {
289
- // // tslint:disable-next-line:no-any
290
- // (<any> workspaceAst.value).projects = projects;
291
- // }
292
357
293
358
return addProjectToWorkspace ( workspace , options . name , project ) ;
294
359
}
295
360
296
361
function minimalPathFilter ( path : string ) : boolean {
297
- const toRemoveList = / ( t e s t .t s | t s c o n f i g .s p e c .j s o n | k a r m a .c o n f .j s ) .t e m p l a t e $ / ;
362
+ const toRemoveList = / ( t e s t .t s | t s c o n f i g .s p e c .j s o n | k a r m a .c o n f .j s | t s l i n t . j s o n ) .t e m p l a t e $ / ;
298
363
299
364
return ! toRemoveList . test ( path ) ;
300
365
}
@@ -305,8 +370,8 @@ export default function (options: ApplicationOptions): Rule {
305
370
throw new SchematicsException ( `Invalid options, "name" is required.` ) ;
306
371
}
307
372
validateProjectName ( options . name ) ;
308
- const prefix = options . prefix || 'app' ;
309
- const appRootSelector = `${ prefix } -root` ;
373
+ options . prefix = options . prefix || 'app' ;
374
+ const appRootSelector = `${ options . prefix } -root` ;
310
375
const componentOptions : Partial < ComponentOptions > = ! options . minimal ?
311
376
{
312
377
inlineStyle : options . inlineStyle ,
@@ -323,23 +388,15 @@ export default function (options: ApplicationOptions): Rule {
323
388
} ;
324
389
325
390
const workspace = getWorkspace ( host ) ;
326
- let newProjectRoot = workspace . newProjectRoot || '' ;
327
- let appDir = `${ newProjectRoot } /${ options . name } ` ;
328
- let sourceRoot = `${ appDir } /src` ;
329
- let sourceDir = `${ sourceRoot } /app` ;
330
- let relativePathToWorkspaceRoot = appDir . split ( '/' ) . map ( x => '..' ) . join ( '/' ) ;
331
- const rootInSrc = options . projectRoot !== undefined ;
332
- if ( options . projectRoot !== undefined ) {
333
- newProjectRoot = options . projectRoot ;
334
- appDir = `${ newProjectRoot } /src` ;
335
- sourceRoot = appDir ;
336
- sourceDir = `${ sourceRoot } /app` ;
337
- relativePathToWorkspaceRoot = relative ( normalize ( '/' + sourceRoot ) , normalize ( '/' ) ) ;
338
- if ( relativePathToWorkspaceRoot === '' ) {
339
- relativePathToWorkspaceRoot = '.' ;
340
- }
341
- }
342
- const tsLintRoot = appDir ;
391
+ const newProjectRoot = workspace . newProjectRoot || '' ;
392
+ const isRootApp = options . projectRoot !== undefined ;
393
+ const appDir = isRootApp
394
+ ? options . projectRoot as string
395
+ : `${ newProjectRoot } /${ options . name } ` ;
396
+ const relativePathToWorkspaceRoot = appDir
397
+ ? appDir . split ( '/' ) . map ( ( ) => '..' ) . join ( '/' )
398
+ : '.' ;
399
+ const sourceDir = `${ appDir } /src/app` ;
343
400
344
401
const e2eOptions : E2eOptions = {
345
402
relatedAppName : options . name ,
@@ -349,43 +406,18 @@ export default function (options: ApplicationOptions): Rule {
349
406
return chain ( [
350
407
addAppToWorkspaceFile ( options , workspace ) ,
351
408
mergeWith (
352
- apply ( url ( './files/src' ) , [
353
- options . minimal ? filter ( minimalPathFilter ) : noop ( ) ,
354
- applyTemplates ( {
355
- utils : strings ,
356
- ...options ,
357
- 'dot' : '.' ,
358
- relativePathToWorkspaceRoot,
359
- } ) ,
360
- move ( sourceRoot ) ,
361
- ] ) ) ,
362
- mergeWith (
363
- apply ( url ( './files/root' ) , [
409
+ apply ( url ( './files' ) , [
364
410
options . minimal ? filter ( minimalPathFilter ) : noop ( ) ,
365
411
applyTemplates ( {
366
412
utils : strings ,
367
413
...options ,
368
- 'dot' : '.' ,
369
414
relativePathToWorkspaceRoot,
370
- rootInSrc,
371
415
appName : options . name ,
416
+ isRootApp,
372
417
} ) ,
418
+ isRootApp ? mergeWithRootTsLint ( host ) : noop ( ) ,
373
419
move ( appDir ) ,
374
- ] ) ) ,
375
- options . minimal ? noop ( ) : mergeWith (
376
- apply ( url ( './files/lint' ) , [
377
- applyTemplates ( {
378
- utils : strings ,
379
- ...options ,
380
- tsLintRoot,
381
- relativePathToWorkspaceRoot,
382
- prefix,
383
- } ) ,
384
- // TODO: Moving should work but is bugged right now.
385
- // The __tsLintRoot__ is being used meanwhile.
386
- // Otherwise the tslint.json file could be inside of the root folder and
387
- // this block and the lint folder could be removed.
388
- ] ) ) ,
420
+ ] ) , MergeStrategy . Overwrite ) ,
389
421
schematic ( 'module' , {
390
422
name : 'app' ,
391
423
commonModule : false ,
@@ -410,11 +442,11 @@ export default function (options: ApplicationOptions): Rule {
410
442
? filter ( path => ! path . endsWith ( '.html.template' ) )
411
443
: noop ( ) ,
412
444
componentOptions . skipTests
413
- ? filter ( path => ! / [ . | - ] s p e c .t s .t e m p l a t e $ / . test ( path ) )
445
+ ? filter ( path => ! path . endsWith ( '. spec.ts.template' ) )
414
446
: noop ( ) ,
415
447
applyTemplates ( {
416
448
utils : strings ,
417
- ...options as any , // tslint:disable-line:no-any
449
+ ...options ,
418
450
selector : appRootSelector ,
419
451
...componentOptions ,
420
452
} ) ,
@@ -423,7 +455,7 @@ export default function (options: ApplicationOptions): Rule {
423
455
options . minimal ? noop ( ) : schematic ( 'e2e' , e2eOptions ) ,
424
456
options . enableIvy ? addPostInstallScript ( ) : noop ( ) ,
425
457
options . skipPackageJson ? noop ( ) : addDependenciesToPackageJson ( options ) ,
426
- options . lintFix ? applyLintFix ( sourceDir ) : noop ( ) ,
458
+ options . lintFix ? applyLintFix ( appDir ) : noop ( ) ,
427
459
] ) ;
428
460
} ;
429
461
}
0 commit comments