diff --git a/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts b/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts index ddf70c3fe1ce..0b9be64f884b 100644 --- a/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts +++ b/src/cdk/schematics/ng-generate/drag-drop/index.spec.ts @@ -1,6 +1,4 @@ import {SchematicTestRunner} from '@angular-devkit/schematics/testing'; -import {getProjectFromWorkspace} from '@angular/cdk/schematics'; -import {getWorkspace} from '@schematics/angular/utility/config'; import {COLLECTION_PATH} from '../../index.spec'; import {createTestApp, getFileContent} from '../../testing'; import {Schema} from './schema'; @@ -56,16 +54,23 @@ describe('CDK drag-drop schematic', () => { it('should respect the deprecated "styleext" option value', async () => { let tree = await createTestApp(runner); - const workspace = getWorkspace(tree); - const project = getProjectFromWorkspace(workspace); - - // We need to specify the default component options by overwriting - // the existing workspace configuration because passing the "styleext" - // option is no longer supported. Though we want to verify that we - // properly handle old CLI projects which still use the "styleext" option. - project.schematics!['@schematics/angular:component'] = {styleext: 'scss'}; - - tree.overwrite('angular.json', JSON.stringify(workspace)); + tree.overwrite('angular.json', JSON.stringify({ + version: 1, + defaultProject: 'material', + projects: { + material: { + projectType: 'application', + schematics: { + // We need to specify the default component options by overwriting + // the existing workspace configuration because passing the "styleext" + // option is no longer supported. Though we want to verify that we + // properly handle old CLI projects which still use the "styleext" option. + '@schematics/angular:component': {styleext: 'scss'} + }, + root: 'projects/material' + } + } + })); tree = await runner.runSchematicAsync('drag-drop', baseOptions, tree).toPromise(); expect(tree.files).toContain('/projects/material/src/app/foo/foo.component.scss'); @@ -153,16 +158,23 @@ describe('CDK drag-drop schematic', () => { it('should respect the deprecated global "spec" option value', async () => { let tree = await createTestApp(runner); - const workspace = getWorkspace(tree); - const project = getProjectFromWorkspace(workspace); - - // We need to specify the default component options by overwriting - // the existing workspace configuration because passing the "spec" - // option is no longer supported. Though we want to verify that we - // properly handle old CLI projects which still use the "spec" option. - project.schematics!['@schematics/angular:component'] = {spec: false}; - - tree.overwrite('angular.json', JSON.stringify(workspace)); + tree.overwrite('angular.json', JSON.stringify({ + version: 1, + defaultProject: 'material', + projects: { + material: { + projectType: 'application', + schematics: { + // We need to specify the default component options by overwriting + // the existing workspace configuration because passing the "spec" + // option is no longer supported. Though we want to verify that we + // properly handle old CLI projects which still use the "spec" option. + '@schematics/angular:component': {spec: false} + }, + root: 'projects/material' + } + } + })); tree = await runner.runSchematicAsync('drag-drop', baseOptions, tree).toPromise(); expect(tree.files).not.toContain('/projects/material/src/app/foo/foo.component.spec.ts'); diff --git a/src/cdk/schematics/ng-generate/drag-drop/index.ts b/src/cdk/schematics/ng-generate/drag-drop/index.ts index be5d28ca7356..97f71fc6cf4a 100644 --- a/src/cdk/schematics/ng-generate/drag-drop/index.ts +++ b/src/cdk/schematics/ng-generate/drag-drop/index.ts @@ -24,10 +24,8 @@ export default function(options: Schema): Rule { /** Adds the required modules to the main module of the CLI project. */ function addDragDropModulesToModule(options: Schema) { - return (host: Tree) => { - const modulePath = findModuleFromOptions(host, options)!; - - addModuleImportToModule(host, modulePath, 'DragDropModule', '@angular/cdk/drag-drop'); - return host; + return async (host: Tree) => { + const modulePath = await findModuleFromOptions(host, options); + addModuleImportToModule(host, modulePath!, 'DragDropModule', '@angular/cdk/drag-drop'); }; } diff --git a/src/cdk/schematics/ng-update/devkit-migration-rule.ts b/src/cdk/schematics/ng-update/devkit-migration-rule.ts index 170553c0cce5..9fde382e0394 100644 --- a/src/cdk/schematics/ng-update/devkit-migration-rule.ts +++ b/src/cdk/schematics/ng-update/devkit-migration-rule.ts @@ -8,7 +8,7 @@ import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; -import {WorkspaceProject} from '@schematics/angular/utility/workspace-models'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import {UpdateProject} from '../update-tool'; import {WorkspacePath} from '../update-tool/file-system'; @@ -62,7 +62,7 @@ export function createMigrationSchematicRule( upgradeData: UpgradeData, onMigrationCompleteFn?: PostMigrationFn): Rule { return async (tree: Tree, context: SchematicContext) => { const logger = context.logger; - const workspace = getWorkspaceConfigGracefully(tree); + const workspace = await getWorkspaceConfigGracefully(tree); if (workspace === null) { logger.error('Could not find workspace configuration file.'); @@ -74,12 +74,12 @@ export function createMigrationSchematicRule( // we don't want to check these again, as this would result in duplicated failure messages. const analyzedFiles = new Set(); const fileSystem = new DevkitFileSystem(tree); - const projectNames = Object.keys(workspace.projects); + const projectNames = workspace.projects.keys(); const migrations: NullableDevkitMigration[] = [...cdkMigrations, ...extraMigrations]; let hasFailures = false; for (const projectName of projectNames) { - const project = workspace.projects[projectName]; + const project = workspace.projects.get(projectName)!; const buildTsconfigPath = getTargetTsconfigPath(project, 'build'); const testTsconfigPath = getTargetTsconfigPath(project, 'test'); @@ -126,7 +126,7 @@ export function createMigrationSchematicRule( } /** Runs the migrations for the specified workspace project. */ - function runMigrations(project: WorkspaceProject, projectName: string, + function runMigrations(project: ProjectDefinition, projectName: string, tsconfigPath: WorkspacePath, additionalStylesheetPaths: string[], isTestTarget: boolean) { const program = UpdateProject.createProgramFromTsconfig(tsconfigPath, fileSystem); diff --git a/src/cdk/schematics/ng-update/devkit-migration.ts b/src/cdk/schematics/ng-update/devkit-migration.ts index feae4bc66777..dcd764ed5df6 100644 --- a/src/cdk/schematics/ng-update/devkit-migration.ts +++ b/src/cdk/schematics/ng-update/devkit-migration.ts @@ -7,7 +7,7 @@ */ import {SchematicContext, Tree} from '@angular-devkit/schematics'; -import {WorkspaceProject} from '@schematics/angular/utility/workspace-models'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import {Constructor, Migration, PostMigrationAction} from '../update-tool/migration'; export type DevkitContext = { @@ -16,7 +16,7 @@ export type DevkitContext = { /** Name of the project the migrations run against. */ projectName: string; /** Workspace project the migrations run against. */ - project: WorkspaceProject, + project: ProjectDefinition, /** Whether the migrations run for a test target. */ isTestTarget: boolean, }; diff --git a/src/cdk/schematics/testing/test-case-setup.ts b/src/cdk/schematics/testing/test-case-setup.ts index a268830c218e..03569d2b3bf9 100644 --- a/src/cdk/schematics/testing/test-case-setup.ts +++ b/src/cdk/schematics/testing/test-case-setup.ts @@ -67,9 +67,7 @@ export async function createTestCaseSetup(migrationName: string, collectionPath: let logOutput = ''; runner.logger.subscribe(entry => logOutput += `${entry.message}\n`); - - const {appTree, writeFile} = - await createFileSystemTestApp(runner); + const {appTree, writeFile} = await createFileSystemTestApp(runner); _patchTypeScriptDefaultLib(appTree); diff --git a/src/cdk/schematics/utils/ast.ts b/src/cdk/schematics/utils/ast.ts index 8660e1ea3e46..140e8aedbca2 100644 --- a/src/cdk/schematics/utils/ast.ts +++ b/src/cdk/schematics/utils/ast.ts @@ -6,12 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; import {SchematicsException, Tree} from '@angular-devkit/schematics'; import {Schema as ComponentOptions} from '@schematics/angular/component/schema'; import {InsertChange} from '@schematics/angular/utility/change'; -import {getWorkspace} from '@schematics/angular/utility/config'; +import {getWorkspace} from '@schematics/angular/utility/workspace'; import {findModuleFromOptions as internalFindModule} from '@schematics/angular/utility/find-module'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import * as ts from 'typescript'; import {getProjectMainFile} from './project-main-file'; import {addImportToModule, getAppModulePath} from './vendored-ast-utils'; @@ -27,7 +27,7 @@ export function parseSourceFile(host: Tree, path: string): ts.SourceFile { /** Import and add module to root app module. */ export function addModuleImportToRootModule(host: Tree, moduleName: string, src: string, - project: WorkspaceProject) { + project: ProjectDefinition) { const modulePath = getAppModulePath(host, getProjectMainFile(project)); addModuleImportToModule(host, modulePath, moduleName, src); } @@ -51,7 +51,7 @@ export function addModuleImportToModule(host: Tree, modulePath: string, moduleNa const changes = addImportToModule(moduleSource, modulePath, moduleName, src); const recorder = host.beginUpdate(modulePath); - changes.forEach((change) => { + changes.forEach(change => { if (change instanceof InsertChange) { recorder.insertLeft(change.pos, change.toAdd); } @@ -61,14 +61,15 @@ export function addModuleImportToModule(host: Tree, modulePath: string, moduleNa } /** Wraps the internal find module from options with undefined path handling */ -export function findModuleFromOptions(host: Tree, options: ComponentOptions): string | undefined { - const workspace = getWorkspace(host); +export async function findModuleFromOptions(host: Tree, options: ComponentOptions): + Promise { + const workspace = await getWorkspace(host); if (!options.project) { - options.project = Object.keys(workspace.projects)[0]; + options.project = Array.from(workspace.projects.keys())[0]; } - const project = workspace.projects[options.project]; + const project = workspace.projects.get(options.project)!; if (options.path === undefined) { options.path = `/${project.root}/src/app`; diff --git a/src/cdk/schematics/utils/build-component.ts b/src/cdk/schematics/utils/build-component.ts index 05837bf8be1f..e803cbd3ae25 100644 --- a/src/cdk/schematics/utils/build-component.ts +++ b/src/cdk/schematics/utils/build-component.ts @@ -24,11 +24,11 @@ import { import {FileSystemSchematicContext} from '@angular-devkit/schematics/tools'; import {Schema as ComponentOptions, Style} from '@schematics/angular/component/schema'; import {InsertChange} from '@schematics/angular/utility/change'; -import {getWorkspace} from '@schematics/angular/utility/config'; +import {getWorkspace} from '@schematics/angular/utility/workspace'; import {buildRelativePath, findModuleFromOptions} from '@schematics/angular/utility/find-module'; import {parseName} from '@schematics/angular/utility/parse-name'; import {validateHtmlSelector, validateName} from '@schematics/angular/utility/validation'; -import {ProjectType, WorkspaceProject} from '@schematics/angular/utility/workspace-models'; +import {ProjectType} from '@schematics/angular/utility/workspace-models'; import {readFileSync, statSync} from 'fs'; import {dirname, join, resolve} from 'path'; import * as ts from 'typescript'; @@ -39,17 +39,18 @@ import { } from '../utils/vendored-ast-utils'; import {getProjectFromWorkspace} from './get-project'; import {getDefaultComponentOptions} from './schematic-options'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; /** * Build a default project path for generating. * @param project The project to build the path for. */ -function buildDefaultPath(project: WorkspaceProject): string { +function buildDefaultPath(project: ProjectDefinition): string { const root = project.sourceRoot ? `/${project.sourceRoot}/` : `/${project.root}/src/`; - const projectDirName = project.projectType === ProjectType.Application ? 'app' : 'lib'; + const projectDirName = project.extensions.projectType === ProjectType.Application ? 'app' : 'lib'; return `${root}${projectDirName}`; } @@ -143,7 +144,7 @@ function addDeclarationToNgModule(options: ComponentOptions): Rule { } -function buildSelector(options: ComponentOptions, projectPrefix: string) { +function buildSelector(options: ComponentOptions, projectPrefix?: string) { let selector = strings.dasherize(options.name); if (options.prefix) { selector = `${options.prefix}-${selector}`; @@ -176,8 +177,8 @@ function indentTextContent(text: string, numSpaces: number): string { export function buildComponent(options: ComponentOptions, additionalFiles: {[key: string]: string} = {}): Rule { - return (host: Tree, context: FileSystemSchematicContext) => { - const workspace = getWorkspace(host); + return async (host: Tree, context: FileSystemSchematicContext) => { + const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const defaultComponentOptions = getDefaultComponentOptions(project); @@ -200,7 +201,7 @@ export function buildComponent(options: ComponentOptions, if (options.path === undefined) { // TODO(jelbourn): figure out if the need for this `as any` is a bug due to two different - // incompatible `WorkspaceProject` classes in @angular-devkit + // incompatible `ProjectDefinition` classes in @angular-devkit options.path = buildDefaultPath(project as any); } @@ -257,7 +258,7 @@ export function buildComponent(options: ComponentOptions, move(null as any, parsedPath.path), ]); - return chain([ + return () => chain([ branchAndMerge(chain([ addDeclarationToNgModule(options), mergeWith(templateSource), diff --git a/src/cdk/schematics/utils/get-project.ts b/src/cdk/schematics/utils/get-project.ts index b68d69d3d8a1..a88e9a6c709a 100644 --- a/src/cdk/schematics/utils/get-project.ts +++ b/src/cdk/schematics/utils/get-project.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {WorkspaceSchema, WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; +import {ProjectDefinition, WorkspaceDefinition} from '@angular-devkit/core/src/workspace'; import {SchematicsException} from '@angular-devkit/schematics'; /** @@ -14,10 +14,9 @@ import {SchematicsException} from '@angular-devkit/schematics'; * couldn't be found. */ export function getProjectFromWorkspace( - workspace: WorkspaceSchema, - projectName?: string): WorkspaceProject { - - const project = workspace.projects[projectName || workspace.defaultProject!]; + workspace: WorkspaceDefinition, + projectName = workspace.extensions.defaultProject as string): ProjectDefinition { + const project = workspace.projects.get(projectName); if (!project) { throw new SchematicsException(`Could not find project in workspace: ${projectName}`); diff --git a/src/cdk/schematics/utils/project-index-file.ts b/src/cdk/schematics/utils/project-index-file.ts index 7cb1ede2194b..4510afd7c94f 100644 --- a/src/cdk/schematics/utils/project-index-file.ts +++ b/src/cdk/schematics/utils/project-index-file.ts @@ -7,16 +7,15 @@ */ import {Path} from '@angular-devkit/core'; -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; -import {BrowserBuilderTarget} from '@schematics/angular/utility/workspace-models'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import {defaultTargetBuilders, getTargetsByBuilderName} from './project-targets'; /** Gets the path of the index file in the given project. */ -export function getProjectIndexFiles(project: WorkspaceProject): Path[] { - // Use a set to remove duplicate index files referenced in multiple build targets - // of a project. - return [...new Set( - (getTargetsByBuilderName(project, defaultTargetBuilders.build) as BrowserBuilderTarget[]) - .filter(t => t.options.index) - .map(t => t.options.index! as Path))]; +export function getProjectIndexFiles(project: ProjectDefinition): Path[] { + const paths = getTargetsByBuilderName(project, defaultTargetBuilders.build) + .filter(t => t.options?.index) + .map(t => t.options!.index as Path); + + // Use a set to remove duplicate index files referenced in multiple build targets of a project. + return Array.from(new Set(paths)); } diff --git a/src/cdk/schematics/utils/project-main-file.ts b/src/cdk/schematics/utils/project-main-file.ts index ce0b2ccda88b..b7cd2b25388f 100644 --- a/src/cdk/schematics/utils/project-main-file.ts +++ b/src/cdk/schematics/utils/project-main-file.ts @@ -7,12 +7,12 @@ */ import {Path} from '@angular-devkit/core'; -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import {SchematicsException} from '@angular-devkit/schematics'; import {getProjectTargetOptions} from './project-targets'; /** Looks for the main TypeScript file in the given project and returns its path. */ -export function getProjectMainFile(project: WorkspaceProject): Path { +export function getProjectMainFile(project: ProjectDefinition): Path { const buildOptions = getProjectTargetOptions(project, 'build'); if (!buildOptions.main) { @@ -20,5 +20,5 @@ export function getProjectMainFile(project: WorkspaceProject): Path { `workspace config (${project.sourceRoot})`); } - return buildOptions.main; + return buildOptions.main as Path; } diff --git a/src/cdk/schematics/utils/project-style-file.ts b/src/cdk/schematics/utils/project-style-file.ts index 7ca209fcc1d7..f5554e611309 100644 --- a/src/cdk/schematics/utils/project-style-file.ts +++ b/src/cdk/schematics/utils/project-style-file.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {normalize} from '@angular-devkit/core'; -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; +import {isJsonArray, normalize} from '@angular-devkit/core'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import {getProjectTargetOptions} from './project-targets'; /** Regular expression that matches all possible Angular CLI default style files. */ @@ -20,11 +20,11 @@ const validStyleFileRegex = /\.(c|le|sc)ss/; * Gets a style file with the given extension in a project and returns its path. If no * extension is specified, any style file with a valid extension will be returned. */ -export function getProjectStyleFile(project: WorkspaceProject, extension?: string): string | null { +export function getProjectStyleFile(project: ProjectDefinition, extension?: string): string | null { const buildOptions = getProjectTargetOptions(project, 'build'); - - if (buildOptions.styles && buildOptions.styles.length) { - const styles = buildOptions.styles.map(s => typeof s === 'string' ? s : s.input); + if (buildOptions.styles && isJsonArray(buildOptions.styles) && buildOptions.styles.length) { + const styles = + buildOptions.styles.map(s => typeof s === 'string' ? s : (s as {input: string}).input); // Look for the default style file that is generated for new projects by the Angular CLI. This // default style file is usually called `styles.ext` unless it has been changed explicitly. diff --git a/src/cdk/schematics/utils/project-targets.ts b/src/cdk/schematics/utils/project-targets.ts index dbed3d4951ab..cc087a5d3ae5 100644 --- a/src/cdk/schematics/utils/project-targets.ts +++ b/src/cdk/schematics/utils/project-targets.ts @@ -6,9 +6,9 @@ * found in the LICENSE file at https://angular.io/license */ -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; +import {ProjectDefinition, TargetDefinition} from '@angular-devkit/core/src/workspace'; +import {JsonValue} from '@angular-devkit/core'; import {SchematicsException} from '@angular-devkit/schematics'; -import {BuilderTarget} from '@schematics/angular/utility/workspace-models'; /** Object that maps a CLI target to its default builder name. */ export const defaultTargetBuilders = { @@ -17,28 +17,22 @@ export const defaultTargetBuilders = { }; /** Resolves the architect options for the build target of the given project. */ -export function getProjectTargetOptions(project: WorkspaceProject, buildTarget: string) { - if (project.targets && project.targets[buildTarget] && project.targets[buildTarget].options) { - return project.targets[buildTarget].options; - } +export function getProjectTargetOptions(project: ProjectDefinition, buildTarget: string): + Record { + const options = project.targets?.get(buildTarget)?.options; - // TODO(devversion): consider removing this architect check if the CLI completely switched - // over to `targets`, and the `architect` support has been removed. - // See: https://github.com/angular/angular-cli/commit/307160806cb48c95ecb8982854f452303801ac9f - if (project.architect && project.architect[buildTarget] && - project.architect[buildTarget].options) { - return project.architect[buildTarget].options; + if (!options) { + throw new SchematicsException( + `Cannot determine project target configuration for: ${buildTarget}.`); } - throw new SchematicsException( - `Cannot determine project target configuration for: ${buildTarget}.`); + return options; } /** Gets all targets from the given project that match the specified builder name. */ export function getTargetsByBuilderName( - project: WorkspaceProject, builderName: string): BuilderTarget[] { - const targets = project.targets || project.architect || {}; - return Object.keys(targets) - .filter(name => targets[name].builder === builderName) - .map(name => targets[name]); + project: ProjectDefinition, builderName: string): TargetDefinition[] { + return Array.from(project.targets.keys()) + .filter(name => project.targets.get(name)?.builder === builderName) + .map(name => project.targets.get(name)!); } diff --git a/src/cdk/schematics/utils/project-tsconfig-paths.spec.ts b/src/cdk/schematics/utils/project-tsconfig-paths.spec.ts index 5083a9074a90..4ff7cb716dc0 100644 --- a/src/cdk/schematics/utils/project-tsconfig-paths.spec.ts +++ b/src/cdk/schematics/utils/project-tsconfig-paths.spec.ts @@ -8,23 +8,25 @@ describe('project tsconfig paths', () => { beforeEach(() => { testTree = new UnitTestTree(new HostTree()); }); - it('should detect build tsconfig path inside of angular.json file', () => { + it('should detect build tsconfig path inside of angular.json file', async () => { testTree.create('/my-custom-config.json', ''); testTree.create('/angular.json', JSON.stringify({ + version: 1, projects: {my_name: {architect: {build: {options: {tsConfig: './my-custom-config.json'}}}}} })); - const config = getWorkspaceConfigGracefully(testTree); + const config = await getWorkspaceConfigGracefully(testTree); expect(config).not.toBeNull(); - expect(getTargetTsconfigPath(config!.projects['my_name'], 'build')) + expect(getTargetTsconfigPath(config!.projects!.get('my_name')!, 'build')) .toEqual('my-custom-config.json' as WorkspacePath); }); - it('should be able to read workspace configuration which is using JSON5 features', () => { + it('should be able to read workspace configuration which is using JSON5 features', async () => { testTree.create('/my-build-config.json', ''); testTree.create('/angular.json', `{ // Comments, unquoted properties or trailing commas are only supported in JSON5. + version: 1, projects: { with_tests: { targets: { @@ -38,34 +40,36 @@ describe('project tsconfig paths', () => { }, }`); - const config = getWorkspaceConfigGracefully(testTree); + const config = await getWorkspaceConfigGracefully(testTree); expect(config).not.toBeNull(); - expect(getTargetTsconfigPath(config!.projects['with_tests'], 'build')) + expect(getTargetTsconfigPath(config!.projects.get('with_tests')!, 'build')) .toEqual('my-build-config.json' as WorkspacePath); }); - it('should detect test tsconfig path inside of angular.json file', () => { + it('should detect test tsconfig path inside of angular.json file', async () => { testTree.create('/my-test-config.json', ''); testTree.create('/angular.json', JSON.stringify({ + version: 1, projects: {my_name: {architect: {test: {options: {tsConfig: './my-test-config.json'}}}}} })); - const config = getWorkspaceConfigGracefully(testTree); + const config = await getWorkspaceConfigGracefully(testTree); expect(config).not.toBeNull(); - expect(getTargetTsconfigPath(config!.projects['my_name'], 'test')) + expect(getTargetTsconfigPath(config!.projects.get('my_name')!, 'test')) .toEqual('my-test-config.json' as WorkspacePath); }); - it('should detect test tsconfig path inside of .angular.json file', () => { + it('should detect test tsconfig path inside of .angular.json file', async () => { testTree.create('/my-test-config.json', ''); testTree.create('/.angular.json', JSON.stringify({ + version: 1, projects: {with_tests: {architect: {test: {options: {tsConfig: './my-test-config.json'}}}}} })); - const config = getWorkspaceConfigGracefully(testTree); + const config = await getWorkspaceConfigGracefully(testTree); expect(config).not.toBeNull(); - expect(getTargetTsconfigPath(config!.projects['with_tests'], 'test')) + expect(getTargetTsconfigPath(config!.projects.get('with_tests')!, 'test')) .toEqual('my-test-config.json' as WorkspacePath); }); }); diff --git a/src/cdk/schematics/utils/project-tsconfig-paths.ts b/src/cdk/schematics/utils/project-tsconfig-paths.ts index 8786113c5b59..f43a76119683 100644 --- a/src/cdk/schematics/utils/project-tsconfig-paths.ts +++ b/src/cdk/schematics/utils/project-tsconfig-paths.ts @@ -6,36 +6,28 @@ * found in the LICENSE file at https://angular.io/license */ -import {JsonParseMode, normalize, parseJson} from '@angular-devkit/core'; +import {normalize} from '@angular-devkit/core'; +import { + ProjectDefinition, + WorkspaceDefinition, + WorkspaceHost, +} from '@angular-devkit/core/src/workspace'; +import {readJsonWorkspace} from '@angular-devkit/core/src/workspace/json/reader'; import {Tree} from '@angular-devkit/schematics'; -import {WorkspaceProject, WorkspaceSchema} from '@schematics/angular/utility/workspace-models'; import {WorkspacePath} from '../update-tool/file-system'; /** Name of the default Angular CLI workspace configuration files. */ const defaultWorkspaceConfigPaths = ['/angular.json', '/.angular.json']; /** Gets the tsconfig path from the given target within the specified project. */ -export function getTargetTsconfigPath(project: WorkspaceProject, +export function getTargetTsconfigPath(project: ProjectDefinition, targetName: string): WorkspacePath|null { - if (project.targets && project.targets[targetName] && project.targets[targetName].options && - project.targets[targetName].options.tsConfig) { - return normalize(project.targets[targetName].options.tsConfig); - } - - if (project.architect && project.architect[targetName] && project.architect[targetName].options && - project.architect[targetName].options.tsConfig) { - return normalize(project.architect[targetName].options.tsConfig); - } - return null; + const tsconfig = project.targets?.get(targetName)?.options?.tsConfig; + return tsconfig ? normalize(tsconfig as string) : null; } -/** - * Resolve the workspace configuration of the specified tree gracefully. We cannot use the utility - * functions from the default Angular schematics because those might not be present in older - * versions of the CLI. Also it's important to resolve the workspace gracefully because - * the CLI project could be still using `.angular-cli.json` instead of thew new config. - */ -export function getWorkspaceConfigGracefully(tree: Tree): null|WorkspaceSchema { +/** Resolve the workspace configuration of the specified tree gracefully. */ +export async function getWorkspaceConfigGracefully(tree: Tree): Promise { const path = defaultWorkspaceConfigPaths.find(filePath => tree.exists(filePath)); const configBuffer = tree.read(path!); @@ -44,9 +36,9 @@ export function getWorkspaceConfigGracefully(tree: Tree): null|WorkspaceSchema { } try { - // Parse the workspace file as JSON5 which is also supported for CLI - // workspace configurations. - return parseJson(configBuffer.toString(), JsonParseMode.Json5) as unknown as WorkspaceSchema; + return await readJsonWorkspace(path, { + readFile: async filePath => tree.read(filePath)!.toString() + } as WorkspaceHost); } catch (e) { return null; } diff --git a/src/cdk/schematics/utils/schematic-options.ts b/src/cdk/schematics/utils/schematic-options.ts index d82394febcf0..dcf990376ad3 100644 --- a/src/cdk/schematics/utils/schematic-options.ts +++ b/src/cdk/schematics/utils/schematic-options.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; - +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; +import {isJsonObject, JsonObject} from '@angular-devkit/core'; /** * Returns the default options for the `@schematics/angular:component` schematic which would @@ -16,7 +16,7 @@ import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace' * This is necessary because the Angular CLI only exposes the default values for the "--style", * "--inlineStyle", "--skipTests" and "--inlineTemplate" options to the "component" schematic. */ -export function getDefaultComponentOptions(project: WorkspaceProject) { +export function getDefaultComponentOptions(project: ProjectDefinition) { // Note: Not all options which are available when running "ng new" will be stored in the // workspace config. List of options which will be available in the configuration: // angular/angular-cli/blob/master/packages/schematics/angular/application/index.ts#L109-L131 @@ -42,14 +42,16 @@ export function getDefaultComponentOptions(project: WorkspaceProject) { * by looking at the stored schematic options for `@schematics/angular:component` in the * CLI workspace configuration. */ -function getDefaultComponentOption(project: WorkspaceProject, optionNames: string[], +function getDefaultComponentOption(project: ProjectDefinition, optionNames: string[], fallbackValue: T): T { - for (let optionName of optionNames) { - if (project.schematics && - project.schematics['@schematics/angular:component'] && - project.schematics['@schematics/angular:component'][optionName] != null) { - - return project.schematics['@schematics/angular:component'][optionName]; + const schematicOptions = isJsonObject(project.extensions.schematics || null) ? + project.extensions.schematics as JsonObject : null; + const defaultSchematic = schematicOptions ? + schematicOptions['@schematics/angular:component'] as JsonObject | null : null; + + for (const optionName of optionNames) { + if (defaultSchematic && defaultSchematic[optionName] != null) { + return defaultSchematic[optionName] as unknown as T; } } diff --git a/src/material/schematics/ng-add/fonts/material-fonts.ts b/src/material/schematics/ng-add/fonts/material-fonts.ts index 58df95f9a4a0..11edc80cf700 100644 --- a/src/material/schematics/ng-add/fonts/material-fonts.ts +++ b/src/material/schematics/ng-add/fonts/material-fonts.ts @@ -6,19 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {SchematicsException, Tree} from '@angular-devkit/schematics'; +import {Rule, SchematicsException, Tree} from '@angular-devkit/schematics'; import { appendHtmlElementToHead, getProjectFromWorkspace, getProjectIndexFiles, } from '@angular/cdk/schematics'; -import {getWorkspace} from '@schematics/angular/utility/config'; +import {getWorkspace} from '@schematics/angular/utility/workspace'; import {Schema} from '../schema'; /** Adds the Material Design fonts to the index HTML file. */ -export function addFontsToIndex(options: Schema): (host: Tree) => Tree { - return (host: Tree) => { - const workspace = getWorkspace(host); +export function addFontsToIndex(options: Schema): Rule { + return async (host: Tree) => { + const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const projectIndexFiles = getProjectIndexFiles(project); @@ -36,7 +36,5 @@ export function addFontsToIndex(options: Schema): (host: Tree) => Tree { appendHtmlElementToHead(host, indexFilePath, ``); }); }); - - return host; }; } diff --git a/src/material/schematics/ng-add/index.spec.ts b/src/material/schematics/ng-add/index.spec.ts index d54132ba0cc2..f6281766297a 100644 --- a/src/material/schematics/ng-add/index.spec.ts +++ b/src/material/schematics/ng-add/index.spec.ts @@ -1,5 +1,5 @@ import {normalize} from '@angular-devkit/core'; -import {WorkspaceProject} from '@angular-devkit/core/src/experimental/workspace'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; import {Tree} from '@angular-devkit/schematics'; import {SchematicTestRunner} from '@angular-devkit/schematics/testing'; import { @@ -9,7 +9,7 @@ import { getProjectTargetOptions, } from '@angular/cdk/schematics'; import {createTestApp, createTestLibrary, getFileContent} from '@angular/cdk/schematics/testing'; -import {getWorkspace} from '@schematics/angular/utility/config'; +import {getWorkspace} from '@schematics/angular/utility/workspace'; import {COLLECTION_PATH} from '../index.spec'; import {addPackageToPackageJson} from './package-config'; @@ -35,7 +35,7 @@ describe('ng-add schematic', () => { }); /** Expects the given file to be in the styles of the specified workspace project. */ - function expectProjectStyleFile(project: WorkspaceProject, filePath: string) { + function expectProjectStyleFile(project: ProjectDefinition, filePath: string) { expect(getProjectTargetOptions(project, 'build').styles) .toContain( filePath, `Expected "${filePath}" to be added to the project styles in the workspace.`); @@ -97,7 +97,7 @@ describe('ng-add schematic', () => { it('should add default theme', async () => { const tree = await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); expectProjectStyleFile( @@ -111,7 +111,7 @@ describe('ng-add schematic', () => { const tree = await runner.runSchematicAsync('ng-add-setup-project', {theme: 'custom'}, appTree) .toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const expectedStylesPath = normalize(`/${project.root}/src/styles.scss`); @@ -128,7 +128,7 @@ describe('ng-add schematic', () => { const tree = await runner.runSchematicAsync('ng-add-setup-project', {theme: 'custom'}, appTree) .toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const expectedStylesPath = normalize(`/${project.root}/src/custom-theme.scss`); @@ -138,7 +138,7 @@ describe('ng-add schematic', () => { it('should add font links', async () => { const tree = await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const indexFiles = getProjectIndexFiles(project); @@ -161,7 +161,7 @@ describe('ng-add schematic', () => { it('should add material app styles', async () => { const tree = await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const defaultStylesPath = getProjectStyleFile(project)!; @@ -184,7 +184,7 @@ describe('ng-add schematic', () => { }); it('should not add BrowserAnimationsModule if NoopAnimationsModule is set up', async () => { - const workspace = getWorkspace(appTree); + const workspace = await getWorkspace(appTree); const project = getProjectFromWorkspace(workspace); // Simulate the case where a developer uses `ng-add` on an Angular CLI project which already @@ -214,8 +214,8 @@ describe('ng-add schematic', () => { 'Expected the project app module to import the "NoopAnimationsModule".'); }); - it('should not add NoopAnimationsModule if BrowserAnimationsModule is set up', () => { - const workspace = getWorkspace(appTree); + it('should not add NoopAnimationsModule if BrowserAnimationsModule is set up', async () => { + const workspace = await getWorkspace(appTree); const project = getProjectFromWorkspace(workspace); // Simulate the case where a developer uses `ng-add` on an Angular CLI project which already @@ -233,25 +233,52 @@ describe('ng-add schematic', () => { describe('custom project builders', () => { /** Overwrites a target builder for the workspace in the given tree */ - function overwriteTargetBuilder(tree: Tree, targetName: string, newBuilder: string) { - const workspace = getWorkspace(tree); - const project = getProjectFromWorkspace(workspace); - const targetConfig = project.architect && project.architect[targetName] || - project.targets && project.targets[targetName]; - targetConfig['builder'] = newBuilder; - tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2)); + function overwriteTargetBuilder(tree: Tree, targetName: 'build' | 'test', newBuilder: string) { + const config = { + version: 1, + defaultProject: 'material', + projects: { + material: { + projectType: 'application', + root: 'projects/material', + sourceRoot: 'projects/material/src', + prefix: 'app', + architect: { + build: { + builder: '@angular-devkit/build-angular:browser', + options: { + outputPath: 'dist/material', + index: 'projects/material/src/index.html', + main: 'projects/material/src/main.ts', + styles: ['projects/material/src/styles.css'] + } + }, + test: { + builder: '@angular-devkit/build-angular:karma', + options: { + outputPath: 'dist/material', + index: 'projects/material/src/index.html', + main: 'projects/material/src/main.ts', + styles: ['projects/material/src/styles.css'] + } + } + } + } + } + }; + + config.projects.material.architect[targetName].builder = newBuilder; + tree.overwrite('/angular.json', JSON.stringify(config, null, 2)); } it('should throw an error if the "build" target has been changed', async () => { overwriteTargetBuilder(appTree, 'build', 'thirdparty-builder'); - await expectAsync(runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise()) .toBeRejectedWithError(/not using the default builders.*build/); }); it('should warn if the "test" target has been changed', async () => { overwriteTargetBuilder(appTree, 'test', 'thirdparty-test-builder'); - await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); expect(errorOutput.length).toBe(0); @@ -268,17 +295,29 @@ describe('ng-add schematic', () => { /** Writes a specific style file to the workspace in the given tree */ function writeStyleFileToWorkspace(tree: Tree, stylePath: string) { - const workspace = getWorkspace(tree); - const project = getProjectFromWorkspace(workspace); - const buildOptions = getProjectTargetOptions(project, 'build'); - - if (!buildOptions.styles) { - buildOptions.styles = [stylePath]; - } else { - buildOptions.styles.push(stylePath); - } - - tree.overwrite('/angular.json', JSON.stringify(workspace, null, 2)); + tree.overwrite('/angular.json', JSON.stringify({ + version: 1, + defaultProject: 'material', + projects: { + material: { + projectType: 'application', + root: 'projects/material', + sourceRoot: 'projects/material/src', + prefix: 'app', + architect: { + build: { + builder: '@angular-devkit/build-angular:browser', + options: { + outputPath: 'dist/material', + index: 'projects/material/src/index.html', + main: 'projects/material/src/main.ts', + styles: ['projects/material/src/styles.css', stylePath] + } + } + } + } + } + }, null, 2)); } it('should replace existing prebuilt theme files', async () => { @@ -286,7 +325,7 @@ describe('ng-add schematic', () => { writeStyleFileToWorkspace(appTree, existingThemePath); const tree = await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const styles = getProjectTargetOptions(project, 'build').styles; @@ -300,7 +339,7 @@ describe('ng-add schematic', () => { writeStyleFileToWorkspace(appTree, './projects/material/custom-theme.scss'); const tree = await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const styles = getProjectTargetOptions(project, 'build').styles; @@ -314,7 +353,7 @@ describe('ng-add schematic', () => { writeStyleFileToWorkspace(appTree, defaultPrebuiltThemePath); const tree = await runner.runSchematicAsync('ng-add-setup-project', {}, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const styles = getProjectTargetOptions(project, 'build').styles; @@ -338,7 +377,7 @@ describe('ng-add schematic', () => { const tree = await runner.runSchematicAsync('ng-add-setup-project', { typography: true }, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const indexFiles = getProjectIndexFiles(project); @@ -362,7 +401,7 @@ describe('ng-add schematic', () => { typography: true }, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const indexFiles = getProjectIndexFiles(project); expect(indexFiles.length).toBe(1); @@ -385,7 +424,7 @@ describe('ng-add schematic', () => { typography: true }, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const indexFiles = getProjectIndexFiles(project); expect(indexFiles.length).toBe(1); @@ -408,7 +447,7 @@ describe('ng-add schematic', () => { typography: false }, appTree).toPromise(); - const workspace = getWorkspace(tree); + const workspace = await getWorkspace(tree); const project = getProjectFromWorkspace(workspace); const indexFiles = getProjectIndexFiles(project); expect(indexFiles.length).toBe(1); diff --git a/src/material/schematics/ng-add/setup-project.ts b/src/material/schematics/ng-add/setup-project.ts index 448f1e230947..0c9097593425 100644 --- a/src/material/schematics/ng-add/setup-project.ts +++ b/src/material/schematics/ng-add/setup-project.ts @@ -15,7 +15,8 @@ import { getProjectStyleFile, hasNgModuleImport, } from '@angular/cdk/schematics'; -import {getWorkspace} from '@schematics/angular/utility/config'; +import {getWorkspace} from '@schematics/angular/utility/workspace'; +import {ProjectType} from '@schematics/angular/utility/workspace-models'; import {addFontsToIndex} from './fonts/material-fonts'; import {Schema} from './schema'; import {addThemeToAppStyles, addTypographyClass} from './theming/theming'; @@ -33,11 +34,11 @@ const noopAnimationsModuleName = 'NoopAnimationsModule'; * - Adds Browser Animation to app.module */ export default function(options: Schema): Rule { - return (host: Tree, context: SchematicContext) => { - const workspace = getWorkspace(host); + return async (host: Tree, context: SchematicContext) => { + const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); - if (project.projectType === 'application') { + if (project.extensions.projectType === ProjectType.Application) { return chain([ addAnimationsModule(options), addThemeToAppStyles(options), @@ -51,7 +52,7 @@ export default function(options: Schema): Rule { 'required for consuming Angular Material in your library project.\n\n' + 'If you intended to run the schematic on a different project, pass the `--project` ' + 'option.'); - return host; + return; }; } @@ -61,8 +62,8 @@ export default function(options: Schema): Rule { * components of Angular Material will throw an exception. */ function addAnimationsModule(options: Schema) { - return (host: Tree, context: SchematicContext) => { - const workspace = getWorkspace(host); + return async (host: Tree, context: SchematicContext) => { + const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const appModulePath = getAppModulePath(host, getProjectMainFile(project)); @@ -87,8 +88,6 @@ function addAnimationsModule(options: Schema) { addModuleImportToRootModule(host, noopAnimationsModuleName, '@angular/platform-browser/animations', project); } - - return host; }; } @@ -97,8 +96,8 @@ function addAnimationsModule(options: Schema) { * and reset the default browser body margin. */ function addMaterialAppStyles(options: Schema) { - return (host: Tree, context: SchematicContext) => { - const workspace = getWorkspace(host); + return async (host: Tree, context: SchematicContext) => { + const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const styleFilePath = getProjectStyleFile(project); const logger = context.logger; diff --git a/src/material/schematics/ng-add/theming/theming.ts b/src/material/schematics/ng-add/theming/theming.ts index 37328b452522..4c7499b86f67 100644 --- a/src/material/schematics/ng-add/theming/theming.ts +++ b/src/material/schematics/ng-add/theming/theming.ts @@ -7,8 +7,15 @@ */ import {normalize, logging} from '@angular-devkit/core'; -import {WorkspaceProject, WorkspaceSchema} from '@angular-devkit/core/src/experimental/workspace'; -import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; +import {ProjectDefinition} from '@angular-devkit/core/src/workspace'; +import { + chain, + noop, + Rule, + SchematicContext, + SchematicsException, + Tree, +} from '@angular-devkit/schematics'; import { addBodyClass, defaultTargetBuilders, @@ -18,7 +25,7 @@ import { getProjectIndexFiles, } from '@angular/cdk/schematics'; import {InsertChange} from '@schematics/angular/utility/change'; -import {getWorkspace} from '@schematics/angular/utility/config'; +import {getWorkspace, updateWorkspace} from '@schematics/angular/utility/workspace'; import {join} from 'path'; import {Schema} from '../schema'; import {createCustomTheme} from './create-custom-theme'; @@ -31,25 +38,18 @@ const defaultCustomThemeFilename = 'custom-theme.scss'; /** Add pre-built styles to the main project style file. */ export function addThemeToAppStyles(options: Schema): Rule { - return function(host: Tree, context: SchematicContext): Tree { - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); + return (host: Tree, context: SchematicContext) => { const themeName = options.theme || 'indigo-pink'; - - if (themeName === 'custom') { - insertCustomTheme(project, options.project, host, workspace, context.logger); - } else { - insertPrebuiltTheme(project, host, themeName, workspace, context.logger); - } - - return host; + return themeName === 'custom' ? + insertCustomTheme(options.project, host, context.logger) : + insertPrebuiltTheme(options.project, themeName, context.logger); }; } /** Adds the global typography class to the body element. */ -export function addTypographyClass(options: Schema): (host: Tree) => Tree { - return function(host: Tree): Tree { - const workspace = getWorkspace(host); +export function addTypographyClass(options: Schema): Rule { + return async (host: Tree) => { + const workspace = await getWorkspace(host); const project = getProjectFromWorkspace(workspace, options.project); const projectIndexFiles = getProjectIndexFiles(project); @@ -60,8 +60,6 @@ export function addTypographyClass(options: Schema): (host: Tree) => Tree { if (options.typography) { projectIndexFiles.forEach(path => addBodyClass(host, path, 'mat-typography')); } - - return host; }; } @@ -69,9 +67,10 @@ export function addTypographyClass(options: Schema): (host: Tree) => Tree { * Insert a custom theme to project style file. If no valid style file could be found, a new * Scss file for the custom theme will be created. */ -function insertCustomTheme(project: WorkspaceProject, projectName: string, host: Tree, - workspace: WorkspaceSchema, logger: logging.LoggerApi) { - +async function insertCustomTheme(projectName: string, host: Tree, + logger: logging.LoggerApi): Promise { + const workspace = await getWorkspace(host); + const project = getProjectFromWorkspace(workspace, projectName); const stylesPath = getProjectStyleFile(project, 'scss'); const themeContent = createCustomTheme(projectName); @@ -88,12 +87,11 @@ function insertCustomTheme(project: WorkspaceProject, projectName: string, host: if (host.exists(customThemePath)) { logger.warn(`Cannot create a custom Angular Material theme because ${customThemePath} already exists. Skipping custom theme generation.`); - return; + return noop(); } host.create(customThemePath, themeContent); - addThemeStyleToTarget(project, 'build', host, customThemePath, workspace, logger); - return; + return addThemeStyleToTarget(projectName, 'build', customThemePath, logger); } const insertion = new InsertChange(stylesPath, 0, themeContent); @@ -101,60 +99,63 @@ function insertCustomTheme(project: WorkspaceProject, projectName: string, host: recorder.insertLeft(insertion.pos, insertion.toAdd); host.commitUpdate(recorder); + return noop(); } /** Insert a pre-built theme into the angular.json file. */ -function insertPrebuiltTheme(project: WorkspaceProject, host: Tree, theme: string, - workspace: WorkspaceSchema, logger: logging.LoggerApi) { - +function insertPrebuiltTheme(project: string, theme: string, logger: logging.LoggerApi): Rule { // Path needs to be always relative to the `package.json` or workspace root. - const themePath = `./node_modules/@angular/material/prebuilt-themes/${theme}.css`; + const themePath = `./node_modules/@angular/material/prebuilt-themes/${theme}.css`; - addThemeStyleToTarget(project, 'build', host, themePath, workspace, logger); - addThemeStyleToTarget(project, 'test', host, themePath, workspace, logger); + return chain([ + addThemeStyleToTarget(project, 'build', themePath, logger), + addThemeStyleToTarget(project, 'test', themePath, logger) + ]); } /** Adds a theming style entry to the given project target options. */ -function addThemeStyleToTarget(project: WorkspaceProject, targetName: 'test' | 'build', host: Tree, - assetPath: string, workspace: WorkspaceSchema, - logger: logging.LoggerApi) { - // Do not update the builder options in case the target does not use the default CLI builder. - if (!validateDefaultTargetBuilder(project, targetName, logger)) { - return; - } +function addThemeStyleToTarget(projectName: string, targetName: 'test' | 'build', + assetPath: string, logger: logging.LoggerApi): Rule { + return updateWorkspace(workspace => { + const project = getProjectFromWorkspace(workspace, projectName); - const targetOptions = getProjectTargetOptions(project, targetName); + // Do not update the builder options in case the target does not use the default CLI builder. + if (!validateDefaultTargetBuilder(project, targetName, logger)) { + return; + } - if (!targetOptions.styles) { - targetOptions.styles = [assetPath]; - } else { - const existingStyles = targetOptions.styles.map(s => typeof s === 'string' ? s : s.input); + const targetOptions = getProjectTargetOptions(project, targetName); + const styles = targetOptions.styles as (string | {input: string})[]; - for (let [index, stylePath] of existingStyles.entries()) { - // If the given asset is already specified in the styles, we don't need to do anything. - if (stylePath === assetPath) { - return; + if (!styles) { + targetOptions.styles = [assetPath]; + } else { + const existingStyles = styles.map(s => typeof s === 'string' ? s : s.input); + + for (let [index, stylePath] of existingStyles.entries()) { + // If the given asset is already specified in the styles, we don't need to do anything. + if (stylePath === assetPath) { + return; + } + + // In case a prebuilt theme is already set up, we can safely replace the theme with the new + // theme file. If a custom theme is set up, we are not able to safely replace the custom + // theme because these files can contain custom styles, while prebuilt themes are + // always packaged and considered replaceable. + if (stylePath.includes(defaultCustomThemeFilename)) { + logger.error(`Could not add the selected theme to the CLI project ` + + `configuration because there is already a custom theme file referenced.`); + logger.info(`Please manually add the following style file to your configuration:`); + logger.info(` ${assetPath}`); + return; + } else if (stylePath.includes(prebuiltThemePathSegment)) { + styles.splice(index, 1); + } } - // In case a prebuilt theme is already set up, we can safely replace the theme with the new - // theme file. If a custom theme is set up, we are not able to safely replace the custom - // theme because these files can contain custom styles, while prebuilt themes are - // always packaged and considered replaceable. - if (stylePath.includes(defaultCustomThemeFilename)) { - logger.error(`Could not add the selected theme to the CLI project ` + - `configuration because there is already a custom theme file referenced.`); - logger.info(`Please manually add the following style file to your configuration:`); - logger.info(` ${assetPath}`); - return; - } else if (stylePath.includes(prebuiltThemePathSegment)) { - targetOptions.styles.splice(index, 1); - } + styles.unshift(assetPath); } - - targetOptions.styles.unshift(assetPath); - } - - host.overwrite('angular.json', JSON.stringify(workspace, null, 2)); + }); } /** @@ -162,11 +163,10 @@ function addThemeStyleToTarget(project: WorkspaceProject, targetName: 'test' | ' * provided by the Angular CLI. If the configured builder does not match the default builder, * this function can either throw or just show a warning. */ -function validateDefaultTargetBuilder(project: WorkspaceProject, targetName: 'build' | 'test', +function validateDefaultTargetBuilder(project: ProjectDefinition, targetName: 'build' | 'test', logger: logging.LoggerApi) { const defaultBuilder = defaultTargetBuilders[targetName]; - const targetConfig = project.architect && project.architect[targetName] || - project.targets && project.targets[targetName]; + const targetConfig = project.targets && project.targets.get(targetName); const isDefaultBuilder = targetConfig && targetConfig['builder'] === defaultBuilder; // Because the build setup for the Angular CLI can be customized by developers, we can't know diff --git a/src/material/schematics/ng-generate/address-form/index.ts b/src/material/schematics/ng-generate/address-form/index.ts index d1d8a3e33784..f1aea63d7918 100644 --- a/src/material/schematics/ng-generate/address-form/index.ts +++ b/src/material/schematics/ng-generate/address-form/index.ts @@ -33,14 +33,13 @@ export default function(options: Schema): Rule { * Adds the required modules to the relative module. */ function addFormModulesToModule(options: Schema) { - return (host: Tree) => { - const modulePath = findModuleFromOptions(host, options)!; + return async (host: Tree) => { + const modulePath = (await findModuleFromOptions(host, options))!; addModuleImportToModule(host, modulePath, 'MatInputModule', '@angular/material/input'); addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material/button'); addModuleImportToModule(host, modulePath, 'MatSelectModule', '@angular/material/select'); addModuleImportToModule(host, modulePath, 'MatRadioModule', '@angular/material/radio'); addModuleImportToModule(host, modulePath, 'MatCardModule', '@angular/material/card'); addModuleImportToModule(host, modulePath, 'ReactiveFormsModule', '@angular/forms'); - return host; }; } diff --git a/src/material/schematics/ng-generate/dashboard/index.ts b/src/material/schematics/ng-generate/dashboard/index.ts index 5bef0849cfbc..8b8587238cbe 100644 --- a/src/material/schematics/ng-generate/dashboard/index.ts +++ b/src/material/schematics/ng-generate/dashboard/index.ts @@ -33,14 +33,13 @@ export default function(options: Schema): Rule { * Adds the required modules to the relative module. */ function addNavModulesToModule(options: Schema) { - return (host: Tree) => { - const modulePath = findModuleFromOptions(host, options)!; + return async (host: Tree) => { + const modulePath = (await findModuleFromOptions(host, options))!; addModuleImportToModule(host, modulePath, 'MatGridListModule', '@angular/material/grid-list'); addModuleImportToModule(host, modulePath, 'MatCardModule', '@angular/material/card'); addModuleImportToModule(host, modulePath, 'MatMenuModule', '@angular/material/menu'); addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material/icon'); addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material/button'); addModuleImportToModule(host, modulePath, 'LayoutModule', '@angular/cdk/layout'); - return host; }; } diff --git a/src/material/schematics/ng-generate/navigation/index.ts b/src/material/schematics/ng-generate/navigation/index.ts index 1e1dcb462431..d30f0a0d3557 100644 --- a/src/material/schematics/ng-generate/navigation/index.ts +++ b/src/material/schematics/ng-generate/navigation/index.ts @@ -33,14 +33,13 @@ export default function(options: Schema): Rule { * Adds the required modules to the relative module. */ function addNavModulesToModule(options: Schema) { - return (host: Tree) => { - const modulePath = findModuleFromOptions(host, options)!; + return async (host: Tree) => { + const modulePath = (await findModuleFromOptions(host, options))!; addModuleImportToModule(host, modulePath, 'LayoutModule', '@angular/cdk/layout'); addModuleImportToModule(host, modulePath, 'MatToolbarModule', '@angular/material/toolbar'); addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material/button'); addModuleImportToModule(host, modulePath, 'MatSidenavModule', '@angular/material/sidenav'); addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material/icon'); addModuleImportToModule(host, modulePath, 'MatListModule', '@angular/material/list'); - return host; }; } diff --git a/src/material/schematics/ng-generate/table/index.ts b/src/material/schematics/ng-generate/table/index.ts index b9c5657f6a0d..76d70943c257 100644 --- a/src/material/schematics/ng-generate/table/index.ts +++ b/src/material/schematics/ng-generate/table/index.ts @@ -33,11 +33,10 @@ export default function(options: Schema): Rule { * Adds the required modules to the relative module. */ function addTableModulesToModule(options: Schema) { - return (host: Tree) => { - const modulePath = findModuleFromOptions(host, options)!; + return async (host: Tree) => { + const modulePath = (await findModuleFromOptions(host, options))!; addModuleImportToModule(host, modulePath, 'MatTableModule', '@angular/material/table'); addModuleImportToModule(host, modulePath, 'MatPaginatorModule', '@angular/material/paginator'); addModuleImportToModule(host, modulePath, 'MatSortModule', '@angular/material/sort'); - return host; }; } diff --git a/src/material/schematics/ng-generate/tree/index.ts b/src/material/schematics/ng-generate/tree/index.ts index 1c0e474f9424..4f44395210dc 100644 --- a/src/material/schematics/ng-generate/tree/index.ts +++ b/src/material/schematics/ng-generate/tree/index.ts @@ -33,11 +33,10 @@ export default function(options: Schema): Rule { * Adds the required modules to the relative module. */ function addTreeModulesToModule(options: Schema) { - return (host: Tree) => { - const modulePath = findModuleFromOptions(host, options)!; + return async (host: Tree) => { + const modulePath = (await findModuleFromOptions(host, options))!; addModuleImportToModule(host, modulePath, 'MatTreeModule', '@angular/material/tree'); addModuleImportToModule(host, modulePath, 'MatIconModule', '@angular/material/icon'); addModuleImportToModule(host, modulePath, 'MatButtonModule', '@angular/material/button'); - return host; }; }