Skip to content

feat(schematics): compatibility with Angular CLI 6.2.0 #13078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ filegroup(
"*.d.ts",
]] + [
"node_modules/http-server/**",
# Reference all files of the "@schematics/angular" package because the schematic
# tests depend on the template files of the angular schematics.
"node_modules/@schematics/angular/**",
]),
)

Expand Down
39 changes: 16 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/core": "0.7.4",
"@angular-devkit/schematics": "0.7.4",
"@angular-devkit/core": "^0.9.0-beta.2",
"@angular-devkit/schematics": "^0.9.0-beta.2",
"@angular/bazel": "7.0.0-beta.4",
"@angular/compiler-cli": "7.0.0-beta.4",
"@angular/http": "7.0.0-beta.4",
Expand All @@ -56,7 +56,7 @@
"@bazel/ibazel": "0.3.1",
"@google-cloud/storage": "^1.1.1",
"@octokit/rest": "^15.9.4",
"@schematics/angular": "0.7.4",
"@schematics/angular": "^0.9.0-beta.2",
"@types/chalk": "^0.4.31",
"@types/fs-extra": "^4.0.3",
"@types/glob": "^5.0.33",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/schematics/install/fonts/project-index-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

import {SchematicsException} from '@angular-devkit/schematics';
import {WorkspaceProject} from '@schematics/angular/utility/config';
import {getArchitectOptions} from '../../utils/architect-options';
import {getProjectTargetOptions} from '../../utils/project-targets';

/** Looks for the index HTML file in the given project and returns its path. */
export function getIndexHtmlPath(project: WorkspaceProject): string {
const buildOptions = getArchitectOptions(project, 'build');
const buildOptions = getProjectTargetOptions(project, 'build');

if (!buildOptions.index) {
throw new SchematicsException('No project "index.html" file could be found.');
Expand Down
7 changes: 2 additions & 5 deletions src/lib/schematics/install/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Tree} from '@angular-devkit/schematics';
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
import {getProjectTargetOptions} from '@angular/material/schematics/utils/project-targets';
import {getProjectStyleFile} from '../utils/project-style-file';
import {getIndexHtmlPath} from './fonts/project-index-html';
import {getProjectFromWorkspace} from '../utils/get-project';
Expand All @@ -19,11 +20,7 @@ describe('material-install-schematic', () => {

/** Expects the given file to be in the styles of the specified workspace project. */
function expectProjectStyleFile(project: WorkspaceProject, filePath: string) {
const architect = project.architect!;

expect(architect!['build']).toBeTruthy();
expect(architect!['build']['options']).toBeTruthy();
expect(architect!['build']['options']['styles']).toContain(filePath,
expect(getProjectTargetOptions(project, 'build').styles).toContain(filePath,
`Expected "${filePath}" to be added to the project styles in the workspace.`);
}

Expand Down
55 changes: 28 additions & 27 deletions src/lib/schematics/install/theming/theming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {getWorkspace, WorkspaceProject, WorkspaceSchema} from '@schematics/angul
import {join} from 'path';
import {getProjectFromWorkspace} from '../../utils/get-project';
import {getProjectStyleFile} from '../../utils/project-style-file';
import {getProjectTargetOptions} from '../../utils/project-targets';
import {Schema} from '../schema';
import {createCustomTheme} from './custom-theme';

Expand Down Expand Up @@ -60,9 +61,7 @@ function insertCustomTheme(project: WorkspaceProject, projectName: string, host:

host.create(customThemePath, themeContent);

// Architect is always defined because we initially asserted if the default builder
// configuration is set up or not.
return addStyleToTarget(project.architect!['build'], host, customThemePath, workspace);
return addStyleToTarget(project, 'build', host, customThemePath, workspace);
}

const insertion = new InsertChange(stylesPath, 0, themeContent);
Expand All @@ -79,27 +78,24 @@ function insertPrebuiltTheme(project: WorkspaceProject, host: Tree, theme: strin
// Path needs to be always relative to the `package.json` or workspace root.
const themePath = `./node_modules/@angular/material/prebuilt-themes/${theme}.css`;

// Architect is always defined because we initially asserted if the default builder
// configuration is set up or not.
addStyleToTarget(project.architect!['build'], host, themePath, workspace);
addStyleToTarget(project.architect!['test'], host, themePath, workspace);
addStyleToTarget(project, 'build', host, themePath, workspace);
addStyleToTarget(project, 'test', host, themePath, workspace);
}

/** Adds a style entry to the given target. */
function addStyleToTarget(target: any, host: Tree, asset: string, workspace: WorkspaceSchema) {
// We can't assume that any of these properties are defined, so safely add them as we go
// if necessary.
if (!target.options) {
target.options = {styles: [asset]};
} else if (!target.options.styles) {
target.options.styles = [asset];
/** Adds a style entry to the given project target. */
function addStyleToTarget(project: WorkspaceProject, targetName: string, host: Tree,
assetPath: string, workspace: WorkspaceSchema) {
const targetOptions = getProjectTargetOptions(project, targetName);

if (!targetOptions.styles) {
targetOptions.styles = [assetPath];
} else {
const existingStyles = target.options.styles.map(s => typeof s === 'string' ? s : s.input);
const hasGivenTheme = existingStyles.find(s => s.includes(asset));
const existingStyles = targetOptions.styles.map(s => typeof s === 'string' ? s : s.input);
const hasGivenTheme = existingStyles.find(s => s.includes(assetPath));
const hasOtherTheme = existingStyles.find(s => s.includes('material/prebuilt'));

if (!hasGivenTheme && !hasOtherTheme) {
target.options.styles.unshift(asset);
targetOptions.styles.unshift(assetPath);
}
}

Expand All @@ -108,18 +104,23 @@ function addStyleToTarget(target: any, host: Tree, asset: string, workspace: Wor

/** Throws if the project is not using the default Angular devkit builders. */
function assertDefaultBuildersConfigured(project: WorkspaceProject) {
const defaultBuilder = '@angular-devkit/build-angular:browser';
const defaultTestBuilder = '@angular-devkit/build-angular:karma';
checkProjectTargetBuilder(project, 'build', '@angular-devkit/build-angular:browser');
checkProjectTargetBuilder(project, 'test', '@angular-devkit/build-angular:karma');
}

/**
* Checks if the specified project target is configured with the default builders which are
* provided by the Angular CLI.
*/
function checkProjectTargetBuilder(project: WorkspaceProject, targetName: string,
defaultBuilder: string) {

const hasDefaultBuilders = project.architect &&
project.architect['build'] &&
project.architect['build']['builder'] === defaultBuilder &&
project.architect['test'] &&
project.architect['test']['builder'] === defaultTestBuilder;
const targetConfig = project.architect && project.architect[targetName] ||
project.targets && project.targets[targetName];

if (!hasDefaultBuilders) {
if (!targetConfig || targetConfig['builder'] !== defaultBuilder) {
throw new SchematicsException(
'Your project is not using the default builders for build and test. The Angular Material ' +
`Your project is not using the default builders for "${targetName}". The Angular Material ` +
'schematics can only be used if the original builders from the Angular CLI are configured.');
}
}
47 changes: 47 additions & 0 deletions src/lib/schematics/update/project-tsconfig-paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {Tree} from '@angular-devkit/schematics';
import {getWorkspace} from '@schematics/angular/utility/config';

/**
* Gets all tsconfig paths from a CLI project by reading the workspace configuration
* and looking for common tsconfig locations.
*/
export function getProjectTsConfigPaths(tree: Tree): string[] {
// Start with some tsconfig paths that are generally used within CLI projects.
const tsconfigPaths = new Set<string>([
'./tsconfig.json',
'./src/tsconfig.json',
'./src/tsconfig.app.json',
]);

// Add any tsconfig directly referenced in a build or test task of the angular.json workspace.
const workspace = getWorkspace(tree);

for (const project of Object.values(workspace.projects)) {
['build', 'test'].forEach(targetName => {
if (project.targets &&
project.targets[targetName] &&
project.targets[targetName].options &&
project.targets[targetName].options.tsConfig) {
tsconfigPaths.add(project.targets[targetName].options.tsConfig);
}

if (project.architect &&
project.architect[targetName] &&
project.architect[targetName].options &&
project.architect[targetName].options.tsConfig) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that we could extract this into a more generic function in order to reduce duplication, but I prefer being explicit here.

tsconfigPaths.add(project.architect[targetName].options.tsConfig);
}
});
}

// Filter out tsconfig files that don't exist in the CLI project.
return Array.from(tsconfigPaths).filter(p => tree.exists(p));
}
45 changes: 4 additions & 41 deletions src/lib/schematics/update/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@

import {Rule, SchematicContext, TaskId, Tree} from '@angular-devkit/schematics';
import {RunSchematicTask, TslintFixTask} from '@angular-devkit/schematics/tasks';
import {getWorkspace} from '@schematics/angular/utility/config';
import {TargetVersion} from './index';
import {getProjectTsConfigPaths} from './project-tsconfig-paths';
import {createTslintConfig} from './tslint-update';

/** Entry point for `ng update` from Angular CLI. */
export function createUpdateRule(targetVersion: TargetVersion): Rule {
return (tree: Tree, context: SchematicContext) => {

const allTsConfigPaths = getTsConfigPaths(tree);
const projectTsConfigPaths = getProjectTsConfigPaths(tree);
const tslintFixTasks: TaskId[] = [];

if (!allTsConfigPaths.length) {
if (!projectTsConfigPaths.length) {
throw new Error('Could not find any tsconfig file. Please submit an issue on the Angular ' +
'Material repository that includes the name of your TypeScript configuration.');
}

const tslintConfig = createTslintConfig(targetVersion);

for (const tsconfig of allTsConfigPaths) {
for (const tsconfig of projectTsConfigPaths) {
// Run the update tslint rules.
tslintFixTasks.push(context.addTask(new TslintFixTask(tslintConfig, {
silent: false,
Expand All @@ -39,40 +39,3 @@ export function createUpdateRule(targetVersion: TargetVersion): Rule {
context.addTask(new RunSchematicTask('ng-post-update', {}), tslintFixTasks);
};
}

/**
* Gets all tsconfig paths from a CLI project by reading the workspace configuration
* and looking for common tsconfig locations.
*/
function getTsConfigPaths(tree: Tree): string[] {
// Start with some tsconfig paths that are generally used.
const tsconfigPaths = [
'./tsconfig.json',
'./src/tsconfig.json',
'./src/tsconfig.app.json',
];

// Add any tsconfig directly referenced in a build or test task of the angular.json workspace.
const workspace = getWorkspace(tree);

for (const project of Object.values(workspace.projects)) {
if (project && project.architect) {
for (const taskName of ['build', 'test']) {
const task = project.architect[taskName];
if (task && task.options && task.options.tsConfig) {
const tsConfigOption = task.options.tsConfig;
if (typeof tsConfigOption === 'string') {
tsconfigPaths.push(tsConfigOption);
} else if (Array.isArray(tsConfigOption)) {
tsconfigPaths.push(...tsConfigOption);
}
}
}
}
}

// Filter out tsconfig files that don't exist and remove any duplicates.
return tsconfigPaths
.filter(p => tree.exists(p))
.filter((value, index, self) => self.indexOf(value) === index);
}
21 changes: 0 additions & 21 deletions src/lib/schematics/utils/architect-options.ts

This file was deleted.

5 changes: 1 addition & 4 deletions src/lib/schematics/utils/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@ export function addModuleImportToModule(host: Tree, modulePath: string, moduleNa
throw new SchematicsException(`Module not found: ${modulePath}`);
}

// TODO(devversion): Cast to any because the Bazel typescript rules seem to incorrectly resolve
// the the required TypeScript version for the @schematics/angular utility functions. Meaning
// that is a type signature mismatch at compilation which is not valid.
const changes = addImportToModule(moduleSource as any, modulePath, moduleName, src);
const changes = addImportToModule(moduleSource, modulePath, moduleName, src);
const recorder = host.beginUpdate(modulePath);

changes.forEach((change) => {
Expand Down
Loading