From 9f2de4e7461c1064e1702654d23d7b876cb0c7d6 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Wed, 24 Feb 2021 13:22:06 -0500 Subject: [PATCH] fix(@angular/cli): remove npm 7 incompatibility notification npm 7.5.6 contains several fixes that allow it to work successfully with the Angular CLI. The minimum npm engine value is now set to support npm versions greater than 7.5.6 (npm 6 support remains unchanged). A warning will be shown to users with npm 7 versions less than 7.5.6 when used with the new, add, or update commands. --- lib/packages.ts | 2 +- packages/angular/cli/commands/new-impl.ts | 2 - .../angular/cli/models/schematic-command.ts | 12 +++- .../angular/cli/utilities/package-manager.ts | 23 +++---- tests/legacy-cli/e2e/tests/misc/npm-7.ts | 64 +++++++++++++++---- 5 files changed, 75 insertions(+), 28 deletions(-) diff --git a/lib/packages.ts b/lib/packages.ts index 76befa1d010d..1417354a9b01 100644 --- a/lib/packages.ts +++ b/lib/packages.ts @@ -84,7 +84,7 @@ function loadPackageJson(p: string) { case 'engines': pkg['engines'] = { 'node': '>= 10.13.0', - 'npm': '^6.11.0', + 'npm': '^6.11.0 || ^7.5.6', 'yarn': '>= 1.13.0', }; break; diff --git a/packages/angular/cli/commands/new-impl.ts b/packages/angular/cli/commands/new-impl.ts index b13f42b81be9..032923138992 100644 --- a/packages/angular/cli/commands/new-impl.ts +++ b/packages/angular/cli/commands/new-impl.ts @@ -24,8 +24,6 @@ export class NewCommand extends SchematicCommand { } public async run(options: NewCommandSchema & Arguments) { - await ensureCompatibleNpm(this.workspace.root); - // Register the version of the CLI in the registry. const packageJson = require('../package.json'); const version = packageJson.version; diff --git a/packages/angular/cli/models/schematic-command.ts b/packages/angular/cli/models/schematic-command.ts index e2c7803cb968..b19f95b479cf 100644 --- a/packages/angular/cli/models/schematic-command.ts +++ b/packages/angular/cli/models/schematic-command.ts @@ -40,7 +40,7 @@ import { getWorkspaceRaw, } from '../utilities/config'; import { parseJsonSchemaToOptions } from '../utilities/json-schema'; -import { getPackageManager } from '../utilities/package-manager'; +import { ensureCompatibleNpm, getPackageManager } from '../utilities/package-manager'; import { isTTY } from '../utilities/tty'; import { isPackageNameSafeForAnalytics } from './analytics'; import { BaseCommandOptions, Command } from './command'; @@ -534,6 +534,16 @@ export abstract class SchematicCommand< } }); + // Temporary compatibility check for NPM 7 + if (collectionName === '@schematics/angular' && schematicName === 'ng-new') { + if ( + !input.skipInstall && + (input.packageManager === undefined || input.packageManager === 'npm') + ) { + await ensureCompatibleNpm(this.workspace.root); + } + } + return new Promise(resolve => { workflow .execute({ diff --git a/packages/angular/cli/utilities/package-manager.ts b/packages/angular/cli/utilities/package-manager.ts index 3789c56ea23b..8c53bdf97d20 100644 --- a/packages/angular/cli/utilities/package-manager.ts +++ b/packages/angular/cli/utilities/package-manager.ts @@ -8,6 +8,7 @@ import { execSync } from 'child_process'; import { existsSync } from 'fs'; import { join } from 'path'; +import { satisfies, valid } from 'semver'; import { PackageManager } from '../lib/config/schema'; import { getConfiguredPackageManager } from './config'; @@ -56,7 +57,7 @@ export async function getPackageManager(root: string): Promise { } /** - * Checks if the npm version is version 6.x. If not, display a message and exit. + * Checks if the npm version is a supported 7.x version. If not, display a warning. */ export async function ensureCompatibleNpm(root: string): Promise { if ((await getPackageManager(root)) !== PackageManager.Npm) { @@ -64,19 +65,19 @@ export async function ensureCompatibleNpm(root: string): Promise { } try { - const version = execSync('npm --version', {encoding: 'utf8', stdio: 'pipe'}).trim(); - const major = Number(version.match(/^(\d+)\./)?.[1]); - if (major === 6) { + const versionText = execSync('npm --version', {encoding: 'utf8', stdio: 'pipe'}).trim(); + const version = valid(versionText); + if (!version) { return; } - // tslint:disable-next-line: no-console - console.error( - `npm version ${version} detected.\n` + - 'The Angular CLI currently requires npm version 6.\n\n' + - 'Please install a compatible version to proceed (`npm install --global npm@6`).\n', - ); - process.exit(3); + if (satisfies(version, '>=7 <7.5.6')) { + // tslint:disable-next-line: no-console + console.warn( + `npm version ${version} detected.` + + ' When using npm 7 with the Angular CLI, npm version 7.5.6 or higher is recommended.', + ); + } } catch { // npm is not installed } diff --git a/tests/legacy-cli/e2e/tests/misc/npm-7.ts b/tests/legacy-cli/e2e/tests/misc/npm-7.ts index 38c3b4f8e76d..6a2a2c9d7eb2 100644 --- a/tests/legacy-cli/e2e/tests/misc/npm-7.ts +++ b/tests/legacy-cli/e2e/tests/misc/npm-7.ts @@ -1,7 +1,8 @@ +import { rimraf } from '../../utils/fs'; import { ng, npm } from '../../utils/process'; import { expectToFail } from '../../utils/utils'; -const errorText = 'The Angular CLI currently requires npm version 6.'; +const warningText = 'npm version 7.5.6 or higher is recommended'; export default async function() { // Windows CI fails with permission errors when trying to replace npm @@ -11,29 +12,66 @@ export default async function() { const currentDirectory = process.cwd(); try { - // Install version 7.x - await npm('install', '--global', 'npm@7'); + // Install version >=7.5.6 + await npm('install', '--global', 'npm@>=7.5.6'); - // Ensure `ng add` exits and shows npm error + // Ensure `ng update` does not show npm warning + const { stderr: stderrUpdate1 } = await ng('update'); + if (stderrUpdate1.includes(warningText)) { + throw new Error('ng update expected to not show npm version warning.'); + } + + // Install version <7.5.6 + await npm('install', '--global', 'npm@7.4.0'); + + // Ensure `ng add` shows npm warning const { message: stderrAdd } = await expectToFail(() => ng('add')); - if (!stderrAdd.includes(errorText)) { - throw new Error('ng add expected to show npm version error.'); + if (!stderrAdd.includes(warningText)) { + throw new Error('ng add expected to show npm version warning.'); } - // Ensure `ng update` exits and shows npm error - const { message: stderrUpdate } = await expectToFail(() => ng('update')); - if (!stderrUpdate.includes(errorText)) { - throw new Error('ng update expected to show npm version error.'); + // Ensure `ng update` shows npm warning + const { stderr: stderrUpdate2 } = await ng('update'); + if (!stderrUpdate2.includes(warningText)) { + throw new Error('ng update expected to show npm version warning.'); } - // Ensure `ng new` exits and shows npm error + // Ensure `ng build` executes successfully + const { stderr: stderrBuild } = await ng('build'); + if (stderrBuild.includes(warningText)) { + throw new Error('ng build expected to not show npm version warning.'); + } + + // Ensure `ng new` shows npm warning // Must be outside the project for `ng new` process.chdir('..'); const { message: stderrNew } = await expectToFail(() => ng('new')); - if (!stderrNew.includes(errorText)) { - throw new Error('ng new expected to show npm version error.'); + if (!stderrNew.includes(warningText)) { + throw new Error('ng new expected to show npm version warning.'); + } + + // Ensure `ng new --package-manager=npm` shows npm warning + const { message: stderrNewNpm } = await expectToFail(() => ng('new', '--package-manager=npm')); + if (!stderrNewNpm.includes(warningText)) { + throw new Error('ng new expected to show npm version warning.'); + } + + // Ensure `ng new --skip-install` executes successfully + const { stderr: stderrNewSkipInstall } = await ng('new', 'npm-seven-skip', '--skip-install'); + if (stderrNewSkipInstall.includes(warningText)) { + throw new Error('ng new --skip-install expected to not show npm version warning.'); + } + + // Ensure `ng new --package-manager=yarn` executes successfully + const { stderr: stderrNewYarn } = await ng('new', 'npm-seven-yarn', '--package-manager=yarn'); + if (stderrNewYarn.includes(warningText)) { + throw new Error('ng new --package-manager=yarn expected to not show npm version warning.'); } } finally { + // Cleanup extra test projects + await rimraf('npm-seven-skip'); + await rimraf('npm-seven-yarn'); + // Change directory back process.chdir(currentDirectory);