Skip to content

Commit dfa1ab8

Browse files
alan-agius4vikerman
authored andcommitted
fix(@angular/cli): logic to determine if the installed CLI is out of date
With this change we now check if the current CLI version is the latest published version. If it is not, we install a temporary version to run the ng update with.
1 parent 7299d7a commit dfa1ab8

File tree

13 files changed

+335
-135
lines changed

13 files changed

+335
-135
lines changed

etc/api/angular_devkit/core/node/_golden-api.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export interface ProcessOutput {
4949
write(buffer: string | Buffer): boolean;
5050
}
5151

52-
export declare function resolve(x: string, options: ResolveOptions): string;
52+
export declare function resolve(packageName: string, options: ResolveOptions): string;
5353

5454
export interface ResolveOptions {
5555
basedir: string;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"@types/minimist": "^1.2.0",
103103
"@types/node": "10.9.4",
104104
"@types/request": "^2.47.1",
105+
"@types/rimraf": "^2.0.2",
105106
"@types/semver": "^6.0.0",
106107
"@types/webpack": "^4.32.1",
107108
"@types/webpack-dev-server": "^3.1.7",

packages/angular/cli/BUILD

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ ts_library(
3838
"@npm//debug",
3939
"@npm//@types/node",
4040
"@npm//@types/inquirer",
41+
"@npm//@types/rimraf",
4142
"@npm//@types/semver",
4243
"@npm//@types/universal-analytics",
4344
"@npm//@types/uuid",
4445
"@npm//ansi-colors",
45-
"@npm//rxjs",
4646
],
4747
)
4848

@@ -53,6 +53,7 @@ ts_library(
5353
":add_schema",
5454
":analytics_schema",
5555
":build_schema",
56+
":cli_schema",
5657
":config_schema",
5758
":deploy_schema",
5859
":deprecated_schema",
@@ -72,6 +73,11 @@ ts_library(
7273
],
7374
)
7475

76+
ts_json_schema(
77+
name = "cli_schema",
78+
src = "lib/config/schema.json",
79+
)
80+
7581
ts_json_schema(
7682
name = "analytics_schema",
7783
src = "commands/analytics.json",

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { intersects, prerelease, rcompare, satisfies, valid, validRange } from '
1212
import { isPackageNameSafeForAnalytics } from '../models/analytics';
1313
import { Arguments } from '../models/interface';
1414
import { RunSchematicOptions, SchematicCommand } from '../models/schematic-command';
15-
import npmInstall from '../tasks/npm-install';
15+
import { installPackage } from '../tasks/install-package';
1616
import { colors } from '../utilities/color';
1717
import { getPackageManager } from '../utilities/package-manager';
1818
import {
@@ -135,7 +135,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
135135
}
136136
}
137137

138-
await npmInstall(packageIdentifier.raw, this.logger, this.packageManager, this.workspace.root);
138+
installPackage(packageIdentifier.raw, this.logger, this.packageManager);
139139

140140
return this.executeSchematic(collectionName, options['--']);
141141
}

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

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import { execSync } from 'child_process';
99
import * as fs from 'fs';
1010
import * as path from 'path';
11+
import * as semver from 'semver';
1112
import { Arguments, Option } from '../models/interface';
1213
import { SchematicCommand } from '../models/schematic-command';
14+
import { runTempPackageBin } from '../tasks/install-package';
1315
import { getPackageManager } from '../utilities/package-manager';
1416
import {
1517
PackageIdentifier,
@@ -30,13 +32,29 @@ const oldConfigFileNames = ['.angular-cli.json', 'angular-cli.json'];
3032

3133
export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
3234
public readonly allowMissingWorkspace = true;
35+
private readonly packageManager = getPackageManager(this.workspace.root);
3336

3437
async parseArguments(_schematicOptions: string[], _schema: Option[]): Promise<Arguments> {
3538
return {};
3639
}
3740

3841
// tslint:disable-next-line:no-big-function
3942
async run(options: UpdateCommandSchema & Arguments) {
43+
// Check if the current installed CLI version is older than the latest version.
44+
if (await this.checkCLILatestVersion(options.verbose)) {
45+
this.logger.warn(
46+
'The installed Angular CLI version is older than the latest published version.\n' +
47+
'Installing a temporary version to perform the update.',
48+
);
49+
50+
return runTempPackageBin(
51+
'@angular/cli@latest',
52+
this.logger,
53+
this.packageManager,
54+
process.argv.slice(2),
55+
);
56+
}
57+
4058
const packages: PackageIdentifier[] = [];
4159
for (const request of options['--'] || []) {
4260
try {
@@ -99,8 +117,7 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
99117
}
100118
}
101119

102-
const packageManager = getPackageManager(this.workspace.root);
103-
this.logger.info(`Using package manager: '${packageManager}'`);
120+
this.logger.info(`Using package manager: '${this.packageManager}'`);
104121

105122
// Special handling for Angular CLI 1.x migrations
106123
if (
@@ -134,7 +151,7 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
134151
force: options.force || false,
135152
next: options.next || false,
136153
verbose: options.verbose || false,
137-
packageManager,
154+
packageManager: this.packageManager,
138155
packages: options.all ? Object.keys(rootDependencies) : [],
139156
},
140157
});
@@ -351,7 +368,7 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
351368
additionalOptions: {
352369
verbose: options.verbose || false,
353370
force: options.force || false,
354-
packageManager,
371+
packageManager: this.packageManager,
355372
packages: packagesToUpdate,
356373
},
357374
});
@@ -381,4 +398,28 @@ export class UpdateCommand extends SchematicCommand<UpdateCommandSchema> {
381398

382399
return true;
383400
}
401+
402+
/**
403+
* Checks if the current installed CLI version is older than the latest version.
404+
* @returns `true` when the installed version is older.
405+
*/
406+
private async checkCLILatestVersion(verbose = false): Promise<boolean> {
407+
const { version: installedCLIVersion } = require('../package.json');
408+
409+
const LatestCLIManifest = await fetchPackageMetadata(
410+
'@angular/cli',
411+
this.logger,
412+
{
413+
verbose,
414+
usingYarn: this.packageManager === 'yarn',
415+
},
416+
);
417+
418+
const latest = LatestCLIManifest.tags['latest'];
419+
if (!latest) {
420+
return false;
421+
}
422+
423+
return semver.lt(installedCLIVersion, latest.version);
424+
}
384425
}

packages/angular/cli/lib/cli/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ import { colors, supportsColor } from '../../utilities/color';
1313
import { getWorkspaceRaw } from '../../utilities/config';
1414
import { getWorkspaceDetails } from '../../utilities/project';
1515

16+
const debugEnv = process.env['NG_DEBUG'];
17+
const isDebug =
18+
debugEnv !== undefined &&
19+
debugEnv !== '0' &&
20+
debugEnv.toLowerCase() !== 'false';
21+
1622
// tslint:disable: no-console
1723
export default async function(options: { testing?: boolean; cliArgs: string[] }) {
18-
const logger = createConsoleLogger(false, process.stdout, process.stderr, {
24+
const logger = createConsoleLogger(isDebug, process.stdout, process.stderr, {
1925
info: s => (supportsColor ? s : colors.unstyle(s)),
2026
debug: s => (supportsColor ? s : colors.unstyle(s)),
2127
warn: s => (supportsColor ? colors.bold.yellow(s) : colors.unstyle(s)),

packages/angular/cli/lib/init.ts

Lines changed: 55 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,15 @@ import { isWarningEnabled } from '../utilities/config';
1919

2020
const packageJson = require('../package.json');
2121

22-
function _fromPackageJson(cwd?: string) {
23-
cwd = cwd || process.cwd();
24-
22+
function _fromPackageJson(cwd = process.cwd()): SemVer | null {
2523
do {
2624
const packageJsonPath = path.join(cwd, 'node_modules/@angular/cli/package.json');
2725
if (fs.existsSync(packageJsonPath)) {
2826
const content = fs.readFileSync(packageJsonPath, 'utf-8');
2927
if (content) {
30-
const json = JSON.parse(content);
31-
if (json['version']) {
32-
return new SemVer(json['version']);
28+
const { version } = JSON.parse(content);
29+
if (version) {
30+
return new SemVer(version);
3331
}
3432
}
3533
}
@@ -78,50 +76,64 @@ if (process.env['NG_CLI_PROFILING']) {
7876
}
7977

8078
let cli;
81-
try {
82-
const projectLocalCli = require.resolve('@angular/cli', { paths: [process.cwd()] });
83-
84-
// This was run from a global, check local version.
85-
const globalVersion = new SemVer(packageJson['version']);
86-
let localVersion;
87-
let shouldWarn = false;
79+
const disableVersionCheckEnv = process.env['NG_DISABLE_VERSION_CHECK'];
80+
/**
81+
* Disable CLI version mismatch checks and forces usage of the invoked CLI
82+
* instead of invoking the local installed version.
83+
*/
84+
const disableVersionCheck =
85+
disableVersionCheckEnv !== undefined &&
86+
disableVersionCheckEnv !== '0' &&
87+
disableVersionCheckEnv.toLowerCase() !== 'false';
8888

89+
if (disableVersionCheck) {
90+
cli = require('./cli');
91+
} else {
8992
try {
90-
localVersion = _fromPackageJson();
91-
shouldWarn = localVersion != null && globalVersion.compare(localVersion) > 0;
92-
} catch (e) {
93-
// eslint-disable-next-line no-console
94-
console.error(e);
95-
shouldWarn = true;
96-
}
93+
const projectLocalCli = require.resolve('@angular/cli', { paths: [process.cwd()] });
9794

98-
if (shouldWarn && isWarningEnabled('versionMismatch')) {
99-
const warning = colors.yellow(tags.stripIndents`
100-
Your global Angular CLI version (${globalVersion}) is greater than your local
101-
version (${localVersion}). The local Angular CLI version is used.
95+
// This was run from a global, check local version.
96+
const globalVersion = new SemVer(packageJson['version']);
97+
let localVersion;
98+
let shouldWarn = false;
10299

103-
To disable this warning use "ng config -g cli.warnings.versionMismatch false".
104-
`);
105-
// Don't show warning colorised on `ng completion`
106-
if (process.argv[2] !== 'completion') {
100+
try {
101+
localVersion = _fromPackageJson();
102+
shouldWarn = localVersion != null && globalVersion.compare(localVersion) > 0;
103+
} catch (e) {
107104
// eslint-disable-next-line no-console
108-
console.error(warning);
109-
} else {
110-
// eslint-disable-next-line no-console
111-
console.error(warning);
112-
process.exit(1);
105+
console.error(e);
106+
shouldWarn = true;
113107
}
114-
}
115108

116-
// No error implies a projectLocalCli, which will load whatever
117-
// version of ng-cli you have installed in a local package.json
118-
cli = require(projectLocalCli);
119-
} catch {
120-
// If there is an error, resolve could not find the ng-cli
121-
// library from a package.json. Instead, include it from a relative
122-
// path to this script file (which is likely a globally installed
123-
// npm package). Most common cause for hitting this is `ng new`
124-
cli = require('./cli');
109+
if (shouldWarn && isWarningEnabled('versionMismatch')) {
110+
const warning = colors.yellow(tags.stripIndents`
111+
Your global Angular CLI version (${globalVersion}) is greater than your local
112+
version (${localVersion}). The local Angular CLI version is used.
113+
114+
To disable this warning use "ng config -g cli.warnings.versionMismatch false".
115+
`);
116+
// Don't show warning colorised on `ng completion`
117+
if (process.argv[2] !== 'completion') {
118+
// eslint-disable-next-line no-console
119+
console.error(warning);
120+
} else {
121+
// eslint-disable-next-line no-console
122+
console.error(warning);
123+
process.exit(1);
124+
}
125+
}
126+
127+
// No error implies a projectLocalCli, which will load whatever
128+
// version of ng-cli you have installed in a local package.json
129+
cli = require(projectLocalCli);
130+
} catch {
131+
// If there is an error, resolve could not find the ng-cli
132+
// library from a package.json. Instead, include it from a relative
133+
// path to this script file (which is likely a globally installed
134+
// npm package). Most common cause for hitting this is `ng new`
135+
cli = require('./cli');
136+
}
125137
}
126138

127139
if ('default' in cli) {

packages/angular/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"open": "6.4.0",
4141
"pacote": "9.5.5",
4242
"read-package-tree": "5.3.1",
43+
"rimraf": "3.0.0",
4344
"semver": "6.3.0",
4445
"symbol-observable": "1.2.0",
4546
"universal-analytics": "^0.4.20",

0 commit comments

Comments
 (0)