fix(bazel): don't rely on @angular/core being as a depedency to install @angular/bazel (#34181)

With this change we fix the logic to detect if a package is installed, removing a package and add a package by using the CLI schematic helpers.

Also we save `@angular/bazel` package directly as a `devDependency` when doing `ng-add`.

Closes #34164

PR Close #34181
This commit is contained in:
Alan Agius 2019-12-02 14:35:37 +01:00 committed by Miško Hevery
parent 755d2d572f
commit 0cd8431698
3 changed files with 54 additions and 105 deletions

View File

@ -51,6 +51,9 @@
}, },
"builders": "./src/builders/builders.json", "builders": "./src/builders/builders.json",
"schematics": "./src/schematics/collection.json", "schematics": "./src/schematics/collection.json",
"ng-add": {
"save": "devDependencies"
},
"ng-update": { "ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP" "packageGroup": "NG_UPDATE_PACKAGE_GROUP"
}, },

View File

@ -12,15 +12,17 @@ import {JsonAstObject, parseJsonAst} from '@angular-devkit/core';
import {Rule, SchematicContext, SchematicsException, Tree, apply, applyTemplates, chain, mergeWith, url} from '@angular-devkit/schematics'; import {Rule, SchematicContext, SchematicsException, Tree, apply, applyTemplates, chain, mergeWith, url} from '@angular-devkit/schematics';
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
import {getWorkspace, getWorkspacePath} from '@schematics/angular/utility/config'; import {getWorkspace, getWorkspacePath} from '@schematics/angular/utility/config';
import {NodeDependencyType, addPackageJsonDependency, getPackageJsonDependency, removePackageJsonDependency} from '@schematics/angular/utility/dependencies';
import {findPropertyInAstObject, insertPropertyInAstObjectInOrder} from '@schematics/angular/utility/json-utils'; import {findPropertyInAstObject, insertPropertyInAstObjectInOrder} from '@schematics/angular/utility/json-utils';
import {validateProjectName} from '@schematics/angular/utility/validation'; import {validateProjectName} from '@schematics/angular/utility/validation';
import {isJsonAstObject, removeKeyValueInAstObject, replacePropertyInAstObject} from '../utility/json-utils'; import {isJsonAstObject, replacePropertyInAstObject} from '../utility/json-utils';
import {findE2eArchitect} from '../utility/workspace-utils'; import {findE2eArchitect} from '../utility/workspace-utils';
import {Schema} from './schema'; import {Schema} from './schema';
/** /**
* Packages that build under Bazel require additional dev dependencies. This * Packages that build under Bazel require additional dev dependencies. This
* function adds those dependencies to "devDependencies" section in * function adds those dependencies to "devDependencies" section in
@ -28,57 +30,41 @@ import {Schema} from './schema';
*/ */
function addDevDependenciesToPackageJson(options: Schema) { function addDevDependenciesToPackageJson(options: Schema) {
return (host: Tree) => { return (host: Tree) => {
const packageJson = 'package.json'; const angularCore = getPackageJsonDependency(host, '@angular/core');
if (!host.exists(packageJson)) { if (!angularCore) {
throw new Error(`Could not find ${packageJson}`);
}
const packageJsonContent = host.read(packageJson);
if (!packageJsonContent) {
throw new Error('Failed to read package.json content');
}
const jsonAst = parseJsonAst(packageJsonContent.toString()) as JsonAstObject;
const deps = findPropertyInAstObject(jsonAst, 'dependencies') as JsonAstObject;
const devDeps = findPropertyInAstObject(jsonAst, 'devDependencies') as JsonAstObject;
const angularCoreNode = findPropertyInAstObject(deps, '@angular/core');
if (!angularCoreNode) {
throw new Error('@angular/core dependency not found in package.json'); throw new Error('@angular/core dependency not found in package.json');
} }
const angularCoreVersion = angularCoreNode.value as string;
const devDependencies: {[k: string]: string} = { // TODO: use a Record<string, string> when the tsc lib setting allows us
'@angular/bazel': angularCoreVersion, const devDependencies: [string, string][] = [
'@bazel/bazel': '1.1.0', ['@angular/bazel', angularCore.version],
'@bazel/ibazel': '^0.10.2', ['@bazel/bazel', '1.1.0'],
'@bazel/karma': '0.40.0', ['@bazel/ibazel', '^0.10.2'],
'@bazel/protractor': '0.40.0', ['@bazel/karma', '0.40.0'],
'@bazel/rollup': '0.40.0', ['@bazel/protractor', '0.40.0'],
'@bazel/terser': '0.40.0', ['@bazel/rollup', '0.40.0'],
'@bazel/typescript': '0.40.0', ['@bazel/terser', '0.40.0'],
'history-server': '^1.3.1', ['@bazel/typescript', '0.40.0'],
'rollup': '^1.25.2', ['history-server', '^1.3.1'],
'rollup-plugin-commonjs': '^10.1.0', ['rollup', '^1.25.2'],
'rollup-plugin-node-resolve': '^5.2.0', ['rollup-plugin-commonjs', '^10.1.0'],
'terser': '^4.3.9', ['rollup-plugin-node-resolve', '^5.2.0'],
}; ['terser', '^4.3.9'],
];
const recorder = host.beginUpdate(packageJson); for (const [name, version] of devDependencies) {
for (const packageName of Object.keys(devDependencies)) { const dep = getPackageJsonDependency(host, name);
const existingDep = findPropertyInAstObject(deps, packageName); if (dep && dep.type !== NodeDependencyType.Dev) {
if (existingDep) { removePackageJsonDependency(host, name);
const content = packageJsonContent.toString();
removeKeyValueInAstObject(recorder, content, deps, packageName);
}
const version = devDependencies[packageName];
const indent = 4;
if (findPropertyInAstObject(devDeps, packageName)) {
replacePropertyInAstObject(recorder, devDeps, packageName, version, indent);
} else {
insertPropertyInAstObjectInOrder(recorder, devDeps, packageName, version, indent);
} }
addPackageJsonDependency(host, {
name,
version,
type: NodeDependencyType.Dev,
overwrite: true,
});
} }
host.commitUpdate(recorder);
return host;
}; };
} }
@ -88,38 +74,13 @@ function addDevDependenciesToPackageJson(options: Schema) {
*/ */
function removeObsoleteDependenciesFromPackageJson(options: Schema) { function removeObsoleteDependenciesFromPackageJson(options: Schema) {
return (host: Tree) => { return (host: Tree) => {
const packageJson = 'package.json';
if (!host.exists(packageJson)) {
throw new Error(`Could not find ${packageJson}`);
}
const buffer = host.read(packageJson);
if (!buffer) {
throw new Error('Failed to read package.json content');
}
const content = buffer.toString();
const jsonAst = parseJsonAst(content) as JsonAstObject;
const deps = findPropertyInAstObject(jsonAst, 'dependencies') as JsonAstObject;
const devDeps = findPropertyInAstObject(jsonAst, 'devDependencies') as JsonAstObject;
const depsToRemove = [ const depsToRemove = [
'@angular-devkit/build-angular', '@angular-devkit/build-angular',
]; ];
const recorder = host.beginUpdate(packageJson);
for (const packageName of depsToRemove) { for (const packageName of depsToRemove) {
const depNode = findPropertyInAstObject(deps, packageName); removePackageJsonDependency(host, packageName);
if (depNode) {
removeKeyValueInAstObject(recorder, content, deps, packageName);
}
const devDepNode = findPropertyInAstObject(devDeps, packageName);
if (devDepNode) {
removeKeyValueInAstObject(recorder, content, devDeps, packageName);
}
} }
host.commitUpdate(recorder);
return host;
}; };
} }
@ -166,7 +127,7 @@ function updateGitignore() {
* Change the architect in angular.json to use Bazel builder. * Change the architect in angular.json to use Bazel builder.
*/ */
function updateAngularJsonToUseBazelBuilder(options: Schema): Rule { function updateAngularJsonToUseBazelBuilder(options: Schema): Rule {
return (host: Tree, context: SchematicContext) => { return (host: Tree) => {
const name = options.name !; const name = options.name !;
const workspacePath = getWorkspacePath(host); const workspacePath = getWorkspacePath(host);
if (!workspacePath) { if (!workspacePath) {
@ -277,39 +238,25 @@ function backupAngularJson(): Rule {
*/ */
function upgradeRxjs() { function upgradeRxjs() {
return (host: Tree, context: SchematicContext) => { return (host: Tree, context: SchematicContext) => {
const packageJson = 'package.json'; const rxjsNode = getPackageJsonDependency(host, 'rxjs');
if (!host.exists(packageJson)) { if (!rxjsNode) {
throw new Error(`Could not find ${packageJson}`); throw new Error(`Failed to find rxjs dependency.`);
} }
const content = host.read(packageJson);
if (!content) { const match = rxjsNode.version.match(/(\d)+\.(\d)+.(\d)+$/);
throw new Error('Failed to read package.json content');
}
const jsonAst = parseJsonAst(content.toString());
if (!isJsonAstObject(jsonAst)) {
throw new Error(`Failed to parse JSON for ${packageJson}`);
}
const deps = findPropertyInAstObject(jsonAst, 'dependencies');
if (!isJsonAstObject(deps)) {
throw new Error(`Failed to find dependencies in ${packageJson}`);
}
const rxjs = findPropertyInAstObject(deps, 'rxjs');
if (!rxjs) {
throw new Error(`Failed to find rxjs in dependencies of ${packageJson}`);
}
const value = rxjs.value as string; // value can be version or range
const match = value.match(/(\d)+\.(\d)+.(\d)+$/);
if (match) { if (match) {
const [_, major, minor] = match; const [_, major, minor] = match;
if (major < '6' || (major === '6' && minor < '4')) { if (major < '6' || (major === '6' && minor < '5')) {
const recorder = host.beginUpdate(packageJson); addPackageJsonDependency(host, {
replacePropertyInAstObject(recorder, deps, 'rxjs', '~6.4.0'); ...rxjsNode,
host.commitUpdate(recorder); version: '~6.5.3',
overwrite: true,
});
} }
} else { } else {
context.logger.info( context.logger.info(
'Could not determine version of rxjs. \n' + 'Could not determine version of rxjs. \n' +
'Please make sure that version is at least 6.4.0.'); 'Please make sure that version is at least 6.5.3.');
} }
return host; return host;
}; };

View File

@ -73,7 +73,7 @@ describe('ng-add schematic', () => {
message = e.message; message = e.message;
} }
expect(message).toBe('Could not find package.json'); expect(message).toBe('Could not read package.json.');
}); });
it('throws if angular.json is not found', async() => { it('throws if angular.json is not found', async() => {
@ -104,7 +104,6 @@ describe('ng-add schematic', () => {
expect(Object.keys(json)).toContain('devDependencies'); expect(Object.keys(json)).toContain('devDependencies');
expect(Object.keys(json.dependencies)).toContain(core); expect(Object.keys(json.dependencies)).toContain(core);
expect(Object.keys(json.devDependencies)).toContain(bazel); expect(Object.keys(json.devDependencies)).toContain(bazel);
expect(json.dependencies[core]).toBe(json.devDependencies[bazel]);
}); });
it('should add @bazel/* dev dependencies', async() => { it('should add @bazel/* dev dependencies', async() => {
@ -273,9 +272,9 @@ describe('ng-add schematic', () => {
['~6.3.3', true], ['~6.3.3', true],
['^6.3.3', true], ['^6.3.3', true],
['~6.3.11', true], ['~6.3.11', true],
['6.4.0', false], ['6.4.0', true],
['~6.4.0', false], ['~6.4.0', true],
['~6.4.1', false], ['~6.4.1', true],
['6.5.0', false], ['6.5.0', false],
['~6.5.0', false], ['~6.5.0', false],
['^6.5.0', false], ['^6.5.0', false],
@ -298,7 +297,7 @@ describe('ng-add schematic', () => {
const content = host.readContent('/package.json'); const content = host.readContent('/package.json');
const json = JSON.parse(content); const json = JSON.parse(content);
if (upgrade) { if (upgrade) {
expect(json.dependencies.rxjs).toBe('~6.4.0'); expect(json.dependencies.rxjs).toBe('~6.5.3');
} else { } else {
expect(json.dependencies.rxjs).toBe(version); expect(json.dependencies.rxjs).toBe(version);
} }