Skip to content

Commit 5bac828

Browse files
crisbetowagnermaciel
authored andcommitted
refactor(cdk/schematics): switch to latest API version
The latest `next` version of the CLI removed some deprecated APIs that we were depending. These changes update all the code so that it used the correct API (according to the CLI team). High-level overview of the changes and why they were necessary: 1. Previously we parsed the `angular.json` ourselves using `JSON.parse` in order to support very old versions of the CLI, however this is no longer feasible, because the CLI has set up classes around the parsed data which are non-trivial to construct. According to the CLI, we don't have to worry about older version anymore, because the schematics infrastructure will ensure that we're running against the correct version. 2. The interface of the new API is different from the one we were using before so I had to rewrite some code. 3. Some of these new APIs are asynchronous so I've had to move some code around to accommodate it. 4. Previously we would `JSON.parse` and `JSON.stringify` the `angular.json` file whenever we needed to mutate it (e.g. when adding a theme), but this isn't possible anymore due to the aforementioned classes around the config file. I've reworked our schematics to use a utility from the CLI to write to the file. 5. A lot of our tests depended on parsing the `angular.json`, changing a property and writing it back as JSON. This isn't possible, because the abstraction around the config can't be stringified so I've worked around it by writing out the `angular.json` file from scratch in the test. While this is more code, it should be easier to maintain in the long term.
1 parent 727285b commit 5bac828

24 files changed

+311
-280
lines changed

src/cdk/schematics/ng-generate/drag-drop/index.spec.ts

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
2-
import {getProjectFromWorkspace} from '@angular/cdk/schematics';
3-
import {getWorkspace} from '@schematics/angular/utility/config';
42
import {COLLECTION_PATH} from '../../index.spec';
53
import {createTestApp, getFileContent} from '../../testing';
64
import {Schema} from './schema';
@@ -56,16 +54,23 @@ describe('CDK drag-drop schematic', () => {
5654

5755
it('should respect the deprecated "styleext" option value', async () => {
5856
let tree = await createTestApp(runner);
59-
const workspace = getWorkspace(tree);
60-
const project = getProjectFromWorkspace(workspace);
61-
62-
// We need to specify the default component options by overwriting
63-
// the existing workspace configuration because passing the "styleext"
64-
// option is no longer supported. Though we want to verify that we
65-
// properly handle old CLI projects which still use the "styleext" option.
66-
project.schematics!['@schematics/angular:component'] = {styleext: 'scss'};
67-
68-
tree.overwrite('angular.json', JSON.stringify(workspace));
57+
tree.overwrite('angular.json', JSON.stringify({
58+
version: 1,
59+
defaultProject: 'material',
60+
projects: {
61+
material: {
62+
projectType: 'application',
63+
schematics: {
64+
// We need to specify the default component options by overwriting
65+
// the existing workspace configuration because passing the "styleext"
66+
// option is no longer supported. Though we want to verify that we
67+
// properly handle old CLI projects which still use the "styleext" option.
68+
'@schematics/angular:component': {styleext: 'scss'}
69+
},
70+
root: 'projects/material'
71+
}
72+
}
73+
}));
6974
tree = await runner.runSchematicAsync('drag-drop', baseOptions, tree).toPromise();
7075

7176
expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.scss');
@@ -153,16 +158,23 @@ describe('CDK drag-drop schematic', () => {
153158

154159
it('should respect the deprecated global "spec" option value', async () => {
155160
let tree = await createTestApp(runner);
156-
const workspace = getWorkspace(tree);
157-
const project = getProjectFromWorkspace(workspace);
158-
159-
// We need to specify the default component options by overwriting
160-
// the existing workspace configuration because passing the "spec"
161-
// option is no longer supported. Though we want to verify that we
162-
// properly handle old CLI projects which still use the "spec" option.
163-
project.schematics!['@schematics/angular:component'] = {spec: false};
164-
165-
tree.overwrite('angular.json', JSON.stringify(workspace));
161+
tree.overwrite('angular.json', JSON.stringify({
162+
version: 1,
163+
defaultProject: 'material',
164+
projects: {
165+
material: {
166+
projectType: 'application',
167+
schematics: {
168+
// We need to specify the default component options by overwriting
169+
// the existing workspace configuration because passing the "spec"
170+
// option is no longer supported. Though we want to verify that we
171+
// properly handle old CLI projects which still use the "spec" option.
172+
'@schematics/angular:component': {spec: false}
173+
},
174+
root: 'projects/material'
175+
}
176+
}
177+
}));
166178
tree = await runner.runSchematicAsync('drag-drop', baseOptions, tree).toPromise();
167179

168180
expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts');

src/cdk/schematics/ng-generate/drag-drop/index.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,8 @@ export default function(options: Schema): Rule {
2424

2525
/** Adds the required modules to the main module of the CLI project. */
2626
function addDragDropModulesToModule(options: Schema) {
27-
return (host: Tree) => {
28-
const modulePath = findModuleFromOptions(host, options)!;
29-
30-
addModuleImportToModule(host, modulePath, 'DragDropModule', '@angular/cdk/drag-drop');
31-
return host;
27+
return async (host: Tree) => {
28+
const modulePath = await findModuleFromOptions(host, options);
29+
addModuleImportToModule(host, modulePath!, 'DragDropModule', '@angular/cdk/drag-drop');
3230
};
3331
}

src/cdk/schematics/ng-update/devkit-migration-rule.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
1010
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
11-
import {WorkspaceProject} from '@schematics/angular/utility/workspace-models';
11+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
1212

1313
import {UpdateProject} from '../update-tool';
1414
import {WorkspacePath} from '../update-tool/file-system';
@@ -62,7 +62,7 @@ export function createMigrationSchematicRule(
6262
upgradeData: UpgradeData, onMigrationCompleteFn?: PostMigrationFn): Rule {
6363
return async (tree: Tree, context: SchematicContext) => {
6464
const logger = context.logger;
65-
const workspace = getWorkspaceConfigGracefully(tree);
65+
const workspace = await getWorkspaceConfigGracefully(tree);
6666

6767
if (workspace === null) {
6868
logger.error('Could not find workspace configuration file.');
@@ -74,12 +74,12 @@ export function createMigrationSchematicRule(
7474
// we don't want to check these again, as this would result in duplicated failure messages.
7575
const analyzedFiles = new Set<WorkspacePath>();
7676
const fileSystem = new DevkitFileSystem(tree);
77-
const projectNames = Object.keys(workspace.projects);
77+
const projectNames = workspace.projects.keys();
7878
const migrations: NullableDevkitMigration[] = [...cdkMigrations, ...extraMigrations];
7979
let hasFailures = false;
8080

8181
for (const projectName of projectNames) {
82-
const project = workspace.projects[projectName];
82+
const project = workspace.projects.get(projectName)!;
8383
const buildTsconfigPath = getTargetTsconfigPath(project, 'build');
8484
const testTsconfigPath = getTargetTsconfigPath(project, 'test');
8585

@@ -126,7 +126,7 @@ export function createMigrationSchematicRule(
126126
}
127127

128128
/** Runs the migrations for the specified workspace project. */
129-
function runMigrations(project: WorkspaceProject, projectName: string,
129+
function runMigrations(project: ProjectDefinition, projectName: string,
130130
tsconfigPath: WorkspacePath, additionalStylesheetPaths: string[],
131131
isTestTarget: boolean) {
132132
const program = UpdateProject.createProgramFromTsconfig(tsconfigPath, fileSystem);

src/cdk/schematics/ng-update/devkit-migration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {SchematicContext, Tree} from '@angular-devkit/schematics';
10-
import {WorkspaceProject} from '@schematics/angular/utility/workspace-models';
10+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
1111
import {Constructor, Migration, PostMigrationAction} from '../update-tool/migration';
1212

1313
export type DevkitContext = {
@@ -16,7 +16,7 @@ export type DevkitContext = {
1616
/** Name of the project the migrations run against. */
1717
projectName: string;
1818
/** Workspace project the migrations run against. */
19-
project: WorkspaceProject,
19+
project: ProjectDefinition,
2020
/** Whether the migrations run for a test target. */
2121
isTestTarget: boolean,
2222
};

src/cdk/schematics/testing/test-case-setup.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ export async function createTestCaseSetup(migrationName: string, collectionPath:
6767

6868
let logOutput = '';
6969
runner.logger.subscribe(entry => logOutput += `${entry.message}\n`);
70-
71-
const {appTree, writeFile} =
72-
await createFileSystemTestApp(runner);
70+
const {appTree, writeFile} = await createFileSystemTestApp(runner);
7371

7472
_patchTypeScriptDefaultLib(appTree);
7573

src/cdk/schematics/utils/ast.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
109
import {SchematicsException, Tree} from '@angular-devkit/schematics';
1110
import {Schema as ComponentOptions} from '@schematics/angular/component/schema';
1211
import {InsertChange} from '@schematics/angular/utility/change';
13-
import {getWorkspace} from '@schematics/angular/utility/config';
12+
import {getWorkspace} from '@schematics/angular/utility/workspace';
1413
import {findModuleFromOptions as internalFindModule} from '@schematics/angular/utility/find-module';
14+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
1515
import * as ts from 'typescript';
1616
import {getProjectMainFile} from './project-main-file';
1717
import {addImportToModule, getAppModulePath} from './vendored-ast-utils';
@@ -27,7 +27,7 @@ export function parseSourceFile(host: Tree, path: string): ts.SourceFile {
2727

2828
/** Import and add module to root app module. */
2929
export function addModuleImportToRootModule(host: Tree, moduleName: string, src: string,
30-
project: WorkspaceProject) {
30+
project: ProjectDefinition) {
3131
const modulePath = getAppModulePath(host, getProjectMainFile(project));
3232
addModuleImportToModule(host, modulePath, moduleName, src);
3333
}
@@ -51,7 +51,7 @@ export function addModuleImportToModule(host: Tree, modulePath: string, moduleNa
5151
const changes = addImportToModule(moduleSource, modulePath, moduleName, src);
5252
const recorder = host.beginUpdate(modulePath);
5353

54-
changes.forEach((change) => {
54+
changes.forEach(change => {
5555
if (change instanceof InsertChange) {
5656
recorder.insertLeft(change.pos, change.toAdd);
5757
}
@@ -61,14 +61,15 @@ export function addModuleImportToModule(host: Tree, modulePath: string, moduleNa
6161
}
6262

6363
/** Wraps the internal find module from options with undefined path handling */
64-
export function findModuleFromOptions(host: Tree, options: ComponentOptions): string | undefined {
65-
const workspace = getWorkspace(host);
64+
export async function findModuleFromOptions(host: Tree, options: ComponentOptions):
65+
Promise<string | undefined> {
66+
const workspace = await getWorkspace(host);
6667

6768
if (!options.project) {
68-
options.project = Object.keys(workspace.projects)[0];
69+
options.project = Array.from(workspace.projects.keys())[0];
6970
}
7071

71-
const project = workspace.projects[options.project];
72+
const project = workspace.projects.get(options.project)!;
7273

7374
if (options.path === undefined) {
7475
options.path = `/${project.root}/src/app`;

src/cdk/schematics/utils/build-component.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ import {
2424
import {FileSystemSchematicContext} from '@angular-devkit/schematics/tools';
2525
import {Schema as ComponentOptions, Style} from '@schematics/angular/component/schema';
2626
import {InsertChange} from '@schematics/angular/utility/change';
27-
import {getWorkspace} from '@schematics/angular/utility/config';
27+
import {getWorkspace} from '@schematics/angular/utility/workspace';
2828
import {buildRelativePath, findModuleFromOptions} from '@schematics/angular/utility/find-module';
2929
import {parseName} from '@schematics/angular/utility/parse-name';
3030
import {validateHtmlSelector, validateName} from '@schematics/angular/utility/validation';
31-
import {ProjectType, WorkspaceProject} from '@schematics/angular/utility/workspace-models';
31+
import {ProjectType} from '@schematics/angular/utility/workspace-models';
3232
import {readFileSync, statSync} from 'fs';
3333
import {dirname, join, resolve} from 'path';
3434
import * as ts from 'typescript';
@@ -39,17 +39,18 @@ import {
3939
} from '../utils/vendored-ast-utils';
4040
import {getProjectFromWorkspace} from './get-project';
4141
import {getDefaultComponentOptions} from './schematic-options';
42+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
4243

4344
/**
4445
* Build a default project path for generating.
4546
* @param project The project to build the path for.
4647
*/
47-
function buildDefaultPath(project: WorkspaceProject): string {
48+
function buildDefaultPath(project: ProjectDefinition): string {
4849
const root = project.sourceRoot
4950
? `/${project.sourceRoot}/`
5051
: `/${project.root}/src/`;
5152

52-
const projectDirName = project.projectType === ProjectType.Application ? 'app' : 'lib';
53+
const projectDirName = project.extensions.projectType === ProjectType.Application ? 'app' : 'lib';
5354

5455
return `${root}${projectDirName}`;
5556
}
@@ -143,7 +144,7 @@ function addDeclarationToNgModule(options: ComponentOptions): Rule {
143144
}
144145

145146

146-
function buildSelector(options: ComponentOptions, projectPrefix: string) {
147+
function buildSelector(options: ComponentOptions, projectPrefix?: string) {
147148
let selector = strings.dasherize(options.name);
148149
if (options.prefix) {
149150
selector = `${options.prefix}-${selector}`;
@@ -176,8 +177,8 @@ function indentTextContent(text: string, numSpaces: number): string {
176177
export function buildComponent(options: ComponentOptions,
177178
additionalFiles: {[key: string]: string} = {}): Rule {
178179

179-
return (host: Tree, context: FileSystemSchematicContext) => {
180-
const workspace = getWorkspace(host);
180+
return async (host: Tree, context: FileSystemSchematicContext) => {
181+
const workspace = await getWorkspace(host);
181182
const project = getProjectFromWorkspace(workspace, options.project);
182183
const defaultComponentOptions = getDefaultComponentOptions(project);
183184

@@ -200,7 +201,7 @@ export function buildComponent(options: ComponentOptions,
200201

201202
if (options.path === undefined) {
202203
// TODO(jelbourn): figure out if the need for this `as any` is a bug due to two different
203-
// incompatible `WorkspaceProject` classes in @angular-devkit
204+
// incompatible `ProjectDefinition` classes in @angular-devkit
204205
options.path = buildDefaultPath(project as any);
205206
}
206207

@@ -257,7 +258,7 @@ export function buildComponent(options: ComponentOptions,
257258
move(null as any, parsedPath.path),
258259
]);
259260

260-
return chain([
261+
return () => chain([
261262
branchAndMerge(chain([
262263
addDeclarationToNgModule(options),
263264
mergeWith(templateSource),

src/cdk/schematics/utils/get-project.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {WorkspaceSchema, WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
9+
import {ProjectDefinition, WorkspaceDefinition} from '@angular-devkit/core/src/workspace';
1010
import {SchematicsException} from '@angular-devkit/schematics';
1111

1212
/**
1313
* Finds the specified project configuration in the workspace. Throws an error if the project
1414
* couldn't be found.
1515
*/
1616
export function getProjectFromWorkspace(
17-
workspace: WorkspaceSchema,
18-
projectName?: string): WorkspaceProject {
19-
20-
const project = workspace.projects[projectName || workspace.defaultProject!];
17+
workspace: WorkspaceDefinition,
18+
projectName = workspace.extensions.defaultProject as string): ProjectDefinition {
19+
const project = workspace.projects.get(projectName);
2120

2221
if (!project) {
2322
throw new SchematicsException(`Could not find project in workspace: ${projectName}`);

src/cdk/schematics/utils/project-index-file.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@
77
*/
88

99
import {Path} from '@angular-devkit/core';
10-
import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
11-
import {BrowserBuilderTarget} from '@schematics/angular/utility/workspace-models';
10+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
1211
import {defaultTargetBuilders, getTargetsByBuilderName} from './project-targets';
1312

1413
/** Gets the path of the index file in the given project. */
15-
export function getProjectIndexFiles(project: WorkspaceProject): Path[] {
16-
// Use a set to remove duplicate index files referenced in multiple build targets
17-
// of a project.
18-
return [...new Set(
19-
(getTargetsByBuilderName(project, defaultTargetBuilders.build) as BrowserBuilderTarget[])
20-
.filter(t => t.options.index)
21-
.map(t => t.options.index! as Path))];
14+
export function getProjectIndexFiles(project: ProjectDefinition): Path[] {
15+
const paths = getTargetsByBuilderName(project, defaultTargetBuilders.build)
16+
.filter(t => t.options?.index)
17+
.map(t => t.options!.index as Path);
18+
19+
// Use a set to remove duplicate index files referenced in multiple build targets of a project.
20+
return Array.from(new Set(paths));
2221
}

src/cdk/schematics/utils/project-main-file.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77
*/
88

99
import {Path} from '@angular-devkit/core';
10-
import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
10+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
1111
import {SchematicsException} from '@angular-devkit/schematics';
1212
import {getProjectTargetOptions} from './project-targets';
1313

1414
/** Looks for the main TypeScript file in the given project and returns its path. */
15-
export function getProjectMainFile(project: WorkspaceProject): Path {
15+
export function getProjectMainFile(project: ProjectDefinition): Path {
1616
const buildOptions = getProjectTargetOptions(project, 'build');
1717

1818
if (!buildOptions.main) {
1919
throw new SchematicsException(`Could not find the project main file inside of the ` +
2020
`workspace config (${project.sourceRoot})`);
2121
}
2222

23-
return buildOptions.main;
23+
return buildOptions.main as Path;
2424
}

src/cdk/schematics/utils/project-style-file.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {normalize} from '@angular-devkit/core';
10-
import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace';
9+
import {isJsonArray, normalize} from '@angular-devkit/core';
10+
import {ProjectDefinition} from '@angular-devkit/core/src/workspace';
1111
import {getProjectTargetOptions} from './project-targets';
1212

1313
/** Regular expression that matches all possible Angular CLI default style files. */
@@ -20,11 +20,11 @@ const validStyleFileRegex = /\.(c|le|sc)ss/;
2020
* Gets a style file with the given extension in a project and returns its path. If no
2121
* extension is specified, any style file with a valid extension will be returned.
2222
*/
23-
export function getProjectStyleFile(project: WorkspaceProject, extension?: string): string | null {
23+
export function getProjectStyleFile(project: ProjectDefinition, extension?: string): string | null {
2424
const buildOptions = getProjectTargetOptions(project, 'build');
25-
26-
if (buildOptions.styles && buildOptions.styles.length) {
27-
const styles = buildOptions.styles.map(s => typeof s === 'string' ? s : s.input);
25+
if (buildOptions.styles && isJsonArray(buildOptions.styles) && buildOptions.styles.length) {
26+
const styles =
27+
buildOptions.styles.map(s => typeof s === 'string' ? s : (s as {input: string}).input);
2828

2929
// Look for the default style file that is generated for new projects by the Angular CLI. This
3030
// default style file is usually called `styles.ext` unless it has been changed explicitly.

0 commit comments

Comments
 (0)