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.
This commit is contained in:
Alan Agius 2019-10-22 08:49:44 +02:00 committed by vikerman
parent 0a959abc60
commit b0dcfd08a0
16 changed files with 342 additions and 186 deletions

View File

@ -48,7 +48,7 @@ export interface ProcessOutput {
write(buffer: string | Buffer): boolean;
}
export declare function resolve(x: string, options: ResolveOptions): string;
export declare function resolve(packageName: string, options: ResolveOptions): string;
export interface ResolveOptions {
basedir: string;

View File

@ -103,6 +103,7 @@
"@types/minimist": "^1.2.0",
"@types/node": "10.12.30",
"@types/request": "^2.47.1",
"@types/rimraf": "^2.0.2",
"@types/semver": "^6.0.0",
"@types/webpack": "^4.32.1",
"@types/webpack-dev-server": "^3.1.7",

View File

@ -37,11 +37,11 @@ ts_library(
"@npm//@types/debug",
"@npm//@types/node",
"@npm//@types/inquirer",
"@npm//@types/rimraf",
"@npm//@types/semver",
"@npm//@types/universal-analytics",
"@npm//@types/uuid",
"@npm//ansi-colors",
"@npm//rxjs",
],
)
@ -52,6 +52,7 @@ ts_library(
":add_schema",
":analytics_schema",
":build_schema",
":cli_schema",
":config_schema",
":deploy_schema",
":deprecated_schema",
@ -71,6 +72,11 @@ ts_library(
],
)
ts_json_schema(
name = "cli_schema",
src = "lib/config/schema.json",
)
ts_json_schema(
name = "analytics_schema",
src = "commands/analytics.json",

View File

@ -9,11 +9,11 @@ import { analytics, tags } from '@angular-devkit/core';
import { NodePackageDoesNotSupportSchematics } from '@angular-devkit/schematics/tools';
import { dirname, join } from 'path';
import { intersects, prerelease, rcompare, satisfies, valid, validRange } from 'semver';
import { PackageManager } from '../lib/config/schema';
import { isPackageNameSafeForAnalytics } from '../models/analytics';
import { Arguments } from '../models/interface';
import { RunSchematicOptions, SchematicCommand } from '../models/schematic-command';
import npmInstall from '../tasks/npm-install';
import npmUninstall from '../tasks/npm-uninstall';
import { installPackage, installTempPackage } from '../tasks/install-package';
import { colors } from '../utilities/color';
import { getPackageManager } from '../utilities/package-manager';
import {
@ -57,7 +57,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
}
const packageManager = await getPackageManager(this.workspace.root);
const usingYarn = packageManager === 'yarn';
const usingYarn = packageManager === PackageManager.Yarn;
if (packageIdentifier.type === 'tag' && !packageIdentifier.rawSpec) {
// only package name provided; search for viable version
@ -115,7 +115,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
}
let collectionName = packageIdentifier.name;
let dependencyType: NgAddSaveDepedency | undefined;
let savePackage: NgAddSaveDepedency | undefined;
try {
const manifest = await fetchPackageManifest(packageIdentifier, this.logger, {
@ -124,7 +124,7 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
usingYarn,
});
dependencyType = manifest['ng-add'] && manifest['ng-add'].save;
savePackage = manifest['ng-add'] && manifest['ng-add'].save;
collectionName = manifest.name;
if (await this.hasMismatchedPeer(manifest)) {
@ -138,16 +138,13 @@ export class AddCommand extends SchematicCommand<AddCommandSchema> {
return 1;
}
await npmInstall(packageIdentifier.raw, this.logger, packageManager, dependencyType);
const schematicResult = await this.executeSchematic(collectionName, options['--']);
if (dependencyType === false) {
// Uninstall the package if it was not meant to be retained.
return npmUninstall(packageIdentifier.raw, this.logger, packageManager);
if (savePackage === false) {
installTempPackage(packageIdentifier.raw, this.logger, packageManager);
} else {
installPackage(packageIdentifier.raw, this.logger, packageManager, savePackage);
}
return schematicResult;
return this.executeSchematic(collectionName, options['--']);
}
async reportAnalytics(

View File

@ -13,14 +13,17 @@ import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
import { PackageManager } from '../lib/config/schema';
import { Command } from '../models/command';
import { Arguments } from '../models/interface';
import { runTempPackageBin } from '../tasks/install-package';
import { colors } from '../utilities/color';
import { getPackageManager } from '../utilities/package-manager';
import {
PackageIdentifier,
PackageManifest,
PackageMetadata,
fetchPackageManifest,
fetchPackageMetadata,
} from '../utilities/package-metadata';
import { PackageTreeNode, findNodeDependencies, readPackageTree } from '../utilities/package-tree';
@ -38,22 +41,23 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
public readonly allowMissingWorkspace = true;
private workflow: NodeWorkflow;
private packageManager: PackageManager;
async initialize() {
this.packageManager = await getPackageManager(this.workspace.root);
this.workflow = new NodeWorkflow(
new virtualFs.ScopedHost(new NodeJsSyncHost(), normalize(this.workspace.root)),
{
packageManager: await getPackageManager(this.workspace.root),
packageManager: this.packageManager,
root: normalize(this.workspace.root),
},
);
this.workflow.engineHost.registerOptionsTransform(
validateOptionsWithSchema(this.workflow.registry),
);
}
async executeSchematic(
private async executeSchematic(
collection: string,
schematic: string,
options = {},
@ -127,7 +131,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
}
}
async executeMigrations(
private async executeMigrations(
packageName: string,
collectionPath: string,
range: semver.Range,
@ -190,6 +194,21 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
// tslint:disable-next-line:no-big-function
async run(options: UpdateCommandSchema & Arguments) {
// Check if the current installed CLI version is older than the latest version.
if (await this.checkCLILatestVersion(options.verbose)) {
this.logger.warn(
'The installed Angular CLI version is older than the latest published version.\n' +
'Installing a temporary version to perform the update.',
);
return runTempPackageBin(
'@angular/cli@latest',
this.logger,
this.packageManager,
process.argv.slice(2),
);
}
const packages: PackageIdentifier[] = [];
for (const request of options['--'] || []) {
try {
@ -252,8 +271,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
}
}
const packageManager = await getPackageManager(this.workspace.root);
this.logger.info(`Using package manager: '${packageManager}'`);
this.logger.info(`Using package manager: '${this.packageManager}'`);
// Special handling for Angular CLI 1.x migrations
if (
@ -293,7 +311,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
force: options.force || false,
next: options.next || false,
verbose: options.verbose || false,
packageManager,
packageManager: this.packageManager,
packages: options.all ? Object.keys(rootDependencies) : [],
});
@ -513,7 +531,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
const { success } = await this.executeSchematic('@schematics/update', 'update', {
verbose: options.verbose || false,
force: options.force || false,
packageManager,
packageManager: this.packageManager,
packages: packagesToUpdate,
migrateExternal: true,
});
@ -549,7 +567,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
return success ? 0 : 1;
}
checkCleanGit() {
private checkCleanGit(): boolean {
try {
const topLevel = execSync('git rev-parse --show-toplevel', { encoding: 'utf8', stdio: 'pipe' });
const result = execSync('git status --porcelain', { encoding: 'utf8', stdio: 'pipe' });
@ -573,7 +591,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
return true;
}
createCommit(message: string, files: string[]) {
private createCommit(message: string, files: string[]) {
try {
execSync('git add -A ' + files.join(' '), { encoding: 'utf8', stdio: 'pipe' });
@ -581,7 +599,7 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
} catch (error) {}
}
findCurrentGitSha(): string | null {
private findCurrentGitSha(): string | null {
try {
const result = execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: 'pipe' });
@ -590,6 +608,25 @@ export class UpdateCommand extends Command<UpdateCommandSchema> {
return null;
}
}
/**
* Checks if the current installed CLI version is older than the latest version.
* @returns `true` when the installed version is older.
*/
private async checkCLILatestVersion(verbose = false): Promise<boolean> {
const { version: installedCLIVersion } = require('../package.json');
const LatestCLIManifest = await fetchPackageManifest(
'@angular/cli@latest',
this.logger,
{
verbose,
usingYarn: this.packageManager === PackageManager.Yarn,
},
);
return semver.lt(installedCLIVersion, LatestCLIManifest.version);
}
}
function coerceVersionNumber(version: string | undefined): string | null {

View File

@ -13,9 +13,15 @@ import { colors, supportsColor } from '../../utilities/color';
import { getWorkspaceRaw } from '../../utilities/config';
import { getWorkspaceDetails } from '../../utilities/project';
const debugEnv = process.env['NG_DEBUG'];
const isDebug =
debugEnv !== undefined &&
debugEnv !== '0' &&
debugEnv.toLowerCase() !== 'false';
// tslint:disable: no-console
export default async function(options: { testing?: boolean; cliArgs: string[] }) {
const logger = createConsoleLogger(false, process.stdout, process.stderr, {
const logger = createConsoleLogger(isDebug, process.stdout, process.stderr, {
info: s => (supportsColor ? s : colors.unstyle(s)),
debug: s => (supportsColor ? s : colors.unstyle(s)),
warn: s => (supportsColor ? colors.bold.yellow(s) : colors.unstyle(s)),

View File

@ -19,17 +19,15 @@ import { isWarningEnabled } from '../utilities/config';
const packageJson = require('../package.json');
function _fromPackageJson(cwd?: string) {
cwd = cwd || process.cwd();
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 json = JSON.parse(content);
if (json['version']) {
return new SemVer(json['version']);
const { version } = JSON.parse(content);
if (version) {
return new SemVer(version);
}
}
}
@ -78,6 +76,20 @@ if (process.env['NG_CLI_PROFILING']) {
}
(async () => {
const disableVersionCheckEnv = process.env['NG_DISABLE_VERSION_CHECK'];
/**
* Disable CLI version mismatch checks and forces usage of the invoked CLI
* instead of invoking the local installed version.
*/
const disableVersionCheck =
disableVersionCheckEnv !== undefined &&
disableVersionCheckEnv !== '0' &&
disableVersionCheckEnv.toLowerCase() !== 'false';
if (disableVersionCheck) {
return (await import('./cli')).default;
}
let cli;
try {
const projectLocalCli = require.resolve('@angular/cli', { paths: [process.cwd()] });
@ -116,13 +128,13 @@ if (process.env['NG_CLI_PROFILING']) {
// No error implies a projectLocalCli, which will load whatever
// version of ng-cli you have installed in a local package.json
cli = require(projectLocalCli);
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
// path to this script file (which is likely a globally installed
// npm package). Most common cause for hitting this is `ng new`
cli = require('./cli');
cli = await import('./cli');
}
if ('default' in cli) {

View File

@ -40,6 +40,7 @@
"open": "7.0.0",
"pacote": "9.5.8",
"read-package-tree": "5.3.1",
"rimraf": "3.0.0",
"semver": "6.3.0",
"symbol-observable": "1.2.0",
"universal-analytics": "^0.4.20",

View File

@ -0,0 +1,183 @@
/**
* @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 { logging } from '@angular-devkit/core';
import { spawnSync } from 'child_process';
import {
existsSync,
mkdtempSync,
readFileSync,
realpathSync,
} from 'fs';
import { tmpdir } from 'os';
import { join, resolve } from 'path';
import * as rimraf from 'rimraf';
import { PackageManager } from '../lib/config/schema';
import { colors } from '../utilities/color';
import { NgAddSaveDepedency } from '../utilities/package-metadata';
interface PackageManagerOptions {
silent: string;
saveDev: string;
install: string;
prefix: string;
}
export function installPackage(
packageName: string,
logger: logging.Logger,
packageManager: PackageManager = PackageManager.Npm,
save: Exclude<NgAddSaveDepedency, false> = true,
extraArgs: string[] = [],
global = false,
) {
const packageManagerArgs = getPackageManagerArguments(packageManager);
const installArgs: string[] = [
packageManagerArgs.install,
packageName,
packageManagerArgs.silent,
];
logger.info(colors.green(`Installing packages for tooling via ${packageManager}.`));
if (save === 'devDependencies') {
installArgs.push(packageManagerArgs.saveDev);
}
if (global) {
if (packageManager === PackageManager.Yarn) {
installArgs.unshift('global');
} else {
installArgs.push('--global');
}
}
const { status } = spawnSync(
packageManager,
[
...installArgs,
...extraArgs,
],
{
stdio: 'inherit',
shell: true,
},
);
if (status !== 0) {
throw new Error('Package install failed, see above.');
}
logger.info(colors.green(`Installed packages for tooling via ${packageManager}.`));
}
export function installTempPackage(
packageName: string,
logger: logging.Logger,
packageManager: PackageManager = PackageManager.Npm,
): string {
const tempPath = mkdtempSync(join(realpathSync(tmpdir()), '.ng-temp-packages-'));
// clean up temp directory on process exit
process.on('exit', () => rimraf.sync(tempPath));
// setup prefix/global modules path
const packageManagerArgs = getPackageManagerArguments(packageManager);
const installArgs: string[] = [
packageManagerArgs.prefix,
tempPath,
];
installPackage(packageName, logger, packageManager, true, installArgs, true);
let tempNodeModules: string;
if (packageManager !== PackageManager.Yarn && process.platform !== 'win32') {
// Global installs on Unix systems go to {prefix}/lib/node_modules.
// Global installs on Windows go to {prefix}/node_modules (that is, no lib folder.)
tempNodeModules = join(tempPath, 'lib', 'node_modules');
} else {
tempNodeModules = join(tempPath, 'node_modules');
}
// Needed to resolve schematics from this location since we use a custom
// resolve strategy in '@angular/devkit-core/node'
// todo: this should be removed when we change the resolutions to use require.resolve
process.env.NG_TEMP_MODULES_DIR = tempNodeModules;
return tempNodeModules;
}
export function runTempPackageBin(
packageName: string,
logger: logging.Logger,
packageManager: PackageManager = PackageManager.Npm,
args: string[] = [],
): number {
const tempNodeModulesPath = installTempPackage(packageName, logger, packageManager);
// Remove version/tag etc... from package name
// Ex: @angular/cli@latest -> @angular/cli
const packageNameNoVersion = packageName.substring(0, packageName.lastIndexOf('@'));
const pkgLocation = join(tempNodeModulesPath, packageNameNoVersion);
const packageJsonPath = join(pkgLocation, 'package.json');
// Get a binary location for this package
let binPath: string | undefined;
if (existsSync(packageJsonPath)) {
const content = readFileSync(packageJsonPath, 'utf-8');
if (content) {
const { bin = {} } = JSON.parse(content);
const binKeys = Object.keys(bin);
if (binKeys.length) {
binPath = resolve(pkgLocation, bin[binKeys[0]]);
}
}
}
if (!binPath) {
throw new Error(`Cannot locate bin for temporary package: ${packageNameNoVersion}.`);
}
const argv = [
binPath,
...args,
];
const { status, error } = spawnSync('node', argv, {
stdio: 'inherit',
shell: true,
env: {
...process.env,
NG_DISABLE_VERSION_CHECK: 'true',
},
});
if (status === null && error) {
throw error;
}
return status || 0;
}
function getPackageManagerArguments(packageManager: PackageManager): PackageManagerOptions {
return packageManager === PackageManager.Yarn
? {
silent: '--silent',
saveDev: '--dev',
install: 'add',
prefix: '--global-folder',
}
: {
silent: '--quiet',
saveDev: '--save-dev',
install: 'install',
prefix: '--prefix',
};
}

View File

@ -1,68 +0,0 @@
/**
* @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 { logging } from '@angular-devkit/core';
import { spawn } from 'child_process';
import { colors } from '../utilities/color';
import { NgAddSaveDepedency } from '../utilities/package-metadata';
export default async function(
packageName: string,
logger: logging.Logger,
packageManager: string,
save: NgAddSaveDepedency = true,
) {
const installArgs: string[] = [];
switch (packageManager) {
case 'cnpm':
case 'pnpm':
case 'npm':
installArgs.push('install');
break;
case 'yarn':
installArgs.push('add');
break;
default:
packageManager = 'npm';
installArgs.push('install');
break;
}
logger.info(colors.green(`Installing packages for tooling via ${packageManager}.`));
if (packageName) {
installArgs.push(packageName);
}
if (!save) {
// IMP: yarn doesn't have a no-save option
installArgs.push('--no-save');
}
if (save === 'devDependencies') {
installArgs.push(packageManager === 'yarn' ? '--dev' : '--save-dev');
}
installArgs.push('--quiet');
await new Promise((resolve, reject) => {
spawn(packageManager, installArgs, { stdio: 'inherit', shell: true }).on(
'close',
(code: number) => {
if (code === 0) {
logger.info(colors.green(`Installed packages for tooling via ${packageManager}.`));
resolve();
} else {
reject('Package install failed, see above.');
}
},
);
});
}

View File

@ -1,53 +0,0 @@
/**
* @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 { logging } from '@angular-devkit/core';
import { spawn } from 'child_process';
import { colors } from '../utilities/color';
export default async function(
packageName: string,
logger: logging.Logger,
packageManager: string,
) {
const installArgs: string[] = [];
switch (packageManager) {
case 'cnpm':
case 'pnpm':
case 'npm':
installArgs.push('uninstall');
break;
case 'yarn':
installArgs.push('remove');
break;
default:
packageManager = 'npm';
installArgs.push('uninstall');
break;
}
installArgs.push(packageName, '--quiet');
logger.info(colors.green(`Uninstalling packages for tooling via ${packageManager}.`));
await new Promise((resolve, reject) => {
spawn(packageManager, installArgs, { stdio: 'inherit', shell: true }).on(
'close',
(code: number) => {
if (code === 0) {
logger.info(colors.green(`Uninstalling packages for tooling via ${packageManager}.`));
resolve();
} else {
reject('Package uninstallation failed, see above.');
}
},
);
});
}

View File

@ -8,6 +8,7 @@
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { join } from 'path';
import { PackageManager } from '../lib/config/schema';
import { getConfiguredPackageManager } from './config';
function supports(name: string): boolean {
@ -28,8 +29,8 @@ export function supportsNpm(): boolean {
return supports('npm');
}
export async function getPackageManager(root: string): Promise<string> {
let packageManager = await getConfiguredPackageManager();
export async function getPackageManager(root: string): Promise<PackageManager> {
let packageManager = await getConfiguredPackageManager() as PackageManager | null;
if (packageManager) {
return packageManager;
}
@ -40,16 +41,16 @@ export async function getPackageManager(root: string): Promise<string> {
const hasNpmLock = existsSync(join(root, 'package-lock.json'));
if (hasYarn && hasYarnLock && !hasNpmLock) {
packageManager = 'yarn';
packageManager = PackageManager.Yarn;
} else if (hasNpm && hasNpmLock && !hasYarnLock) {
packageManager = 'npm';
packageManager = PackageManager.Npm;
} else if (hasYarn && !hasNpm) {
packageManager = 'yarn';
packageManager = PackageManager.Yarn;
} else if (hasNpm && !hasYarn) {
packageManager = 'npm';
packageManager = PackageManager.Npm;
}
// TODO: This should eventually inform the user of ambiguous package manager usage.
// Potentially with a prompt to choose and optionally set as the default.
return packageManager || 'npm';
return packageManager || PackageManager.Npm;
}

View File

@ -120,15 +120,15 @@ export function setResolveHook(
/**
* Resolve a package using a logic similar to npm require.resolve, but with more options.
* @param x The package name to resolve.
* @param packageName The package name to resolve.
* @param options A list of options. See documentation of those options.
* @returns {string} Path to the index to include, or if `resolvePackageJson` option was
* passed, a path to that file.
* @throws {ModuleNotFoundException} If no module with that name was found anywhere.
*/
export function resolve(x: string, options: ResolveOptions): string {
export function resolve(packageName: string, options: ResolveOptions): string {
if (_resolveHook) {
const maybe = _resolveHook(x, options);
const maybe = _resolveHook(packageName, options);
if (maybe) {
return maybe;
}
@ -141,9 +141,9 @@ export function resolve(x: string, options: ResolveOptions): string {
options.paths = options.paths || [];
if (/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/.test(x)) {
let res = path.resolve(basePath, x);
if (x === '..' || x.slice(-1) === '/') {
if (/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\/\\])/.test(packageName)) {
let res = path.resolve(basePath, packageName);
if (packageName === '..' || packageName.slice(-1) === '/') {
res += '/';
}
@ -152,7 +152,7 @@ export function resolve(x: string, options: ResolveOptions): string {
return m;
}
} else {
const n = loadNodeModulesSync(x, basePath);
const n = loadNodeModulesSync(packageName, basePath);
if (n) {
return n;
}
@ -165,7 +165,7 @@ export function resolve(x: string, options: ResolveOptions): string {
const localDir = path.dirname(caller);
if (localDir !== options.basedir) {
try {
return resolve(x, {
return resolve(packageName, {
...options,
checkLocal: false,
checkGlobal: false,
@ -186,7 +186,7 @@ export function resolve(x: string, options: ResolveOptions): string {
const globalDir = path.dirname(_getGlobalNodeModules());
if (globalDir !== options.basedir) {
try {
return resolve(x, {
return resolve(packageName, {
...options,
checkLocal: false,
checkGlobal: false,
@ -201,7 +201,7 @@ export function resolve(x: string, options: ResolveOptions): string {
}
}
throw new ModuleNotFoundException(x, basePath);
throw new ModuleNotFoundException(packageName, basePath);
function loadAsFileSync(x: string): string | null {
if (isFile(x)) {
@ -245,11 +245,11 @@ export function resolve(x: string, options: ResolveOptions): string {
function loadNodeModulesSync(x: string, start: string): string | null {
const dirs = nodeModulesPaths(start, options);
for (const dir of dirs) {
const m = loadAsFileSync(path.join(dir, '/', x));
const m = loadAsFileSync(path.join(dir, x));
if (m) {
return m;
}
const n = loadAsDirectorySync(path.join(dir, '/', x));
const n = loadAsDirectorySync(path.join(dir, x));
if (n) {
return n;
}
@ -301,6 +301,12 @@ export function resolve(x: string, options: ResolveOptions): string {
}));
}, []);
if (process.env.NG_TEMP_MODULES_DIR) {
// When running from a temporary installations, node_modules have to be resolved
// differently and they should be prefered over others.
dirs.unshift(process.env.NG_TEMP_MODULES_DIR);
}
return opts && opts.paths ? dirs.concat(opts.paths) : dirs;
}
}

View File

@ -0,0 +1,21 @@
import { join } from 'path';
import { expectFileToExist, readFile, rimraf } from '../../../utils/fs';
import { ng } from '../../../utils/process';
export default async function () {
// forcibly remove in case another test doesn't clean itself up
await rimraf('node_modules/@angular/pwa');
// set yarn as package manager
await ng('config', 'cli.packageManager', 'yarn');
await ng('add', '@angular/pwa');
await expectFileToExist(join(process.cwd(), 'src/manifest.webmanifest'));
// Angular PWA doesn't install as a dependency
const { dependencies, devDependencies } = JSON.parse(await readFile(join(process.cwd(), 'package.json')));
const hasPWADep = Object.keys({ ...dependencies, ...devDependencies })
.some(d => d === '@angular/pwa');
if (hasPWADep) {
throw new Error(`Expected 'package.json' not to contain a dependency on '@angular/pwa'.`);
}
}

View File

@ -1,13 +1,11 @@
import { join } from 'path';
import { expectFileToExist, expectFileToMatch, rimraf, readFile } from '../../../utils/fs';
import { expectFileToExist, readFile, rimraf } from '../../../utils/fs';
import { ng } from '../../../utils/process';
export default async function () {
// forcibly remove in case another test doesn't clean itself up
await rimraf('node_modules/@angular/pwa');
await ng('add', '@angular/pwa');
await expectFileToExist(join(process.cwd(), 'src/manifest.webmanifest'));
// Angular PWA doesn't install as a dependency

View File

@ -1041,7 +1041,7 @@
resolved "https://registry.yarnpkg.com/@types/find-cache-dir/-/find-cache-dir-2.0.0.tgz#6ee79b947b8e51ce8c565fc8278822b2605609db"
integrity sha512-LHAReDNv7IVTE2Q+nPcRBgUZAUKPJIvR7efMrWgx69442KMoMK+QYjtTtK9WGUdaqUYVLkd/0cvCfb55LFWsVw==
"@types/glob@^7.0.0", "@types/glob@^7.1.1":
"@types/glob@*", "@types/glob@^7.0.0", "@types/glob@^7.1.1":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
@ -1177,6 +1177,14 @@
dependencies:
"@types/node" "*"
"@types/rimraf@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"
integrity sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==
dependencies:
"@types/glob" "*"
"@types/node" "*"
"@types/rx-core-binding@*":
version "4.0.4"
resolved "https://registry.yarnpkg.com/@types/rx-core-binding/-/rx-core-binding-4.0.4.tgz#d969d32f15a62b89e2862c17b3ee78fe329818d3"
@ -8950,6 +8958,13 @@ rfdc@^1.1.2, rfdc@^1.1.4:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
rimraf@3.0.0, rimraf@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==
dependencies:
glob "^7.1.3"
rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@ -8957,13 +8972,6 @@ rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimra
dependencies:
glob "^7.1.3"
rimraf@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b"
integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg==
dependencies:
glob "^7.1.3"
rimraf@~2.4.0:
version "2.4.5"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da"