From 58f6282edffe05f95b8b873cc2a3e8eab028f24c Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 15 Apr 2019 23:22:44 -0400 Subject: [PATCH] refactor(@schematics/angular): update application to use new workspace rules --- .../schematics/angular/application/index.ts | 76 +++-------- .../angular/application/index_spec.ts | 122 +++++++++++------- 2 files changed, 93 insertions(+), 105 deletions(-) diff --git a/packages/schematics/angular/application/index.ts b/packages/schematics/angular/application/index.ts index 1300cc799484..b7a3568aff99 100644 --- a/packages/schematics/angular/application/index.ts +++ b/packages/schematics/angular/application/index.ts @@ -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(), diff --git a/packages/schematics/angular/application/index_spec.ts b/packages/schematics/angular/application/index_spec.ts index f19d3297177b..e46bfde7d19d 100644 --- a/packages/schematics/angular/application/index_spec.ts +++ b/packages/schematics/angular/application/index_spec.ts @@ -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');