refactor(@schematics/angular): update application to use new workspace rules

This commit is contained in:
Charles Lyding 2019-04-15 23:22:44 -04:00 committed by Alex Eagle
parent 10be2672bb
commit 58f6282edf
2 changed files with 93 additions and 105 deletions

View File

@ -33,48 +33,15 @@ import {
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { Schema as ComponentOptions } from '../component/schema';
import { Schema as E2eOptions } from '../e2e/schema';
import {
addProjectToWorkspace,
getWorkspace,
} from '../utility/config';
import { NodeDependencyType, addPackageJsonDependency } from '../utility/dependencies';
import { findPropertyInAstObject, insertPropertyInAstObjectInOrder } from '../utility/json-utils';
import { latestVersions } from '../utility/latest-versions';
import { applyLintFix } from '../utility/lint-fix';
import { validateProjectName } from '../utility/validation';
import {
Builders,
ProjectType,
WorkspaceProject,
WorkspaceSchema,
} from '../utility/workspace-models';
import { getWorkspace, updateWorkspace } from '../utility/workspace';
import { Builders, ProjectType } from '../utility/workspace-models';
import { Schema as ApplicationOptions, Style } from './schema';
// TODO: use JsonAST
// function appendPropertyInAstObject(
// recorder: UpdateRecorder,
// node: JsonAstObject,
// propertyName: string,
// value: JsonValue,
// indent = 4,
// ) {
// const indentStr = '\n' + new Array(indent + 1).join(' ');
// if (node.properties.length > 0) {
// // Insert comma.
// const last = node.properties[node.properties.length - 1];
// recorder.insertRight(last.start.offset + last.text.replace(/\s+$/, '').length, ',');
// }
// recorder.insertLeft(
// node.end.offset - 1,
// ' '
// + `"${propertyName}": ${JSON.stringify(value, null, 2).replace(/\n/g, indentStr)}`
// + indentStr.slice(0, -2),
// );
// }
function addDependenciesToPackageJson(options: ApplicationOptions) {
return (host: Tree, context: SchematicContext) => {
[
@ -175,21 +142,10 @@ function mergeWithRootTsLint(parentHost: Tree) {
};
}
function addAppToWorkspaceFile(options: ApplicationOptions, workspace: WorkspaceSchema): Rule {
// TODO: use JsonAST
// const workspacePath = '/angular.json';
// const workspaceBuffer = host.read(workspacePath);
// if (workspaceBuffer === null) {
// throw new SchematicsException(`Configuration file (${workspacePath}) not found.`);
// }
// const workspaceJson = parseJson(workspaceBuffer.toString());
// if (workspaceJson.value === null) {
// throw new SchematicsException(`Unable to parse configuration file (${workspacePath}).`);
// }
function addAppToWorkspaceFile(options: ApplicationOptions, newProjectRoot: string): Rule {
let projectRoot = options.projectRoot !== undefined
? options.projectRoot
: `${workspace.newProjectRoot}/${options.name}`;
: `${newProjectRoot}/${options.name}`;
if (projectRoot !== '' && !projectRoot.endsWith('/')) {
projectRoot += '/';
@ -224,13 +180,14 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
}
const sourceRoot = join(normalize(projectRoot), 'src');
const project: WorkspaceProject = {
const project = {
root: projectRoot,
sourceRoot,
projectType: ProjectType.Application,
prefix: options.prefix || 'app',
schematics,
architect: {
targets: {
build: {
builder: Builders.Browser,
options: {
@ -321,7 +278,16 @@ function addAppToWorkspaceFile(options: ApplicationOptions, workspace: Workspace
},
};
return addProjectToWorkspace(workspace, options.name, project);
return updateWorkspace(workspace => {
if (workspace.projects.size === 0) {
workspace.extensions.defaultProject = options.name;
}
workspace.projects.add({
name: options.name,
...project,
});
});
}
function minimalPathFilter(path: string): boolean {
@ -331,7 +297,7 @@ function minimalPathFilter(path: string): boolean {
}
export default function (options: ApplicationOptions): Rule {
return (host: Tree, context: SchematicContext) => {
return async (host: Tree, context: SchematicContext) => {
if (!options.name) {
throw new SchematicsException(`Invalid options, "name" is required.`);
}
@ -353,8 +319,8 @@ export default function (options: ApplicationOptions): Rule {
style: options.style,
};
const workspace = getWorkspace(host);
const newProjectRoot = workspace.newProjectRoot || '';
const workspace = await getWorkspace(host);
const newProjectRoot = workspace.extensions.newProjectRoot as string || '';
const isRootApp = options.projectRoot !== undefined;
const appDir = isRootApp
? options.projectRoot as string
@ -370,7 +336,7 @@ export default function (options: ApplicationOptions): Rule {
};
return chain([
addAppToWorkspaceFile(options, workspace),
addAppToWorkspaceFile(options, newProjectRoot),
mergeWith(
apply(url('./files'), [
options.minimal ? filter(minimalPathFilter) : noop(),

View File

@ -38,10 +38,11 @@ describe('Application Schematic', () => {
workspaceTree = schematicRunner.runSchematic('workspace', workspaceOptions);
});
it('should create all files of an application', () => {
it('should create all files of an application', async () => {
const options = { ...defaultOptions };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const files = tree.files;
expect(files).toEqual(jasmine.arrayContaining([
'/projects/foo/karma.conf.js',
@ -64,35 +65,39 @@ describe('Application Schematic', () => {
]));
});
it('should add the application to the workspace', () => {
it('should add the application to the workspace', async () => {
const options = { ...defaultOptions };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const workspace = JSON.parse(tree.readContent('/angular.json'));
expect(workspace.projects.foo).toBeDefined();
expect(workspace.defaultProject).toBe('foo');
});
it('should set the prefix to app if none is set', () => {
it('should set the prefix to app if none is set', async () => {
const options = { ...defaultOptions };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const workspace = JSON.parse(tree.readContent('/angular.json'));
expect(workspace.projects.foo.prefix).toEqual('app');
});
it('should set the prefix correctly', () => {
it('should set the prefix correctly', async () => {
const options = { ...defaultOptions, prefix: 'pre' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const workspace = JSON.parse(tree.readContent('/angular.json'));
expect(workspace.projects.foo.prefix).toEqual('pre');
});
it('should handle the routing flag', () => {
it('should handle the routing flag', async () => {
const options = { ...defaultOptions, routing: true };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const files = tree.files;
expect(files).toContain('/projects/foo/src/app/app.module.ts');
expect(files).toContain('/projects/foo/src/app/app-routing.module.ts');
@ -102,33 +107,36 @@ describe('Application Schematic', () => {
expect(routingModuleContent).toMatch(/RouterModule.forRoot\(routes\)/);
});
it('should import BrowserModule in the app module', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it('should import BrowserModule in the app module', async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const path = '/projects/foo/src/app/app.module.ts';
const content = tree.readContent(path);
expect(content).toMatch(/import { BrowserModule } from \'@angular\/platform-browser\';/);
});
it('should declare app component in the app module', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it('should declare app component in the app module', async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const path = '/projects/foo/src/app/app.module.ts';
const content = tree.readContent(path);
expect(content).toMatch(/import { AppComponent } from \'\.\/app\.component\';/);
});
it(`should set 'defaultEncapsulation' in main.ts when 'ViewEncapsulation' is provided`, () => {
const tree = schematicRunner.runSchematic('application', {
it(`should set 'defaultEncapsulation' in main.ts when 'ViewEncapsulation' is provided`, async () => {
const tree = await schematicRunner.runSchematicAsync('application', {
...defaultOptions,
viewEncapsulation: ViewEncapsulation.ShadowDom,
}, workspaceTree);
}, workspaceTree).toPromise();
const path = '/projects/foo/src/main.ts';
const content = tree.readContent(path);
expect(content).toContain('defaultEncapsulation: ViewEncapsulation.ShadowDom');
expect(content).toContain(`import { enableProdMode, ViewEncapsulation } from '@angular/core'`);
});
it('should set the right paths in the tsconfig files', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it('should set the right paths in the tsconfig files', async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
let path = '/projects/foo/tsconfig.app.json';
let content = tree.readContent(path);
expect(content).toMatch('../../tsconfig.json');
@ -139,8 +147,9 @@ describe('Application Schematic', () => {
expect(specTsConfig.files).toEqual(['src/test.ts', 'src/polyfills.ts']);
});
it('should set the right path and prefix in the tslint file', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it('should set the right path and prefix in the tslint file', async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const path = '/projects/foo/tslint.json';
const content = JSON.parse(tree.readContent(path));
expect(content.extends).toMatch('../../tslint.json');
@ -148,33 +157,37 @@ describe('Application Schematic', () => {
expect(content.rules['component-selector'][2]).toMatch('app');
});
it('should set the right prefix in the tslint file when provided is kebabed', () => {
it('should set the right prefix in the tslint file when provided is kebabed', async () => {
const options: ApplicationOptions = { ...defaultOptions, prefix: 'foo-bar' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const path = '/projects/foo/tslint.json';
const content = JSON.parse(tree.readContent(path));
expect(content.rules['directive-selector'][2]).toMatch('fooBar');
expect(content.rules['component-selector'][2]).toMatch('foo-bar');
});
it('should set the right coverage folder in the karma.json file', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it('should set the right coverage folder in the karma.json file', async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const karmaConf = getFileContent(tree, '/projects/foo/karma.conf.js');
expect(karmaConf).toContain(`dir: require('path').join(__dirname, '../../coverage/foo')`);
});
it('minimal=true should not create e2e and test targets', () => {
it('minimal=true should not create e2e and test targets', async () => {
const options = { ...defaultOptions, minimal: true };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const config = JSON.parse(tree.readContent('/angular.json'));
const architect = config.projects.foo.architect;
expect(architect.test).not.toBeDefined();
expect(architect.e2e).not.toBeDefined();
});
it('should create correct files when using minimal', () => {
it('should create correct files when using minimal', async () => {
const options = { ...defaultOptions, minimal: true };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const files = tree.files;
[
'/projects/foo/tsconfig.spec.json',
@ -201,38 +214,41 @@ describe('Application Schematic', () => {
});
describe(`update package.json`, () => {
it(`should add build-angular to devDependencies`, () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it(`should add build-angular to devDependencies`, async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const packageJson = JSON.parse(tree.readContent('package.json'));
expect(packageJson.devDependencies['@angular-devkit/build-angular'])
.toEqual(latestVersions.DevkitBuildAngular);
});
it('should use the latest known versions in package.json', () => {
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
it('should use the latest known versions in package.json', async () => {
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const pkg = JSON.parse(tree.readContent('/package.json'));
expect(pkg.devDependencies['@angular/compiler-cli']).toEqual(latestVersions.Angular);
expect(pkg.devDependencies['typescript']).toEqual(latestVersions.TypeScript);
});
it(`should not override existing users dependencies`, () => {
it(`should not override existing users dependencies`, async () => {
const oldPackageJson = workspaceTree.readContent('package.json');
workspaceTree.overwrite('package.json', oldPackageJson.replace(
`"typescript": "${latestVersions.TypeScript}"`,
`"typescript": "~2.5.2"`,
));
const tree = schematicRunner.runSchematic('application', defaultOptions, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', defaultOptions, workspaceTree)
.toPromise();
const packageJson = JSON.parse(tree.readContent('package.json'));
expect(packageJson.devDependencies.typescript).toEqual('~2.5.2');
});
it(`should not modify the file when --skipPackageJson`, () => {
const tree = schematicRunner.runSchematic('application', {
it(`should not modify the file when --skipPackageJson`, async () => {
const tree = await schematicRunner.runSchematicAsync('application', {
name: 'foo',
skipPackageJson: true,
}, workspaceTree);
}, workspaceTree).toPromise();
const packageJson = JSON.parse(tree.readContent('package.json'));
expect(packageJson.devDependencies['@angular-devkit/build-angular']).toBeUndefined();
@ -240,10 +256,11 @@ describe('Application Schematic', () => {
});
describe('custom projectRoot', () => {
it('should put app files in the right spot', () => {
it('should put app files in the right spot', async () => {
const options = { ...defaultOptions, projectRoot: '' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const files = tree.files;
expect(files).toEqual(jasmine.arrayContaining([
'/karma.conf.js',
@ -266,10 +283,11 @@ describe('Application Schematic', () => {
]));
});
it('should set values in angular.json correctly', () => {
it('should set values in angular.json correctly', async () => {
const options = { ...defaultOptions, projectRoot: '' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const config = JSON.parse(tree.readContent('/angular.json'));
const prj = config.projects.foo;
expect(prj.root).toEqual('');
@ -288,9 +306,10 @@ describe('Application Schematic', () => {
]);
});
it('should set values in angular.json correctly when using a style preprocessor', () => {
it('should set values in angular.json correctly when using a style preprocessor', async () => {
const options = { ...defaultOptions, projectRoot: '', style: Style.Sass };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const config = JSON.parse(tree.readContent('/angular.json'));
const prj = config.projects.foo;
const buildOpt = prj.architect.build.options;
@ -304,9 +323,10 @@ describe('Application Schematic', () => {
expect(tree.exists('src/styles.sass')).toBe(true);
});
it('should set the relative tsconfig paths', () => {
it('should set the relative tsconfig paths', async () => {
const options = { ...defaultOptions, projectRoot: '' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const appTsConfig = JSON.parse(tree.readContent('/tsconfig.app.json'));
expect(appTsConfig.extends).toEqual('./tsconfig.json');
const specTsConfig = JSON.parse(tree.readContent('/tsconfig.spec.json'));
@ -314,20 +334,22 @@ describe('Application Schematic', () => {
expect(specTsConfig.files).toEqual(['src/test.ts', 'src/polyfills.ts']);
});
it('should set the relative path and prefix in the tslint file', () => {
it('should set the relative path and prefix in the tslint file', async () => {
const options = { ...defaultOptions, projectRoot: '' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const content = JSON.parse(tree.readContent('/tslint.json'));
expect(content.extends).toMatch('tslint:recommended');
expect(content.rules['directive-selector'][2]).toMatch('app');
expect(content.rules['component-selector'][2]).toMatch('app');
});
it('should merge tslint file', () => {
it('should merge tslint file', async () => {
const options = { ...defaultOptions, projectRoot: '' };
const tree = schematicRunner.runSchematic('application', options, workspaceTree);
const tree = await schematicRunner.runSchematicAsync('application', options, workspaceTree)
.toPromise();
const content = JSON.parse(tree.readContent('/tslint.json'));
expect(content.extends).toMatch('tslint:recommended');
expect(content.rules['component-selector'][2]).toMatch('app');