mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-17 02:54:21 +08:00
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:
parent
0a959abc60
commit
b0dcfd08a0
@ -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;
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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)),
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
183
packages/angular/cli/tasks/install-package.ts
Normal file
183
packages/angular/cli/tasks/install-package.ts
Normal 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',
|
||||
};
|
||||
}
|
@ -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.');
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
@ -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.');
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
21
tests/legacy-cli/e2e/tests/commands/add/add-pwa-yarn.ts
Normal file
21
tests/legacy-cli/e2e/tests/commands/add/add-pwa-yarn.ts
Normal 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'.`);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
24
yarn.lock
24
yarn.lock
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user