Skip to content

Commit

Permalink
feat(schematics): add support for generating apps and libs in nested …
Browse files Browse the repository at this point in the history
…dirs
  • Loading branch information
vsavkin committed Dec 5, 2017
1 parent fa5d5bc commit 013a828
Show file tree
Hide file tree
Showing 36 changed files with 327 additions and 151 deletions.
18 changes: 9 additions & 9 deletions e2e/schematics/application.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ describe('Nrwl Workspace', () => {
'should work',
() => {
newProject();
newApp('myapp');
newLib('mylib --ngmodule');
newApp('myApp --directory=myDir');
newLib('myLib --directory=myDir --ngmodule');

updateFile(
'apps/myapp/src/app/app.module.ts',
'apps/my-dir/my-app/src/app/app.module.ts',
`
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { MylibModule } from '@nrwl/mylib';
import { MyLibModule } from '@nrwl/my-dir/my-lib';
import { AppComponent } from './app.component';
@NgModule({
imports: [BrowserModule, MylibModule],
imports: [BrowserModule, MyLibModule],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
Expand All @@ -35,8 +35,8 @@ describe('Nrwl Workspace', () => {
'should support router config generation (lazy)',
() => {
newProject();
newApp('myapp --routing');
newLib('mylib --routing --lazy --parentModule=apps/myapp/src/app/app.module.ts');
newApp('myApp --directory=myDir --routing');
newLib('myLib --directory=myDir --routing --lazy --parentModule=apps/my-dir/my-app/src/app/app.module.ts');

runCLI('build --aot');
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
Expand All @@ -48,8 +48,8 @@ describe('Nrwl Workspace', () => {
'should support router config generation (eager)',
() => {
newProject();
newApp('myapp --routing');
newLib('mylib --routing --parentModule=apps/myapp/src/app/app.module.ts');
newApp('myApp --directory=myDir --routing');
newLib('myLib --directory=myDir --routing --parentModule=apps/my-dir/my-app/src/app/app.module.ts');

runCLI('build --aot');
expect(runCLI('test --single-run')).toContain('Executed 2 of 2 SUCCESS');
Expand Down
2 changes: 2 additions & 0 deletions e2e/schematics/tslint.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ describe('Lint', () => {
`
import '../../../libs/mylib';
import '@nrwl/lazylib';
import '@nrwl/mylib/deep';
`
);

const out = runCLI('lint --type-check', { silenceError: true });
expect(out).toContain('relative imports of libraries are forbidden');
expect(out).toContain('import of lazy-loaded libraries are forbidden');
expect(out).toContain('deep imports into libraries are forbidden');
},
1000000
);
Expand Down
4 changes: 2 additions & 2 deletions packages/schematics/migrations/20171202-change-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default {
const rule = ruleName in json.rules ? json.rules[ruleName] : null;

// Only modify when the rule is configured with optional arguments
if (Array.isArray(rule) && typeof rule[2] === 'object' && rule[2] !== null) {
rule[2][ruleOptionName] = [];
if (Array.isArray(rule) && typeof rule[1] === 'object' && rule[1] !== null) {
rule[1][ruleOptionName] = [];
}
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { updateJsonFile } from '../src/collection/utility/fileutils';

export default {
description: 'Remove npmScope from tslint.json',
run: () => {
updateJsonFile('tslint.json', json => {
const ruleName = 'nx-enforce-module-boundaries';
const rule = ruleName in json.rules ? json.rules[ruleName] : null;

// Only modify when the rule is configured with optional arguments
if (Array.isArray(rule) && typeof rule[1] === 'object' && rule[1] !== null) {
rule[1].npmScope = undefined;
}
});
}
};
105 changes: 71 additions & 34 deletions packages/schematics/src/collection/app/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,49 +14,86 @@ describe('app', () => {
appTree = createEmptyWorkspace(appTree);
});

it('should update angular-cli.json', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree);
const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json'));
expect(updatedAngularCLIJson.apps).toEqual([
{
assets: ['assets', 'favicon.ico'],
environmentSource: 'environments/environment.ts',
environments: { dev: 'environments/environment.ts', prod: 'environments/environment.prod.ts' },
index: 'index.html',
main: 'main.ts',
name: 'my-app',
outDir: 'dist/apps/my-app',
polyfills: 'polyfills.ts',
prefix: 'app',
root: 'apps/my-app/src',
scripts: [],
styles: ['styles.css'],
test: '../../../test.js',
testTsconfig: '../../../tsconfig.spec.json',
tsconfig: '../../../tsconfig.app.json'
}
]);
describe('not nested', () => {
it('should update angular-cli.json', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree);
const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json'));
expect(updatedAngularCLIJson.apps).toEqual([
{
assets: ['assets', 'favicon.ico'],
environmentSource: 'environments/environment.ts',
environments: { dev: 'environments/environment.ts', prod: 'environments/environment.prod.ts' },
index: 'index.html',
main: 'main.ts',
name: 'my-app',
outDir: 'dist/apps/my-app',
polyfills: 'polyfills.ts',
prefix: 'app',
root: 'apps/my-app/src',
scripts: [],
styles: ['styles.css'],
test: '../../../test.js',
testTsconfig: '../../../tsconfig.spec.json',
tsconfig: '../../../tsconfig.app.json'
}
]);
});

it('should generate files', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree);
expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.module.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.component.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/e2e/app.po.ts')).toBeTruthy();
expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('class AppModule');
});
});

it('should generate files', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree);
expect(tree.exists('apps/my-app/src/main.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.module.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/src/app/app.component.ts')).toBeTruthy();
expect(tree.exists('apps/my-app/e2e/app.po.ts')).toBeTruthy();
expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('class AppModule');
describe('nested', () => {
it('should update angular-cli.json', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir' }, appTree);
const updatedAngularCLIJson = JSON.parse(getFileContent(tree, '/.angular-cli.json'));
expect(updatedAngularCLIJson.apps).toEqual([
{
assets: ['assets', 'favicon.ico'],
environmentSource: 'environments/environment.ts',
environments: { dev: 'environments/environment.ts', prod: 'environments/environment.prod.ts' },
index: 'index.html',
main: 'main.ts',
name: 'my-dir/my-app',
outDir: 'dist/apps/my-dir/my-app',
polyfills: 'polyfills.ts',
prefix: 'app',
root: 'apps/my-dir/my-app/src',
scripts: [],
styles: ['styles.css'],
test: '../../../../test.js',
testTsconfig: '../../../../tsconfig.spec.json',
tsconfig: '../../../../tsconfig.app.json'
}
]);
});

it('should generate files', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir' }, appTree);
expect(tree.exists('apps/my-dir/my-app/src/main.ts')).toBeTruthy();
expect(tree.exists('apps/my-dir/my-app/src/app/app.module.ts')).toBeTruthy();
expect(tree.exists('apps/my-dir/my-app/src/app/app.component.ts')).toBeTruthy();
expect(tree.exists('apps/my-dir/my-app/e2e/app.po.ts')).toBeTruthy();
expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')).toContain('class AppModule');
});
});

it('should import NgModule', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp' }, appTree);
expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('NxModule.forRoot()');
const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir' }, appTree);
expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')).toContain('NxModule.forRoot()');
});

describe('routing', () => {
it('should include RouterTestingModule', () => {
const tree = schematicRunner.runSchematic('app', { name: 'myApp', routing: true }, appTree);
expect(getFileContent(tree, 'apps/my-app/src/app/app.module.ts')).toContain('RouterModule.forRoot');
expect(getFileContent(tree, 'apps/my-app/src/app/app.component.spec.ts')).toContain(
const tree = schematicRunner.runSchematic('app', { name: 'myApp', directory: 'myDir', routing: true }, appTree);
expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.module.ts')).toContain('RouterModule.forRoot');
expect(getFileContent(tree, 'apps/my-dir/my-app/src/app/app.component.spec.ts')).toContain(
'imports: [RouterTestingModule]'
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
<body>
<<%= prefix %>-root></<%= prefix %>-root>
</body>
</html>
</html>
42 changes: 26 additions & 16 deletions packages/schematics/src/collection/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ import { addBootstrapToModule } from '@schematics/angular/utility/ast-utils';
import { insertImport } from '@schematics/angular/utility/route-utils';
import { addApp, serializeJson } from '../utility/fileutils';
import { addImportToTestBed } from '../utility/ast-utils';
import { offsetFromRoot } from '../utility/common';

interface NormalizedSchema extends Schema {
fullName: string;
fullPath: string;
}

function addBootstrap(path: string): Rule {
return (host: Tree) => {
Expand Down Expand Up @@ -48,7 +54,7 @@ function addNxModule(path: string): Rule {
return host;
};
}
function addAppToAngularCliJson(options: Schema): Rule {
function addAppToAngularCliJson(options: NormalizedSchema): Rule {
return (host: Tree) => {
if (!host.exists('.angular-cli.json')) {
throw new Error('Missing .angular-cli.json');
Expand All @@ -57,16 +63,16 @@ function addAppToAngularCliJson(options: Schema): Rule {
const sourceText = host.read('.angular-cli.json')!.toString('utf-8');
const json = JSON.parse(sourceText);
json.apps = addApp(json.apps, {
name: options.name,
root: fullPath(options),
outDir: `dist/apps/${options.name}`,
name: options.fullName,
root: options.fullPath,
outDir: `dist/apps/${options.fullName}`,
assets: ['assets', 'favicon.ico'],
index: 'index.html',
main: 'main.ts',
polyfills: 'polyfills.ts',
test: '../../../test.js',
tsconfig: '../../../tsconfig.app.json',
testTsconfig: '../../../tsconfig.spec.json',
test: `${offsetFromRoot(options)}test.js`,
tsconfig: `${offsetFromRoot(options)}tsconfig.app.json`,
testTsconfig: `${offsetFromRoot(options)}tsconfig.spec.json`,
prefix: options.prefix,
styles: [`styles.${options.style}`],
scripts: [],
Expand Down Expand Up @@ -109,7 +115,8 @@ function addRouterRootConfiguration(path: string): Rule {
}

export default function(schema: Schema): Rule {
const options = { ...schema, name: toFileName(schema.name) };
const options = normalizeOptions(schema);

const templateSource = apply(url('./files'), [
template({ utils: stringUtils, dot: '.', tmpl: '', ...(options as object) })
]);
Expand All @@ -122,13 +129,13 @@ export default function(schema: Schema): Rule {
commonModule: false,
flat: true,
routing: false,
sourceDir: fullPath(options),
sourceDir: options.fullPath,
spec: false
}),
externalSchematic('@schematics/angular', 'component', {
name: 'app',
selector: selector,
sourceDir: fullPath(options),
sourceDir: options.fullPath,
flat: true,
inlineStyle: options.inlineStyle,
inlineTemplate: options.inlineTemplate,
Expand All @@ -142,17 +149,20 @@ export default function(schema: Schema): Rule {
apply(url('./component-files'), [
options.inlineTemplate ? filter(path => !path.endsWith('.html')) : noop(),
template({ ...options, tmpl: '' }),
move(`${fullPath(options)}/app`)
move(`${options.fullPath}/app`)
]),
MergeStrategy.Overwrite
),
addBootstrap(fullPath(options)),
addNxModule(fullPath(options)),
addBootstrap(options.fullPath),
addNxModule(options.fullPath),
addAppToAngularCliJson(options),
options.routing ? addRouterRootConfiguration(fullPath(options)) : noop()
options.routing ? addRouterRootConfiguration(options.fullPath) : noop()
]);
}

function fullPath(options: Schema) {
return `apps/${options.name}/${options.sourceDir}`;
function normalizeOptions(options: Schema): NormalizedSchema {
const name = toFileName(options.name);
const fullName = options.directory ? `${toFileName(options.directory)}/${name}` : name;
const fullPath = `apps/${fullName}/${options.sourceDir}`;
return { ...options, name, fullName, fullPath };
}
1 change: 1 addition & 0 deletions packages/schematics/src/collection/app/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface Schema {
name: string;
directory?: string;
sourceDir?: string;
inlineStyle?: boolean;
inlineTemplate?: boolean;
Expand Down
4 changes: 4 additions & 0 deletions packages/schematics/src/collection/app/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"type": "string",
"description": "Application name"
},
"directory": {
"type": "string",
"description": "A directory where the app is placed"
},
"sourceDir": {
"type": "string",
"default": "src",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,5 @@ describe('application', () => {

const tsconfigJson = JSON.parse(getFileContent(tree, '/my-app/tsconfig.json'));
expect(tsconfigJson.compilerOptions.paths).toEqual({ '@myApp/*': ['libs/*'] });

const tslintJson = JSON.parse(getFileContent(tree, '/my-app/tslint.json'));
expect(tslintJson.rules['nx-enforce-module-boundaries']).toEqual([
true,
{ allow: [], lazyLoad: [], npmScope: 'myApp' }
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@
"nx-enforce-module-boundaries": [
true,
{
"npmScope": "<%= npmScope %>",
"lazyLoad": [],
"allow": []
}
Expand Down
Loading

0 comments on commit 013a828

Please sign in to comment.