feat(core): add postinstall ngcc migration (#32999)

PR Close #32999
This commit is contained in:
Filipe Silva 2019-10-04 14:20:03 +01:00 committed by Miško Hevery
parent ca94d2b7f0
commit 30d25f67af
7 changed files with 182 additions and 0 deletions

View File

@ -13,6 +13,7 @@ npm_package(
"//packages/core/schematics/migrations/dynamic-queries", "//packages/core/schematics/migrations/dynamic-queries",
"//packages/core/schematics/migrations/missing-injectable", "//packages/core/schematics/migrations/missing-injectable",
"//packages/core/schematics/migrations/move-document", "//packages/core/schematics/migrations/move-document",
"//packages/core/schematics/migrations/postinstall-ngcc",
"//packages/core/schematics/migrations/renderer-to-renderer2", "//packages/core/schematics/migrations/renderer-to-renderer2",
"//packages/core/schematics/migrations/static-queries", "//packages/core/schematics/migrations/static-queries",
"//packages/core/schematics/migrations/template-var-assignment", "//packages/core/schematics/migrations/template-var-assignment",

View File

@ -39,6 +39,11 @@
"version": "9-beta", "version": "9-beta",
"description": "Removes the `static` flag from dynamic queries.", "description": "Removes the `static` flag from dynamic queries.",
"factory": "./migrations/dynamic-queries/index" "factory": "./migrations/dynamic-queries/index"
},
"migration-v9-postinstall-ngcc": {
"version": "9-beta",
"description": "Adds an ngcc call as a postinstall hook in package.json",
"factory": "./migrations/postinstall-ngcc/index"
} }
} }
} }

View File

@ -0,0 +1,16 @@
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "postinstall-ngcc",
srcs = glob(["**/*.ts"]),
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = [
"//packages/core/schematics:__pkg__",
"//packages/core/schematics/test:__pkg__",
],
deps = [
"@npm//@angular-devkit/core",
"@npm//@angular-devkit/schematics",
"@npm//@schematics/angular",
],
)

View File

@ -0,0 +1,22 @@
## Postinstall ngcc migration
Automatically adds a postinstall script to `package.json` to run `ngcc`.
If a postinstall script is already there and does not call `ngcc`, the call will be prepended.
#### Before
```json
{
"scripts": {
"postinstall": "do-something"
}
}
```
#### After
```json
{
"scripts": {
"postinstall": "ngcc ... && do-something"
}
}
```

View File

@ -0,0 +1,79 @@
/**
* @license
* Copyright Google Inc. 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 {JsonParseMode, parseJsonAst} from '@angular-devkit/core';
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
import {appendPropertyInAstObject, findPropertyInAstObject, insertPropertyInAstObjectInOrder} from '@schematics/angular/utility/json-utils';
/**
* Runs the ngcc postinstall migration for the current CLI workspace.
*/
export default function(): Rule {
return (tree: Tree, context: SchematicContext) => {
addPackageJsonScript(
tree, 'postinstall',
'ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points');
context.addTask(new NodePackageInstallTask());
};
}
function addPackageJsonScript(tree: Tree, scriptName: string, script: string): void {
const pkgJsonPath = '/package.json';
// Read package.json and turn it into an AST.
const buffer = tree.read(pkgJsonPath);
if (buffer === null) {
throw new SchematicsException('Could not read package.json.');
}
const content = buffer.toString();
const packageJsonAst = parseJsonAst(content, JsonParseMode.Strict);
if (packageJsonAst.kind != 'object') {
throw new SchematicsException('Invalid package.json. Was expecting an object.');
}
// Begin recording changes.
const recorder = tree.beginUpdate(pkgJsonPath);
const scriptsNode = findPropertyInAstObject(packageJsonAst, 'scripts');
if (!scriptsNode) {
// Haven't found the scripts key, add it to the root of the package.json.
appendPropertyInAstObject(
recorder, packageJsonAst, 'scripts', {
[scriptName]: script,
},
2);
} else if (scriptsNode.kind === 'object') {
// Check if the script is already there.
const scriptNode = findPropertyInAstObject(scriptsNode, scriptName);
if (!scriptNode) {
// Script not found, add it.
insertPropertyInAstObjectInOrder(recorder, scriptsNode, scriptName, script, 4);
} else {
// Script found, prepend the new script with &&.
const currentScript = scriptNode.value;
if (typeof currentScript == 'string') {
// Only add script if there's no ngcc call there already.
if (!currentScript.includes('ngcc')) {
const {start, end} = scriptNode;
recorder.remove(start.offset, end.offset - start.offset);
recorder.insertRight(start.offset, JSON.stringify(`${script} && ${currentScript}`));
}
} else {
throw new SchematicsException(
'Invalid postinstall script in package.json. Was expecting a string.');
}
}
}
// Write the changes.
tree.commitUpdate(recorder);
}

View File

@ -11,6 +11,7 @@ ts_library(
"//packages/core/schematics/migrations/dynamic-queries", "//packages/core/schematics/migrations/dynamic-queries",
"//packages/core/schematics/migrations/missing-injectable", "//packages/core/schematics/migrations/missing-injectable",
"//packages/core/schematics/migrations/move-document", "//packages/core/schematics/migrations/move-document",
"//packages/core/schematics/migrations/postinstall-ngcc",
"//packages/core/schematics/migrations/renderer-to-renderer2", "//packages/core/schematics/migrations/renderer-to-renderer2",
"//packages/core/schematics/migrations/static-queries", "//packages/core/schematics/migrations/static-queries",
"//packages/core/schematics/migrations/template-var-assignment", "//packages/core/schematics/migrations/template-var-assignment",

View File

@ -0,0 +1,58 @@
/**
* @license
* Copyright Google Inc. 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 {EmptyTree} from '@angular-devkit/schematics';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
describe('postinstall ngcc migration', () => {
let runner: SchematicTestRunner;
let tree: UnitTestTree;
const pkgJsonPath = '/package.json';
const ngccPostinstall =
`"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points"`;
beforeEach(() => {
runner = new SchematicTestRunner('test', require.resolve('../migrations.json'));
tree = new UnitTestTree(new EmptyTree());
});
it(`should add postinstall if scripts object is missing`, async() => {
tree.create(pkgJsonPath, JSON.stringify({}, null, 2));
await runMigration();
expect(tree.readContent(pkgJsonPath)).toContain(ngccPostinstall);
});
it(`should add postinstall if the script is missing`, async() => {
tree.create(pkgJsonPath, JSON.stringify({scripts: {}}, null, 2));
await runMigration();
expect(tree.readContent(pkgJsonPath)).toContain(ngccPostinstall);
});
it(`should prepend to postinstall if script already exists`, async() => {
tree.create(pkgJsonPath, JSON.stringify({scripts: {postinstall: 'do-something'}}, null, 2));
await runMigration();
expect(tree.readContent(pkgJsonPath))
.toContain(
`"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points && do-something"`);
});
it(`should not prepend to postinstall if script contains ngcc`, async() => {
tree.create(pkgJsonPath, JSON.stringify({scripts: {postinstall: 'ngcc --something'}}, null, 2));
await runMigration();
expect(tree.readContent(pkgJsonPath)).toContain(`"postinstall": "ngcc --something"`);
expect(tree.readContent(pkgJsonPath)).not.toContain(ngccPostinstall);
expect(tree.readContent(pkgJsonPath))
.not.toContain(
`ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points`);
});
function runMigration() {
return runner.runSchematicAsync('migration-v9-postinstall-ngcc', {}, tree).toPromise();
}
});