Skip to content

Commit

Permalink
feat(@angular/cli): verify Angular version is supported
Browse files Browse the repository at this point in the history
  • Loading branch information
filipesilva authored and alexeagle committed Apr 15, 2019
1 parent bab9eb6 commit 5ec27db
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 24 deletions.
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -95,7 +95,7 @@
"@types/minimist": "^1.2.0",
"@types/node": "10.9.4",
"@types/request": "^2.47.1",
"@types/semver": "^5.5.0",
"@types/semver": "^6.0.0",
"@types/source-map": "0.5.2",
"@types/webpack": "^4.4.11",
"@types/webpack-dev-server": "^3.1.0",
Expand All @@ -122,7 +122,7 @@
"pidtree": "^0.3.0",
"pidusage": "^2.0.17",
"rxjs": "~6.4.0",
"semver": "^5.3.0",
"semver": "6.0.0",
"source-map": "^0.5.6",
"source-map-support": "^0.5.0",
"spdx-satisfies": "^4.0.0",
Expand Down
4 changes: 0 additions & 4 deletions packages/angular/cli/commands/build-impl.ts
Expand Up @@ -8,16 +8,12 @@
import { analytics } from '@angular-devkit/core';
import { ArchitectCommand, ArchitectCommandOptions } from '../models/architect-command';
import { Arguments } from '../models/interface';
import { Version } from '../upgrade/version';
import { Schema as BuildCommandSchema } from './build';

export class BuildCommand extends ArchitectCommand<BuildCommandSchema> {
public readonly target = 'build';

public async run(options: ArchitectCommandOptions & Arguments) {
// Check Angular version.
Version.assertCompatibleAngularVersion(this.workspace.root);

return this.runArchitectTarget(options);
}

Expand Down
4 changes: 0 additions & 4 deletions packages/angular/cli/commands/serve-impl.ts
Expand Up @@ -8,17 +8,13 @@
import { analytics } from '@angular-devkit/core';
import { ArchitectCommand, ArchitectCommandOptions } from '../models/architect-command';
import { Arguments } from '../models/interface';
import { Version } from '../upgrade/version';
import { Schema as BuildCommandSchema } from './build';
import { Schema as ServeCommandSchema } from './serve';

export class ServeCommand extends ArchitectCommand<ServeCommandSchema> {
public readonly target = 'serve';

public validate(_options: ArchitectCommandOptions & Arguments) {
// Check Angular versions.
Version.assertCompatibleAngularVersion(this.workspace.root);

return true;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/angular/cli/models/architect-command.ts
Expand Up @@ -9,6 +9,7 @@ import { Architect, Target } from '@angular-devkit/architect';
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
import { experimental, json, schema, tags } from '@angular-devkit/core';
import { NodeJsSyncHost } from '@angular-devkit/core/node';
import { Version } from '../upgrade/version';
import { BepJsonWriter } from '../utilities/bep';
import { parseJsonSchemaToOptions } from '../utilities/json-schema';
import { isPackageNameSafeForAnalytics } from './analytics';
Expand Down Expand Up @@ -274,6 +275,9 @@ export abstract class ArchitectCommand<
protected async runArchitectTarget(
options: ArchitectCommandOptions & Arguments,
): Promise<number> {
// Check Angular version.
Version.assertCompatibleAngularVersion(this.workspace.root);

const extra = options['--'] || [];

try {
Expand Down
38 changes: 33 additions & 5 deletions packages/angular/cli/upgrade/version.ts
Expand Up @@ -8,7 +8,7 @@

import { tags, terminal } from '@angular-devkit/core';
import * as path from 'path';
import { SemVer } from 'semver';
import { SemVer, satisfies } from 'semver';


export class Version {
Expand All @@ -26,6 +26,12 @@ export class Version {
isGreaterThanOrEqualTo(other: SemVer) {
return this._semver !== null && this._semver.compare(other) >= 0;
}
satisfies(other: string) {
// This comparison includes pre-releases (like betas and rcs), and considers them to be
// before the release proper.
// e.g. '9.0.0-beta.1' will satisfy '>=7.0.0 <9.0.0', but '9.0.0' will not.
return this._semver !== null && satisfies(this._semver, other, { includePrerelease: true });
}

get major() { return this._semver ? this._semver.major : 0; }
get minor() { return this._semver ? this._semver.minor : 0; }
Expand All @@ -36,11 +42,12 @@ export class Version {
toString() { return this._version; }

static assertCompatibleAngularVersion(projectRoot: string) {
let angularCliPkgJson;
let angularPkgJson;
let rxjsPkgJson;
const resolveOptions = { paths: [projectRoot] };

try {
const resolveOptions = { paths: [projectRoot] };
const angularPackagePath = require.resolve('@angular/core/package.json', resolveOptions);
const rxjsPackagePath = require.resolve('rxjs/package.json', resolveOptions);

Expand All @@ -61,6 +68,26 @@ export class Version {
process.exit(2);
}

try {
const angularCliPkgPath = require.resolve('@angular/cli/package.json', resolveOptions);
angularCliPkgJson = require(angularCliPkgPath);
if (!(angularCliPkgJson && angularCliPkgJson['version'])) {
throw new Error();
}
} catch (error) {
console.error(terminal.bold(terminal.red(tags.stripIndents`
Cannot determine versions of "@angular/cli".
This likely means your local installation is broken. Please reinstall your packages.
`)));
process.exit(2);
}

const cliMajor = new Version(angularCliPkgJson['version']).major;
// e.g. CLI 8.0 supports '>=8.0.0 <9.0.0', including pre-releases (betas, rcs, snapshots)
// of both 8 and 9.
const supportedAngularSemver = `^${cliMajor}.0.0-beta || ` +
`>=${cliMajor}.0.0 <${cliMajor + 1}.0.0`;

const angularVersion = new Version(angularPkgJson['version']);
const rxjsVersion = new Version(rxjsPkgJson['version']);

Expand All @@ -70,9 +97,10 @@ export class Version {
return;
}

if (!angularVersion.isGreaterThanOrEqualTo(new SemVer('5.0.0'))) {
if (!angularVersion.satisfies(supportedAngularSemver)) {
console.error(terminal.bold(terminal.red(tags.stripIndents`
This version of CLI is only compatible with Angular version 5.0.0 or higher.
This version of CLI is only compatible with Angular versions ${supportedAngularSemver},
but Angular version ${angularVersion} was found instead.
Please visit the link below to find instructions on how to update Angular.
https://angular-update-guide.firebaseapp.com/
Expand All @@ -84,7 +112,7 @@ export class Version {
&& !rxjsVersion.isGreaterThanOrEqualTo(new SemVer('6.0.0-beta.0'))
) {
console.error(terminal.bold(terminal.red(tags.stripIndents`
This project uses version ${rxjsVersion} of RxJs, which is not supported by Angular v6.
This project uses version ${rxjsVersion} of RxJs, which is not supported by Angular v6+.
The official RxJs version that is supported is 5.6.0-forward-compat.0 and greater.
Please visit the link below to find instructions on how to update RxJs.
Expand Down
2 changes: 0 additions & 2 deletions tests/legacy-cli/e2e/assets/1.0-project/src/app/app.module.ts
@@ -1,7 +1,6 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';

Expand All @@ -12,7 +11,6 @@ import { AppComponent } from './app.component';
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
Expand Down
2 changes: 1 addition & 1 deletion tests/legacy-cli/e2e/tests/basic/update-1.0.ts
Expand Up @@ -7,7 +7,7 @@ import { expectToFail } from '../../utils/utils';


export default async function () {
const extraUpdateArgs = await isPrereleaseCli() ? ['--next'] : [];
const extraUpdateArgs = await isPrereleaseCli() ? ['--next', '--force'] : [];

await createProjectFromAsset('1.0-project');
await useCIChrome('.');
Expand Down
2 changes: 1 addition & 1 deletion tests/legacy-cli/e2e/tests/basic/update-1.7-longhand.ts
Expand Up @@ -5,7 +5,7 @@ import { expectToFail } from '../../utils/utils';


export default async function () {
const extraUpdateArgs = await isPrereleaseCli() ? ['--next'] : [];
const extraUpdateArgs = await isPrereleaseCli() ? ['--next', '--force'] : [];

await createProjectFromAsset('1.7-project');
await expectToFail(() => ng('build'));
Expand Down
2 changes: 1 addition & 1 deletion tests/legacy-cli/e2e/tests/basic/update-1.7.ts
Expand Up @@ -7,7 +7,7 @@ import { expectToFail } from '../../utils/utils';


export default async function () {
const extraUpdateArgs = await isPrereleaseCli() ? ['--next'] : [];
const extraUpdateArgs = await isPrereleaseCli() ? ['--next', '--force'] : [];

await createProjectFromAsset('1.7-project');
await useCIChrome('.');
Expand Down
38 changes: 38 additions & 0 deletions tests/legacy-cli/e2e/tests/misc/supported-angular.ts
@@ -0,0 +1,38 @@
import { SemVer } from 'semver';
import { getGlobalVariable } from '../../utils/env';
import { readFile, writeFile } from '../../utils/fs';
import { ng } from '../../utils/process';
import { expectToFail } from '../../utils/utils';


export default async function () {
if (getGlobalVariable('argv')['ng-snapshots'] || getGlobalVariable('argv')['ivy']) {
// Don't run this test in snapshots or Ivy test jobs.
// The snapshots job won't work correctly because it doesn't use semver for Angular, and the
// Ivy job will fail because Ivy wasn't stable in 7.
return;
}

const angularCliPkgJson = JSON.parse(await readFile('node_modules/@angular/cli/package.json'));
const cliMajor = new SemVer(angularCliPkgJson.version as string).major;
const angularCorePkgPath = 'node_modules/@angular/core/package.json';
const originalAngularCorePkgJson = await readFile(angularCorePkgPath);

// Fake version by writing over the @angular/core version, since that's what the CLI checks.
const fakeCoreVersion = async (newMajor: number) => {
const tmpPkgJson = JSON.parse(originalAngularCorePkgJson);
tmpPkgJson.version = `${newMajor}.0.0`;
await writeFile(angularCorePkgPath, JSON.stringify(tmpPkgJson));
};

// Major should succeed, but we don't need to test it here since it's tested everywhere else.
// Major+1 and -1 should fail architect commands, but allow other commands.
await fakeCoreVersion(cliMajor + 1);
await expectToFail(() => ng('build'), 'Should fail Major+1');
await ng('version');
await fakeCoreVersion(cliMajor - 1);
await ng('version');

// Restore the original core package.json.
await writeFile(angularCorePkgPath, originalAngularCorePkgJson);
}
8 changes: 4 additions & 4 deletions yarn.lock
Expand Up @@ -548,10 +548,10 @@
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.12.tgz#6affe5aed1ba379175075a889adbe2bc3aa62159"
integrity sha512-hYn+eoOehVUIdMwp5h34ZsGAO1ydja10GDup4BwyoFCdcH5MQ35nQq+AInSaBMEMopD5hEooFCyKo2Pajbe1ag==

"@types/semver@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
"@types/semver@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.0.0.tgz#86ba89f02a414e39c68d02b351872e4ed31bd773"
integrity sha512-OO0srjOGH99a4LUN2its3+r6CBYcplhJ466yLqs+zvAWgphCpS8hYZEZ797tRDP/QKcqTdb/YCN6ifASoAWkrQ==

"@types/serve-static@*":
version "1.13.2"
Expand Down

0 comments on commit 5ec27db

Please sign in to comment.