From 4a2bcd9a73e4d70f2fcc88bdf6294c181bed9f23 Mon Sep 17 00:00:00 2001 From: Mike Brocchi Date: Wed, 30 May 2018 18:35:20 -0400 Subject: [PATCH] fix(@schematics/angular): Fix usage of sourceRoot configuration value fixes angular/angular-cli#10653 --- packages/schematics/angular/class/index.ts | 4 +- .../schematics/angular/class/index_spec.ts | 8 ++++ .../schematics/angular/component/index.ts | 4 +- .../angular/component/index_spec.ts | 15 +++++++ .../schematics/angular/directive/index.ts | 4 +- .../angular/directive/index_spec.ts | 15 +++++++ packages/schematics/angular/enum/index.ts | 4 +- .../schematics/angular/enum/index_spec.ts | 8 +++- packages/schematics/angular/guard/index.ts | 4 +- .../schematics/angular/guard/index_spec.ts | 9 +++++ .../schematics/angular/interface/index.ts | 4 +- .../angular/interface/index_spec.ts | 8 ++++ packages/schematics/angular/module/index.ts | 4 +- .../schematics/angular/module/index_spec.ts | 9 +++++ packages/schematics/angular/pipe/index.ts | 4 +- .../schematics/angular/pipe/index_spec.ts | 15 +++++++ packages/schematics/angular/service/index.ts | 4 +- .../schematics/angular/service/index_spec.ts | 9 +++++ .../schematics/angular/utility/project.ts | 23 +++++++++++ .../angular/utility/project_spec.ts | 40 +++++++++++++++++++ 20 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 packages/schematics/angular/utility/project.ts create mode 100644 packages/schematics/angular/utility/project_spec.ts diff --git a/packages/schematics/angular/class/index.ts b/packages/schematics/angular/class/index.ts index 6b3f5b55e6..61cde918ce 100644 --- a/packages/schematics/angular/class/index.ts +++ b/packages/schematics/angular/class/index.ts @@ -22,6 +22,7 @@ import { } from '@angular-devkit/schematics'; import { getWorkspace } from '../utility/config'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as ClassOptions } from './schema'; export default function (options: ClassOptions): Rule { @@ -33,8 +34,7 @@ export default function (options: ClassOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } options.type = !!options.type ? `.${options.type}` : ''; diff --git a/packages/schematics/angular/class/index_spec.ts b/packages/schematics/angular/class/index_spec.ts index bd484afaf9..4bfaf6e087 100644 --- a/packages/schematics/angular/class/index_spec.ts +++ b/packages/schematics/angular/class/index_spec.ts @@ -88,4 +88,12 @@ describe('Class Schematic', () => { const tree = schematicRunner.runSchematic('class', options, appTree); expect(tree.files.indexOf('/zzz/foo.ts')).toBeGreaterThanOrEqual(0); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = schematicRunner.runSchematic('class', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo.ts')).toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/component/index.ts b/packages/schematics/angular/component/index.ts index efa53995ab..bf95f873d3 100644 --- a/packages/schematics/angular/component/index.ts +++ b/packages/schematics/angular/component/index.ts @@ -31,6 +31,7 @@ import { InsertChange } from '../utility/change'; import { getWorkspace } from '../utility/config'; import { buildRelativePath, findModuleFromOptions } from '../utility/find-module'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { validateHtmlSelector, validateName } from '../utility/validation'; import { Schema as ComponentOptions } from './schema'; @@ -134,8 +135,7 @@ export default function(options: ComponentOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } options.module = findModuleFromOptions(host, options); diff --git a/packages/schematics/angular/component/index_spec.ts b/packages/schematics/angular/component/index_spec.ts index 90fd99db8b..a89fc95419 100644 --- a/packages/schematics/angular/component/index_spec.ts +++ b/packages/schematics/angular/component/index_spec.ts @@ -287,4 +287,19 @@ describe('Component Schematic', () => { const content = appTree.readContent('/projects/bar/src/app/sub/test/test.component.ts'); expect(content).toMatch(/selector: 'app-test'/); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + + // should fail without a module in that dir + expect(() => schematicRunner.runSchematic('component', defaultOptions, appTree)).toThrow(); + + // move the module + appTree.rename('/projects/bar/src/app/app.module.ts', '/projects/bar/custom/app/app.module.ts'); + appTree = schematicRunner.runSchematic('component', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo/foo.component.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/directive/index.ts b/packages/schematics/angular/directive/index.ts index 3591c1e4bf..161839b940 100644 --- a/packages/schematics/angular/directive/index.ts +++ b/packages/schematics/angular/directive/index.ts @@ -27,6 +27,7 @@ import { InsertChange } from '../utility/change'; import { getWorkspace } from '../utility/config'; import { buildRelativePath, findModuleFromOptions } from '../utility/find-module'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { validateHtmlSelector } from '../utility/validation'; import { Schema as DirectiveOptions } from './schema'; @@ -110,8 +111,7 @@ export default function (options: DirectiveOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } options.module = findModuleFromOptions(host, options); diff --git a/packages/schematics/angular/directive/index_spec.ts b/packages/schematics/angular/directive/index_spec.ts index e5f91c3e2b..32ace5f5fc 100644 --- a/packages/schematics/angular/directive/index_spec.ts +++ b/packages/schematics/angular/directive/index_spec.ts @@ -154,4 +154,19 @@ describe('Directive Schematic', () => { const content = tree.readContent('/projects/bar/src/app/foo.directive.ts'); expect(content).toMatch(/selector: '\[foo\]'/); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + + // should fail without a module in that dir + expect(() => schematicRunner.runSchematic('directive', defaultOptions, appTree)).toThrow(); + + // move the module + appTree.rename('/projects/bar/src/app/app.module.ts', '/projects/bar/custom/app/app.module.ts'); + appTree = schematicRunner.runSchematic('directive', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo.directive.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/enum/index.ts b/packages/schematics/angular/enum/index.ts index a101740027..ed119762cb 100644 --- a/packages/schematics/angular/enum/index.ts +++ b/packages/schematics/angular/enum/index.ts @@ -21,6 +21,7 @@ import { } from '@angular-devkit/schematics'; import { getWorkspace } from '../utility/config'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as EnumOptions } from './schema'; @@ -33,8 +34,7 @@ export default function (options: EnumOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } const parsedPath = parseName(options.path, options.name); diff --git a/packages/schematics/angular/enum/index_spec.ts b/packages/schematics/angular/enum/index_spec.ts index 0db0e1e46f..48d524bf62 100644 --- a/packages/schematics/angular/enum/index_spec.ts +++ b/packages/schematics/angular/enum/index_spec.ts @@ -54,5 +54,11 @@ describe('Enum Schematic', () => { expect(content).toMatch('export enum Foo {'); }); - + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = schematicRunner.runSchematic('enum', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo.enum.ts')).toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/guard/index.ts b/packages/schematics/angular/guard/index.ts index d29d277533..682395e325 100644 --- a/packages/schematics/angular/guard/index.ts +++ b/packages/schematics/angular/guard/index.ts @@ -23,6 +23,7 @@ import { } from '@angular-devkit/schematics'; import { getWorkspace } from '../utility/config'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as GuardOptions } from './schema'; @@ -35,8 +36,7 @@ export default function (options: GuardOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } const parsedPath = parseName(options.path, options.name); diff --git a/packages/schematics/angular/guard/index_spec.ts b/packages/schematics/angular/guard/index_spec.ts index faadfaa35a..82fcb8e0e0 100644 --- a/packages/schematics/angular/guard/index_spec.ts +++ b/packages/schematics/angular/guard/index_spec.ts @@ -59,4 +59,13 @@ describe('Guard Schematic', () => { expect(files.indexOf('/projects/bar/src/app/foo.guard.spec.ts')).toEqual(-1); expect(files.indexOf('/projects/bar/src/app/foo.guard.ts')).toBeGreaterThanOrEqual(0); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = schematicRunner.runSchematic('guard', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo.guard.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/interface/index.ts b/packages/schematics/angular/interface/index.ts index d8c15f5471..3e7abfdd06 100644 --- a/packages/schematics/angular/interface/index.ts +++ b/packages/schematics/angular/interface/index.ts @@ -21,6 +21,7 @@ import { } from '@angular-devkit/schematics'; import { getWorkspace } from '../utility/config'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as InterfaceOptions } from './schema'; @@ -33,8 +34,7 @@ export default function (options: InterfaceOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } const parsedPath = parseName(options.path, options.name); diff --git a/packages/schematics/angular/interface/index_spec.ts b/packages/schematics/angular/interface/index_spec.ts index 68459e210a..cc492329ea 100644 --- a/packages/schematics/angular/interface/index_spec.ts +++ b/packages/schematics/angular/interface/index_spec.ts @@ -63,4 +63,12 @@ describe('Interface Schematic', () => { expect(tree.files.indexOf('/projects/bar/src/app/foo.model.ts')).toBeGreaterThanOrEqual(0); }); + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = schematicRunner.runSchematic('interface', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/module/index.ts b/packages/schematics/angular/module/index.ts index 65471cd351..01d31f8e8d 100644 --- a/packages/schematics/angular/module/index.ts +++ b/packages/schematics/angular/module/index.ts @@ -27,6 +27,7 @@ import { InsertChange } from '../utility/change'; import { getWorkspace } from '../utility/config'; import { findModuleFromOptions } from '../utility/find-module'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as ModuleOptions } from './schema'; @@ -79,8 +80,7 @@ export default function (options: ModuleOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } if (options.module) { options.module = findModuleFromOptions(host, options); diff --git a/packages/schematics/angular/module/index_spec.ts b/packages/schematics/angular/module/index_spec.ts index b941c6aa8b..00f8d69899 100644 --- a/packages/schematics/angular/module/index_spec.ts +++ b/packages/schematics/angular/module/index_spec.ts @@ -115,4 +115,13 @@ describe('Module Schematic', () => { expect(files.indexOf('/projects/bar/src/app/two-word/two-word.module.spec.ts')) .toBeGreaterThanOrEqual(0); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = schematicRunner.runSchematic('module', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo/foo.module.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/pipe/index.ts b/packages/schematics/angular/pipe/index.ts index 6c24134830..db9d177d01 100644 --- a/packages/schematics/angular/pipe/index.ts +++ b/packages/schematics/angular/pipe/index.ts @@ -27,6 +27,7 @@ import { InsertChange } from '../utility/change'; import { getWorkspace } from '../utility/config'; import { buildRelativePath, findModuleFromOptions } from '../utility/find-module'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as PipeOptions } from './schema'; @@ -94,8 +95,7 @@ export default function (options: PipeOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } const parsedPath = parseName(options.path, options.name); diff --git a/packages/schematics/angular/pipe/index_spec.ts b/packages/schematics/angular/pipe/index_spec.ts index 482a8538a1..1a1fe1710d 100644 --- a/packages/schematics/angular/pipe/index_spec.ts +++ b/packages/schematics/angular/pipe/index_spec.ts @@ -109,4 +109,19 @@ describe('Pipe Schematic', () => { const content = getFileContent(tree, routingModulePath); expect(content).toMatch(/import { FooPipe } from '.\/foo.pipe/); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + + // should fail without a module in that dir + expect(() => schematicRunner.runSchematic('pipe', defaultOptions, appTree)).toThrow(); + + // move the module + appTree.rename('/projects/bar/src/app/app.module.ts', '/projects/bar/custom/app/app.module.ts'); + appTree = schematicRunner.runSchematic('pipe', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo.pipe.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/service/index.ts b/packages/schematics/angular/service/index.ts index 7d134e558b..5748d9c601 100644 --- a/packages/schematics/angular/service/index.ts +++ b/packages/schematics/angular/service/index.ts @@ -21,6 +21,7 @@ import { } from '@angular-devkit/schematics'; import { getWorkspace } from '../utility/config'; import { parseName } from '../utility/parse-name'; +import { buildDefaultPath } from '../utility/project'; import { Schema as ServiceOptions } from './schema'; export default function (options: ServiceOptions): Rule { @@ -32,8 +33,7 @@ export default function (options: ServiceOptions): Rule { const project = workspace.projects[options.project]; if (options.path === undefined) { - const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; - options.path = `/${project.root}/src/${projectDirName}`; + options.path = buildDefaultPath(project); } const parsedPath = parseName(options.path, options.name); diff --git a/packages/schematics/angular/service/index_spec.ts b/packages/schematics/angular/service/index_spec.ts index f651c2fb41..2493d660ae 100644 --- a/packages/schematics/angular/service/index_spec.ts +++ b/packages/schematics/angular/service/index_spec.ts @@ -70,4 +70,13 @@ describe('Service Schematic', () => { expect(files.indexOf('/projects/bar/src/app/foo/foo.service.ts')).toBeGreaterThanOrEqual(0); expect(files.indexOf('/projects/bar/src/app/foo/foo.service.spec.ts')).toEqual(-1); }); + + it('should respect the sourceRoot value', () => { + const config = JSON.parse(appTree.readContent('/angular.json')); + config.projects.bar.sourceRoot = 'projects/bar/custom'; + appTree.overwrite('/angular.json', JSON.stringify(config, null, 2)); + appTree = schematicRunner.runSchematic('service', defaultOptions, appTree); + expect(appTree.files.indexOf('/projects/bar/custom/app/foo/foo.service.ts')) + .toBeGreaterThanOrEqual(0); + }); }); diff --git a/packages/schematics/angular/utility/project.ts b/packages/schematics/angular/utility/project.ts new file mode 100644 index 0000000000..126273c535 --- /dev/null +++ b/packages/schematics/angular/utility/project.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { WorkspaceProject } from '../utility/config'; + + +/** + * Build a default project path for generating. + * @param project The project to build the path for. + */ +export function buildDefaultPath(project: WorkspaceProject): string { + const root = project.sourceRoot + ? `/${project.sourceRoot}/` + : `/${project.root}/src/`; + + const projectDirName = project.projectType === 'application' ? 'app' : 'lib'; + + return `${root}${projectDirName}`; +} diff --git a/packages/schematics/angular/utility/project_spec.ts b/packages/schematics/angular/utility/project_spec.ts new file mode 100644 index 0000000000..c7b9a73b02 --- /dev/null +++ b/packages/schematics/angular/utility/project_spec.ts @@ -0,0 +1,40 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { WorkspaceProject } from '../utility/config'; +import { buildDefaultPath } from './project'; + + +describe('project', () => { + describe('buildDefaultPath', () => { + let project: WorkspaceProject; + beforeEach(() => { + project = { + projectType: 'application', + root: 'foo', + prefix: 'app', + }; + }); + + it('should handle projectType of application', () => { + const result = buildDefaultPath(project); + expect(result).toEqual('/foo/src/app'); + }); + + it('should handle projectType of library', () => { + project = { ...project, projectType: 'library' }; + const result = buildDefaultPath(project); + expect(result).toEqual('/foo/src/lib'); + }); + + it('should handle sourceRoot', () => { + project = { ...project, sourceRoot: 'foo/bar/custom' }; + const result = buildDefaultPath(project); + expect(result).toEqual('/foo/bar/custom/app'); + }); + }); +});