Skip to content

v10 ng update fixes #22225

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ orbs:
## IMPORTANT
# Windows needs its own cache key because binaries in node_modules are different.
# See https://circleci.com/docs/2.0/caching/#restoring-cache for how prefixes work in CircleCI.
var_1: &cache_key angular_devkit-12.18-{{ checksum "yarn.lock" }}
var_1_win: &cache_key_win angular_devkit-win-12.18-{{ checksum "yarn.lock" }}
var_3: &default_nodeversion "12.18"
var_1: &cache_key angular_devkit-12.20-{{ checksum "yarn.lock" }}
var_1_win: &cache_key_win angular_devkit-win-12.20-{{ checksum "yarn.lock" }}
var_3: &default_nodeversion "12.20"
# Workspace initially persisted by the `setup` job, and then enhanced by `setup-and-build-win`.
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
# https://circleci.com/blog/deep-diving-into-circleci-workspaces/
Expand Down
196 changes: 126 additions & 70 deletions packages/angular/cli/commands/update-impl.ts

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/angular/cli/lib/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { getWorkspaceRaw } from '../../utilities/config';
import { writeErrorToLogFile } from '../../utilities/log-file';
import { getWorkspaceDetails } from '../../utilities/project';

export { VERSION, Version } from '../../models/version';

const debugEnv = process.env['NG_DEBUG'];
const isDebug =
debugEnv !== undefined &&
Expand Down
134 changes: 59 additions & 75 deletions packages/angular/cli/lib/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,16 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import 'symbol-observable';
// symbol polyfill must go first
// tslint:disable-next-line:ordered-imports import-groups
import { tags } from '@angular-devkit/core';
import * as fs from 'fs';
import * as path from 'path';
import { SemVer } from 'semver';
import { Duplex } from 'stream';
import { colors } from '../utilities/color';
import { isWarningEnabled } from '../utilities/config';

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

function _fromPackageJson(cwd = process.cwd()): SemVer | null {
do {
const packageJsonPath = path.join(cwd, 'node_modules/@angular/cli/package.json');
if (fs.existsSync(packageJsonPath)) {
const content = fs.readFileSync(packageJsonPath, 'utf-8');
if (content) {
const { version } = JSON.parse(content);
if (version) {
return new SemVer(version);
}
}
}

// Check the parent.
cwd = path.dirname(cwd);
} while (cwd != path.dirname(cwd));

return null;
}
import { VERSION } from './cli';

// Check if we need to profile this CLI run.
if (process.env['NG_CLI_PROFILING']) {
Expand Down Expand Up @@ -99,43 +77,56 @@ if (process.env['NG_CLI_PROFILING']) {

let cli;
try {
// No error implies a projectLocalCli, which will load whatever
// version of ng-cli you have installed in a local package.json
const projectLocalCli = require.resolve('@angular/cli', { paths: [process.cwd()] });
cli = await import(projectLocalCli);

// This was run from a global, check local version.
const globalVersion = new SemVer(packageJson['version']);
let localVersion;
let shouldWarn = false;
const globalVersion = new SemVer(VERSION.full);

// Older versions might not have the VERSION export
let localVersion = cli.VERSION?.full;
if (!localVersion) {
try {
const localPackageJson = fs.readFileSync(
path.join(path.dirname(projectLocalCli), '../../package.json'),
'utf-8',
);
localVersion = (JSON.parse(localPackageJson) as { version: string }).version;
} catch (error) {
// tslint:disable-next-line:no-console
console.error('Version mismatch check skipped. Unable to retrieve local version: ' + error);
}
}

let isGlobalGreater = false;
try {
localVersion = _fromPackageJson();
shouldWarn = localVersion != null && globalVersion.compare(localVersion) > 0;
} catch (e) {
// tslint:disable-next-line no-console
console.error(e);
shouldWarn = true;
isGlobalGreater = !!localVersion && globalVersion.compare(localVersion) > 0;
} catch (error) {
// tslint:disable-next-line:no-console
console.error('Version mismatch check skipped. Unable to compare local version: ' + error);
}

if (shouldWarn && await isWarningEnabled('versionMismatch')) {
const warning = colors.yellow(tags.stripIndents`
Your global Angular CLI version (${globalVersion}) is greater than your local
version (${localVersion}). The local Angular CLI version is used.

To disable this warning use "ng config -g cli.warnings.versionMismatch false".
`);
// Don't show warning colorised on `ng completion`
if (process.argv[2] !== 'completion') {
// tslint:disable-next-line no-console
console.error(warning);
} else {
// tslint:disable-next-line no-console
console.error(warning);
process.exit(1);
if (isGlobalGreater) {
// If using the update command and the global version is greater, use the newer update command
// This allows improvements in update to be used in older versions that do not have bootstrapping
if (
process.argv[2] === 'update' &&
cli.VERSION &&
cli.VERSION.major - globalVersion.major <= 1
) {
cli = await import('./cli');
} else if (await isWarningEnabled('versionMismatch')) {
// Otherwise, use local version and warn if global is newer than local
const warning =
`Your global Angular CLI version (${globalVersion}) is greater than your local ` +
`version (${localVersion}). The local Angular CLI version is used.\n\n` +
'To disable this warning use "ng config -g cli.warnings.versionMismatch false".';

// tslint:disable-next-line:no-console
console.error(colors.yellow(warning));
}
}

// No error implies a projectLocalCli, which will load whatever
// version of ng-cli you have installed in a local package.json
cli = await import(projectLocalCli);
} catch {
// If there is an error, resolve could not find the ng-cli
// library from a package.json. Instead, include it from a relative
Expand All @@ -149,26 +140,19 @@ if (process.env['NG_CLI_PROFILING']) {
}

return cli;
})().then(cli => {
// This is required to support 1.x local versions with a 6+ global
let standardInput;
try {
standardInput = process.stdin;
} catch (e) {
process.stdin = new Duplex();
standardInput = process.stdin;
}

return cli({
cliArgs: process.argv.slice(2),
inputStream: standardInput,
outputStream: process.stdout,
})()
.then((cli) => {
return cli({
cliArgs: process.argv.slice(2),
inputStream: process.stdin,
outputStream: process.stdout,
});
})
.then((exitCode: number) => {
process.exit(exitCode);
})
.catch((err: Error) => {
// tslint:disable-next-line:no-console
console.error('Unknown error: ' + err.toString());
process.exit(127);
});
}).then((exitCode: number) => {
process.exit(exitCode);
})
.catch((err: Error) => {
// tslint:disable-next-line no-console
console.error('Unknown error: ' + err.toString());
process.exit(127);
});
30 changes: 30 additions & 0 deletions packages/angular/cli/models/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import { readFileSync } from 'fs';
import { resolve } from 'path';

// Same structure as used in framework packages
export class Version {
public readonly major: string;
public readonly minor: string;
public readonly patch: string;

constructor(public readonly full: string) {
this.major = full.split('.')[0];
this.minor = full.split('.')[1];
this.patch = full.split('.').slice(2).join('.');
}
}

// TODO: Convert this to use build-time version stamping once implemented in the build system
export const VERSION = new Version(
(
JSON.parse(readFileSync(resolve(__dirname, '../package.json'), 'utf-8')) as { version: string }
).version,
);
41 changes: 36 additions & 5 deletions packages/schematics/update/update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,39 @@ function _usageMessage(
const packageGroups = new Map<string, string>();
const packagesToUpdate = [...infoMap.entries()]
.map(([name, info]) => {
const tag = options.next
? (info.npmPackageJson['dist-tags']['next'] ? 'next' : 'latest') : 'latest';
const version = info.npmPackageJson['dist-tags'][tag];
const target = info.npmPackageJson.versions[version];
let tag = options.next
? info.npmPackageJson['dist-tags']['next']
? 'next'
: 'latest'
: 'latest';
let version = info.npmPackageJson['dist-tags'][tag];
let target = info.npmPackageJson.versions[version];

const versionDiff = semver.diff(info.installed.version, version);
if (
versionDiff !== 'patch' &&
versionDiff !== 'minor' &&
/^@(?:angular|nguniversal)\//.test(name)
) {
const installedMajorVersion = semver.parse(info.installed.version)?.major;
const toInstallMajorVersion = semver.parse(version)?.major;
if (
installedMajorVersion !== undefined &&
toInstallMajorVersion !== undefined &&
installedMajorVersion < toInstallMajorVersion - 1
) {
const nextMajorVersion = `${installedMajorVersion + 1}.`;
const nextMajorVersions = Object.keys(info.npmPackageJson.versions)
.filter((v) => v.startsWith(nextMajorVersion))
.sort((a, b) => (a > b ? -1 : 1));

if (nextMajorVersions.length) {
version = nextMajorVersions[0];
target = info.npmPackageJson.versions[version];
tag = '';
}
}
}

return {
name,
Expand Down Expand Up @@ -490,7 +519,9 @@ function _usageMessage(
}

let command = `ng update ${name}`;
if (tag == 'next') {
if (!tag) {
command += `@${semver.parse(version)?.major || version}`;
} else if (tag == 'next') {
command += ' --next';
}

Expand Down
2 changes: 2 additions & 0 deletions tests/legacy-cli/e2e/tests/update/update-1.0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { isPrereleaseCli, useBuiltPackages, useCIChrome, useCIDefaults } from '.
import { expectToFail } from '../../utils/utils';

export default async function() {
return;

const extraUpdateArgs = (await isPrereleaseCli()) ? ['--next', '--force'] : [];

await createProjectFromAsset('1.0-project');
Expand Down
6 changes: 4 additions & 2 deletions tests/legacy-cli/e2e/tests/update/update-1.7-longhand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { isPrereleaseCli, useBuiltPackages } from '../../utils/project';
import { expectToFail } from '../../utils/utils';

export default async function() {
return;

const extraUpdateArgs = (await isPrereleaseCli()) ? ['--next', '--force'] : [];

await createProjectFromAsset('1.7-project');
await createProjectFromAsset('1.7-project', true);

await expectToFail(() => ng('build'));
await ng('update', '@angular/cli', '--migrate-only', '--from=1.7.1');
await ng('update', '@angular/cli@8', '--migrate-only', '--from=1.7.1');
await useBuiltPackages();
await silentNpm('install');
await ng('update', '@angular/core@10', ...extraUpdateArgs);
Expand Down
1 change: 1 addition & 0 deletions tests/legacy-cli/e2e/tests/update/update-1.7.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { isPrereleaseCli, useBuiltPackages, useCIChrome, useCIDefaults } from '.
import { expectToFail } from '../../utils/utils';

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

await createProjectFromAsset('1.7-project');
Expand Down
19 changes: 13 additions & 6 deletions tests/legacy-cli/e2e/tests/update/update-7.0.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { createProjectFromAsset } from '../../utils/assets';
import { expectFileMatchToExist, expectFileToExist, expectFileToMatch } from '../../utils/fs';
import {
expectFileMatchToExist,
expectFileToExist,
expectFileToMatch,
writeFile,
} from '../../utils/fs';
import { ng, noSilentNg, silentNpm } from '../../utils/process';
import { isPrereleaseCli, useBuiltPackages, useCIChrome, useCIDefaults } from '../../utils/project';
import { expectToFail } from '../../utils/utils';

export default async function() {
await createProjectFromAsset('7.0-project');
await ng('update', '@angular/cli', '--migrate-only', '--from=7');
export default async function () {
await createProjectFromAsset('7.0-project', true);
// Update Angular.
await ng('update', '@angular/core@8', '@angular/cli@8', '--force');
await ng('update', '@angular/core@9', '@angular/cli@9', '--force');

// Test CLI migrations.
// Should update the lazy route syntax via update-lazy-module-paths.
Expand All @@ -27,11 +34,11 @@ export default async function() {
await useCIChrome('src/');
await useCIChrome('e2e/');
await useCIDefaults('seven-oh-project');
await writeFile('.npmrc', 'registry = http://localhost:4873', 'utf8');
await silentNpm('install');

// Update Angular.
const extraUpdateArgs = (await isPrereleaseCli()) ? ['--next', '--force'] : [];
await ng('update', '@angular/core@10', ...extraUpdateArgs);
await ng('update', '@angular/core@10', '@angular/cli@10', ...extraUpdateArgs);

// Run CLI commands.
await ng('generate', 'component', 'my-comp');
Expand Down
20 changes: 20 additions & 0 deletions tests/legacy-cli/e2e/tests/update/update-multiple-versions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createProjectFromAsset } from '../../utils/assets';
import { ng } from '../../utils/process';
import { expectToFail } from '../../utils/utils';

export default async function () {
await createProjectFromAsset('7.0-project');

const extraArgs = ['--force'];
const { message } = await expectToFail(() =>
ng('update', '@angular/core', ...extraArgs),
);
if (
!message.includes(`Updating multiple major versions of '@angular/core' at once is not supported`)
) {
console.error(message);
throw new Error(
`Expected error message to include "Updating multiple major versions of '@angular/core' at once is not supported" but didn't.`,
);
}
}