Skip to content

Commit b0dcfd0

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 0a959ab commit b0dcfd0

File tree

16 files changed

+342
-186
lines changed

16 files changed

+342
-186
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
@@ -48,7 +48,7 @@ export interface ProcessOutput {
4848
write(buffer: string | Buffer): boolean;
4949
}
5050

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

5353
export interface ResolveOptions {
5454
basedir: string;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"@types/minimist": "^1.2.0",
104104
"@types/node": "10.12.30",
105105
"@types/request": "^2.47.1",
106+
"@types/rimraf": "^2.0.2",
106107
"@types/semver": "^6.0.0",
107108
"@types/webpack": "^4.32.1",
108109
"@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
@@ -37,11 +37,11 @@ ts_library(
3737
"@npm//@types/debug",
3838
"@npm//@types/node",
3939
"@npm//@types/inquirer",
40+
"@npm//@types/rimraf",
4041
"@npm//@types/semver",
4142
"@npm//@types/universal-analytics",
4243
"@npm//@types/uuid",
4344
"@npm//ansi-colors",
44-
"@npm//rxjs",
4545
],
4646
)
4747

@@ -52,6 +52,7 @@ ts_library(
5252
":add_schema",
5353
":analytics_schema",
5454
":build_schema",
55+
":cli_schema",
5556
":config_schema",
5657
":deploy_schema",
5758
":deprecated_schema",
@@ -71,6 +72,11 @@ ts_library(
7172
],
7273
)
7374

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

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

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { analytics, tags } from '@angular-devkit/core';
99
import { NodePackageDoesNotSupportSchematics } from '@angular-devkit/schematics/tools';
1010
import { dirname, join } from 'path';
1111
import { intersects, prerelease, rcompare, satisfies, valid, validRange } from 'semver';
12+
import { PackageManager } from '../lib/config/schema';
1213
import { isPackageNameSafeForAnalytics } from '../models/analytics';
1314
import { Arguments } from '../models/interface';
1415
import { RunSchematicOptions, SchematicCommand } from '../models/schematic-command';
15-
import npmInstall from '../tasks/npm-install';
16-
import npmUninstall from '../tasks/npm-uninstall';
16+
import { installPackage, installTempPackage } from '../tasks/install-package';
1717
import { colors } from '../utilities/color';
1818
import { getPackageManager } from '../utilities/package-manager';
1919
import {
@@ -57,7 +57,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
5757
}
5858

5959
const packageManager = await getPackageManager(this.workspace.root);
60-
const usingYarn = packageManager === 'yarn';
60+
const usingYarn = packageManager === PackageManager.Yarn;
6161

6262
if (packageIdentifier.type === 'tag' && !packageIdentifier.rawSpec) {
6363
// only package name provided; search for viable version
@@ -115,7 +115,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
115115
}
116116

117117
let collectionName = packageIdentifier.name;
118-
let dependencyType: NgAddSaveDepedency | undefined;
118+
let savePackage: NgAddSaveDepedency | undefined;
119119

120120
try {
121121
const manifest = await fetchPackageManifest(packageIdentifier, this.logger, {
@@ -124,7 +124,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
124124
usingYarn,
125125
});
126126

127-
dependencyType = manifest['ng-add'] && manifest['ng-add'].save;
127+
savePackage = manifest['ng-add'] && manifest['ng-add'].save;
128128
collectionName = manifest.name;
129129

130130
if (await this.hasMismatchedPeer(manifest)) {
@@ -138,16 +138,13 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
138138
return 1;
139139
}
140140

141-
await npmInstall(packageIdentifier.raw, this.logger, packageManager, dependencyType);
142-
143-
const schematicResult = await this.executeSchematic(collectionName, options['--']);
144-
145-
if (dependencyType === false) {
146-
// Uninstall the package if it was not meant to be retained.
147-
return npmUninstall(packageIdentifier.raw, this.logger, packageManager);
141+
if (savePackage === false) {
142+
installTempPackage(packageIdentifier.raw, this.logger, packageManager);
143+
} else {
144+
installPackage(packageIdentifier.raw, this.logger, packageManager, savePackage);
148145
}
149146

150-
return schematicResult;
147+
return this.executeSchematic(collectionName, options['--']);
151148
}
152149

153150
async reportAnalytics(

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

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@ import { execSync } from 'child_process';
1313
import * as fs from 'fs';
1414
import * as path from 'path';
1515
import * as semver from 'semver';
16+
import { PackageManager } from '../lib/config/schema';
1617
import { Command } from '../models/command';
1718
import { Arguments } from '../models/interface';
19+
import { runTempPackageBin } from '../tasks/install-package';
1820
import { colors } from '../utilities/color';
1921
import { getPackageManager } from '../utilities/package-manager';
2022
import {
2123
PackageIdentifier,
2224
PackageManifest,
2325
PackageMetadata,
26+
fetchPackageManifest,
2427
fetchPackageMetadata,
2528
} from '../utilities/package-metadata';
2629
import { PackageTreeNode, findNodeDependencies, readPackageTree } from '../utilities/package-tree';
@@ -38,22 +41,23 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
3841
public readonly allowMissingWorkspace = true;
3942

4043
private workflow: NodeWorkflow;
44+
private packageManager: PackageManager;
4145

4246
async initialize() {
47+
this.packageManager = await getPackageManager(this.workspace.root);
4348
this.workflow = new NodeWorkflow(
4449
new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(this.workspace.root)),
4550
{
46-
packageManager: await getPackageManager(this.workspace.root),
51+
packageManager: this.packageManager,
4752
root: normalize(this.workspace.root),
4853
},
4954
);
50-
5155
this.workflow.engineHost.registerOptionsTransform(
5256
validateOptionsWithSchema(this.workflow.registry),
5357
);
5458
}
5559

56-
async executeSchematic(
60+
private async executeSchematic(
5761
collection: string,
5862
schematic: string,
5963
options = {},
@@ -127,7 +131,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
127131
}
128132
}
129133

130-
async executeMigrations(
134+
private async executeMigrations(
131135
packageName: string,
132136
collectionPath: string,
133137
range: semver.Range,
@@ -190,6 +194,21 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
190194

191195
// tslint:disable-next-line:no-big-function
192196
async run(options: UpdateCommandSchema & Arguments) {
197+
// Check if the current installed CLI version is older than the latest version.
198+
if (await this.checkCLILatestVersion(options.verbose)) {
199+
this.logger.warn(
200+
'The installed Angular CLI version is older than the latest published version.\n' +
201+
'Installing a temporary version to perform the update.',
202+
);
203+
204+
return runTempPackageBin(
205+
'@angular/cli@latest',
206+
this.logger,
207+
this.packageManager,
208+
process.argv.slice(2),
209+
);
210+
}
211+
193212
const packages: PackageIdentifier[] = [];
194213
for (const request of options['--'] || []) {
195214
try {
@@ -252,8 +271,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
252271
}
253272
}
254273

255-
const packageManager = await getPackageManager(this.workspace.root);
256-
this.logger.info(`Using package manager: '${packageManager}'`);
274+
this.logger.info(`Using package manager: '${this.packageManager}'`);
257275

258276
// Special handling for Angular CLI 1.x migrations
259277
if (
@@ -293,7 +311,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
293311
force: options.force || false,
294312
next: options.next || false,
295313
verbose: options.verbose || false,
296-
packageManager,
314+
packageManager: this.packageManager,
297315
packages: options.all ? Object.keys(rootDependencies) : [],
298316
});
299317

@@ -513,7 +531,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
513531
const { success } = await this.executeSchematic('@schematics/update', 'update', {
514532
verbose: options.verbose || false,
515533
force: options.force || false,
516-
packageManager,
534+
packageManager: this.packageManager,
517535
packages: packagesToUpdate,
518536
migrateExternal: true,
519537
});
@@ -549,7 +567,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
549567
return success ? 0 : 1;
550568
}
551569

552-
checkCleanGit() {
570+
private checkCleanGit(): boolean {
553571
try {
554572
const topLevel = execSync('git rev-parse --show-toplevel', { encoding: 'utf8', stdio: 'pipe' });
555573
const result = execSync('git status --porcelain', { encoding: 'utf8', stdio: 'pipe' });
@@ -573,15 +591,15 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
573591
return true;
574592
}
575593

576-
createCommit(message: string, files: string[]) {
594+
private createCommit(message: string, files: string[]) {
577595
try {
578596
execSync('git add -A ' + files.join(' '), { encoding: 'utf8', stdio: 'pipe' });
579597

580598
execSync(`git commit --no-verify -m "${message}"`, { encoding: 'utf8', stdio: 'pipe' });
581599
} catch (error) {}
582600
}
583601

584-
findCurrentGitSha(): string | null {
602+
private findCurrentGitSha(): string | null {
585603
try {
586604
const result = execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: 'pipe' });
587605

@@ -590,6 +608,25 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
590608
return null;
591609
}
592610
}
611+
612+
/**
613+
* Checks if the current installed CLI version is older than the latest version.
614+
* @returns `true` when the installed version is older.
615+
*/
616+
private async checkCLILatestVersion(verbose = false): Promise<boolean> {
617+
const { version: installedCLIVersion } = require('../package.json');
618+
619+
const LatestCLIManifest = await fetchPackageManifest(
620+
'@angular/cli@latest',
621+
this.logger,
622+
{
623+
verbose,
624+
usingYarn: this.packageManager === PackageManager.Yarn,
625+
},
626+
);
627+
628+
return semver.lt(installedCLIVersion, LatestCLIManifest.version);
629+
}
593630
}
594631

595632
function coerceVersionNumber(version: string | undefined): string | null {

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: 20 additions & 8 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,6 +76,20 @@ if (process.env['NG_CLI_PROFILING']) {
7876
}
7977

8078
(async () => {
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';
88+
89+
if (disableVersionCheck) {
90+
return (await import('./cli')).default;
91+
}
92+
8193
let cli;
8294
try {
8395
const projectLocalCli = require.resolve('@angular/cli', { paths: [process.cwd()] });
@@ -116,13 +128,13 @@ if (process.env['NG_CLI_PROFILING']) {
116128

117129
// No error implies a projectLocalCli, which will load whatever
118130
// version of ng-cli you have installed in a local package.json
119-
cli = require(projectLocalCli);
131+
cli = await import(projectLocalCli);
120132
} catch {
121133
// If there is an error, resolve could not find the ng-cli
122134
// library from a package.json. Instead, include it from a relative
123135
// path to this script file (which is likely a globally installed
124136
// npm package). Most common cause for hitting this is `ng new`
125-
cli = require('./cli');
137+
cli = await import('./cli');
126138
}
127139

128140
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": "7.0.0",
4141
"pacote": "9.5.8",
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)