Skip to content

Commit 5ec27db

Browse files
filipesilvaalexeagle
authored andcommitted
feat(@angular/cli): verify Angular version is supported
1 parent bab9eb6 commit 5ec27db

File tree

11 files changed

+84
-24
lines changed

11 files changed

+84
-24
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
"@types/minimist": "^1.2.0",
9696
"@types/node": "10.9.4",
9797
"@types/request": "^2.47.1",
98-
"@types/semver": "^5.5.0",
98+
"@types/semver": "^6.0.0",
9999
"@types/source-map": "0.5.2",
100100
"@types/webpack": "^4.4.11",
101101
"@types/webpack-dev-server": "^3.1.0",
@@ -122,7 +122,7 @@
122122
"pidtree": "^0.3.0",
123123
"pidusage": "^2.0.17",
124124
"rxjs": "~6.4.0",
125-
"semver": "^5.3.0",
125+
"semver": "6.0.0",
126126
"source-map": "^0.5.6",
127127
"source-map-support": "^0.5.0",
128128
"spdx-satisfies": "^4.0.0",

packages/angular/cli/commands/build-impl.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,12 @@
88
import { analytics } from '@angular-devkit/core';
99
import { ArchitectCommand, ArchitectCommandOptions } from '../models/architect-command';
1010
import { Arguments } from '../models/interface';
11-
import { Version } from '../upgrade/version';
1211
import { Schema as BuildCommandSchema } from './build';
1312

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

1716
public async run(options: ArchitectCommandOptions & Arguments) {
18-
// Check Angular version.
19-
Version.assertCompatibleAngularVersion(this.workspace.root);
20-
2117
return this.runArchitectTarget(options);
2218
}
2319

packages/angular/cli/commands/serve-impl.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,13 @@
88
import { analytics } from '@angular-devkit/core';
99
import { ArchitectCommand, ArchitectCommandOptions } from '../models/architect-command';
1010
import { Arguments } from '../models/interface';
11-
import { Version } from '../upgrade/version';
1211
import { Schema as BuildCommandSchema } from './build';
1312
import { Schema as ServeCommandSchema } from './serve';
1413

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

1817
public validate(_options: ArchitectCommandOptions & Arguments) {
19-
// Check Angular versions.
20-
Version.assertCompatibleAngularVersion(this.workspace.root);
21-
2218
return true;
2319
}
2420

packages/angular/cli/models/architect-command.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Architect, Target } from '@angular-devkit/architect';
99
import { WorkspaceNodeModulesArchitectHost } from '@angular-devkit/architect/node';
1010
import { experimental, json, schema, tags } from '@angular-devkit/core';
1111
import { NodeJsSyncHost } from '@angular-devkit/core/node';
12+
import { Version } from '../upgrade/version';
1213
import { BepJsonWriter } from '../utilities/bep';
1314
import { parseJsonSchemaToOptions } from '../utilities/json-schema';
1415
import { isPackageNameSafeForAnalytics } from './analytics';
@@ -274,6 +275,9 @@ export abstract class ArchitectCommand<
274275
protected async runArchitectTarget(
275276
options: ArchitectCommandOptions & Arguments,
276277
): Promise<number> {
278+
// Check Angular version.
279+
Version.assertCompatibleAngularVersion(this.workspace.root);
280+
277281
const extra = options['--'] || [];
278282

279283
try {

packages/angular/cli/upgrade/version.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

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

1313

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

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

3844
static assertCompatibleAngularVersion(projectRoot: string) {
45+
let angularCliPkgJson;
3946
let angularPkgJson;
4047
let rxjsPkgJson;
48+
const resolveOptions = { paths: [projectRoot] };
4149

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

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

71+
try {
72+
const angularCliPkgPath = require.resolve('@angular/cli/package.json', resolveOptions);
73+
angularCliPkgJson = require(angularCliPkgPath);
74+
if (!(angularCliPkgJson && angularCliPkgJson['version'])) {
75+
throw new Error();
76+
}
77+
} catch (error) {
78+
console.error(terminal.bold(terminal.red(tags.stripIndents`
79+
Cannot determine versions of "@angular/cli".
80+
This likely means your local installation is broken. Please reinstall your packages.
81+
`)));
82+
process.exit(2);
83+
}
84+
85+
const cliMajor = new Version(angularCliPkgJson['version']).major;
86+
// e.g. CLI 8.0 supports '>=8.0.0 <9.0.0', including pre-releases (betas, rcs, snapshots)
87+
// of both 8 and 9.
88+
const supportedAngularSemver = `^${cliMajor}.0.0-beta || ` +
89+
`>=${cliMajor}.0.0 <${cliMajor + 1}.0.0`;
90+
6491
const angularVersion = new Version(angularPkgJson['version']);
6592
const rxjsVersion = new Version(rxjsPkgJson['version']);
6693

@@ -70,9 +97,10 @@ export class Version {
7097
return;
7198
}
7299

73-
if (!angularVersion.isGreaterThanOrEqualTo(new SemVer('5.0.0'))) {
100+
if (!angularVersion.satisfies(supportedAngularSemver)) {
74101
console.error(terminal.bold(terminal.red(tags.stripIndents`
75-
This version of CLI is only compatible with Angular version 5.0.0 or higher.
102+
This version of CLI is only compatible with Angular versions ${supportedAngularSemver},
103+
but Angular version ${angularVersion} was found instead.
76104
77105
Please visit the link below to find instructions on how to update Angular.
78106
https://angular-update-guide.firebaseapp.com/
@@ -84,7 +112,7 @@ export class Version {
84112
&& !rxjsVersion.isGreaterThanOrEqualTo(new SemVer('6.0.0-beta.0'))
85113
) {
86114
console.error(terminal.bold(terminal.red(tags.stripIndents`
87-
This project uses version ${rxjsVersion} of RxJs, which is not supported by Angular v6.
115+
This project uses version ${rxjsVersion} of RxJs, which is not supported by Angular v6+.
88116
The official RxJs version that is supported is 5.6.0-forward-compat.0 and greater.
89117
90118
Please visit the link below to find instructions on how to update RxJs.

tests/legacy-cli/e2e/assets/1.0-project/src/app/app.module.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { BrowserModule } from '@angular/platform-browser';
22
import { NgModule } from '@angular/core';
33
import { FormsModule } from '@angular/forms';
4-
import { HttpModule } from '@angular/http';
54

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

@@ -12,7 +11,6 @@ import { AppComponent } from './app.component';
1211
imports: [
1312
BrowserModule,
1413
FormsModule,
15-
HttpModule
1614
],
1715
providers: [],
1816
bootstrap: [AppComponent]

tests/legacy-cli/e2e/tests/basic/update-1.0.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { expectToFail } from '../../utils/utils';
77

88

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

1212
await createProjectFromAsset('1.0-project');
1313
await useCIChrome('.');

tests/legacy-cli/e2e/tests/basic/update-1.7-longhand.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { expectToFail } from '../../utils/utils';
55

66

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

1010
await createProjectFromAsset('1.7-project');
1111
await expectToFail(() => ng('build'));

tests/legacy-cli/e2e/tests/basic/update-1.7.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { expectToFail } from '../../utils/utils';
77

88

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

1212
await createProjectFromAsset('1.7-project');
1313
await useCIChrome('.');
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { SemVer } from 'semver';
2+
import { getGlobalVariable } from '../../utils/env';
3+
import { readFile, writeFile } from '../../utils/fs';
4+
import { ng } from '../../utils/process';
5+
import { expectToFail } from '../../utils/utils';
6+
7+
8+
export default async function () {
9+
if (getGlobalVariable('argv')['ng-snapshots'] || getGlobalVariable('argv')['ivy']) {
10+
// Don't run this test in snapshots or Ivy test jobs.
11+
// The snapshots job won't work correctly because it doesn't use semver for Angular, and the
12+
// Ivy job will fail because Ivy wasn't stable in 7.
13+
return;
14+
}
15+
16+
const angularCliPkgJson = JSON.parse(await readFile('node_modules/@angular/cli/package.json'));
17+
const cliMajor = new SemVer(angularCliPkgJson.version as string).major;
18+
const angularCorePkgPath = 'node_modules/@angular/core/package.json';
19+
const originalAngularCorePkgJson = await readFile(angularCorePkgPath);
20+
21+
// Fake version by writing over the @angular/core version, since that's what the CLI checks.
22+
const fakeCoreVersion = async (newMajor: number) => {
23+
const tmpPkgJson = JSON.parse(originalAngularCorePkgJson);
24+
tmpPkgJson.version = `${newMajor}.0.0`;
25+
await writeFile(angularCorePkgPath, JSON.stringify(tmpPkgJson));
26+
};
27+
28+
// Major should succeed, but we don't need to test it here since it's tested everywhere else.
29+
// Major+1 and -1 should fail architect commands, but allow other commands.
30+
await fakeCoreVersion(cliMajor + 1);
31+
await expectToFail(() => ng('build'), 'Should fail Major+1');
32+
await ng('version');
33+
await fakeCoreVersion(cliMajor - 1);
34+
await ng('version');
35+
36+
// Restore the original core package.json.
37+
await writeFile(angularCorePkgPath, originalAngularCorePkgJson);
38+
}

0 commit comments

Comments
 (0)