diff --git a/packages/@angular/cli/commands/generate.ts b/packages/@angular/cli/commands/generate.ts index 211589a314fc..ea728c16d3d3 100644 --- a/packages/@angular/cli/commands/generate.ts +++ b/packages/@angular/cli/commands/generate.ts @@ -137,15 +137,18 @@ export default Command.extend({ dryRun: commandOptions.dryRun }; const parsedPath = dynamicPathParser(dynamicPathOptions); - const root = appConfig.root + path.sep; commandOptions.sourceDir = appConfig.root; - commandOptions.appRoot = parsedPath.appRoot.startsWith(root) - ? parsedPath.appRoot.substr(root.length) - : parsedPath.appRoot; + const root = appConfig.root + path.sep; + commandOptions.appRoot = parsedPath.appRoot === appConfig.root ? '' : + parsedPath.appRoot.startsWith(root) + ? parsedPath.appRoot.substr(root.length) + : parsedPath.appRoot; + commandOptions.path = parsedPath.dir.replace(separatorRegEx, '/'); - if (parsedPath.dir.startsWith(root)) { - commandOptions.path = commandOptions.path.substr(root.length); - } + commandOptions.path = parsedPath.dir === appConfig.root ? '' : + parsedPath.dir.startsWith(root) + ? commandOptions.path.substr(root.length) + : commandOptions.path; const cwd = this.project.root; const schematicName = rawArgs[0]; diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index af5c8d521ef0..3ba988cb8f08 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -33,6 +33,11 @@ "type": "string", "description": "Name of the app." }, + "appRoot": { + "type": "string", + "description": "Directory where app files are placed.", + "default": "app" + }, "root": { "type": "string", "description": "The root directory of the app." diff --git a/packages/@angular/cli/utilities/dynamic-path-parser.ts b/packages/@angular/cli/utilities/dynamic-path-parser.ts index fd0b259bc4ea..c41f5e06ff78 100644 --- a/packages/@angular/cli/utilities/dynamic-path-parser.ts +++ b/packages/@angular/cli/utilities/dynamic-path-parser.ts @@ -13,7 +13,9 @@ export interface DynamicPathOptions { export function dynamicPathParser(options: DynamicPathOptions) { const projectRoot = options.project.root; const sourceDir = options.appConfig.root; - const appRoot = path.join(sourceDir, 'app'); + + const p = options.appConfig.appRoot === undefined ? 'app' : options.appConfig.appRoot; + const appRoot = path.join(sourceDir, p); const cwd = process.env.PWD; const rootPath = path.join(projectRoot, appRoot); diff --git a/tests/acceptance/dynamic-path-parser.spec.ts b/tests/acceptance/dynamic-path-parser.spec.ts index e694751b509a..48556791ea1c 100644 --- a/tests/acceptance/dynamic-path-parser.spec.ts +++ b/tests/acceptance/dynamic-path-parser.spec.ts @@ -50,6 +50,32 @@ describe('dynamic path parser', () => { expect(result.name).toBe(entityName); }); + it('respects the appRoot configuration', () => { + process.env.PWD = project.root; + const options = { + project, + entityName, + appConfig: {...appConfig, appRoot: 'other'}, + dryRun: false + }; + const result = dynamicPathParser(options); + expect(result.dir).toBe(`src${path.sep}other`); + expect(result.name).toBe(entityName); + }); + + it('respects the empty appRoot configuration', () => { + process.env.PWD = project.root; + const options = { + project, + entityName, + appConfig: {...appConfig, appRoot: ''}, + dryRun: false + }; + const result = dynamicPathParser(options); + expect(result.dir).toBe(`src`); + expect(result.name).toBe(entityName); + }); + it('parse from proj src dir', () => { process.env.PWD = path.join(project.root, 'src'); const options = { diff --git a/tests/acceptance/generate-component.spec.ts b/tests/acceptance/generate-component.spec.ts index a98b03ee0a62..3d37213c78d6 100644 --- a/tests/acceptance/generate-component.spec.ts +++ b/tests/acceptance/generate-component.spec.ts @@ -345,5 +345,22 @@ describe('Acceptance: ng generate component', () => { }) .then(done, done.fail); }); + + describe('should generate components in apps with empty appRoot', () => { + it('should work', (done) => { + const appRoot = path.join(root, 'tmp/foo'); + mkdirsSync(path.join(appRoot, 'other', 'src')); + + return ng(['generate', 'module', 'm', '--app', 'other']).then(() => { + const expectedModule = path.join(appRoot, 'other', 'src', 'm', 'm.module.ts'); + expect(pathExistsSync(expectedModule)).toBe(true); + + return ng(['generate', 'component', 'm/c', '--app', 'other', '--module', 'm']).then(() => { + expect(pathExistsSync(path.join(appRoot, 'other', 'src', 'm', 'c', 'c.component.ts'))).toBe(true); + expect(readFileSync(expectedModule, 'utf-8')).toContain(`import { CComponent } from './c/c.component'`); + }); + }).then(done, done.fail); + }); + }); }); }); diff --git a/tests/helpers/index.ts b/tests/helpers/index.ts index acb42494d57d..bf2c9e0be9bf 100644 --- a/tests/helpers/index.ts +++ b/tests/helpers/index.ts @@ -1,3 +1,5 @@ +import * as path from 'path'; +import {writeFile, readFile} from 'fs-extra'; import { ng } from './ng'; import { setup, teardown } from './tmp'; @@ -10,6 +12,7 @@ export function setupProject() { setup('./tmp') .then(() => process.chdir('./tmp')) .then(() => ng(['new', 'foo', '--skip-install'])) + .then(() => addAppToProject()) .then(done, done.fail); }, 10000); @@ -17,3 +20,14 @@ export function setupProject() { teardown('./tmp').then(done, done.fail); }); } + +function addAppToProject(): Promise { + const cliJson = path.join(path.join(process.cwd()), '.angular-cli.json'); + return readFile(cliJson, 'utf-8').then(content => { + const json = JSON.parse(content); + json.apps.push(({name: 'other', root: 'other/src', appRoot: ''})); + return json; + }).then(json => { + return writeFile(cliJson, JSON.stringify(json, null, 2)) + }); +}