feat(@angular/cli): use PNPM as package manager when pnpm-lock.yaml exists

While supported, we didn't automatically try to determine if PNPM was used through the lock files like we do for other package managers.
This commit is contained in:
Alan Agius 2022-02-02 12:34:17 +01:00 committed by Filipe Silva
parent fbc4c3bfde
commit 9e69331fa6
3 changed files with 91 additions and 74 deletions

View File

@ -261,20 +261,9 @@ export abstract class ArchitectCommand<
}
const packageManager = await getPackageManager(basePath);
let installSuggestion = 'Try installing with ';
switch (packageManager) {
case 'npm':
installSuggestion += `'npm install'`;
break;
case 'yarn':
installSuggestion += `'yarn'`;
break;
default:
installSuggestion += `the project's package manager`;
break;
}
this.logger.warn(`Node packages may not be installed. ${installSuggestion}.`);
this.logger.warn(
`Node packages may not be installed. Try installing with '${packageManager} install'.`,
);
}
async run(options: ArchitectCommandOptions & Arguments) {

View File

@ -10,6 +10,7 @@ import { json, workspaces } from '@angular-devkit/core';
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { PackageManager } from '../lib/config/workspace-schema';
import { findUp } from './find-up';
import { JSONFile, readAndParseJson } from './json-file';
@ -298,18 +299,19 @@ export function getProjectByCwd(workspace: AngularWorkspace): string | null {
return null;
}
export async function getConfiguredPackageManager(): Promise<string | null> {
const getPackageManager = (source: json.JsonValue | undefined): string | undefined => {
export async function getConfiguredPackageManager(): Promise<PackageManager | null> {
const getPackageManager = (source: json.JsonValue | undefined): PackageManager | null => {
if (isJsonObject(source)) {
const value = source['packageManager'];
if (value && typeof value === 'string') {
return value;
return value as PackageManager;
}
}
return null;
};
let result: string | undefined | null;
let result: PackageManager | null = null;
const workspace = await getWorkspace('local');
if (workspace) {
const project = getProjectByCwd(workspace);
@ -317,21 +319,15 @@ export async function getConfiguredPackageManager(): Promise<string | null> {
result = getPackageManager(workspace.projects.get(project)?.extensions['cli']);
}
result = result ?? getPackageManager(workspace.extensions['cli']);
result ??= getPackageManager(workspace.extensions['cli']);
}
if (result === undefined) {
if (!result) {
const globalOptions = await getWorkspace('global');
result = getPackageManager(globalOptions?.extensions['cli']);
if (!workspace && !globalOptions) {
// Only check legacy if updated workspace is not found
result = getLegacyPackageManager();
}
}
// Default to null
return result ?? null;
return result;
}
export function migrateLegacyGlobalConfig(): boolean {
@ -385,30 +381,6 @@ export function migrateLegacyGlobalConfig(): boolean {
return false;
}
// Fallback, check for packageManager in config file in v1.* global config.
function getLegacyPackageManager(): string | null {
const homeDir = os.homedir();
if (homeDir) {
const legacyGlobalConfigPath = path.join(homeDir, '.angular-cli.json');
if (existsSync(legacyGlobalConfigPath)) {
const legacy = readAndParseJson(legacyGlobalConfigPath);
if (!isJsonObject(legacy)) {
return null;
}
if (
legacy.packageManager &&
typeof legacy.packageManager === 'string' &&
legacy.packageManager !== 'default'
) {
return legacy.packageManager;
}
}
}
return null;
}
export async function getSchematicDefaults(
collection: string,
schematic: string,
@ -480,3 +452,27 @@ export async function isWarningEnabled(warning: string): Promise<boolean> {
// All warnings are enabled by default
return result ?? true;
}
// Fallback, check for packageManager in config file in v1.* global config.
function getLegacyPackageManager(): string | null {
const homeDir = os.homedir();
if (homeDir) {
const legacyGlobalConfigPath = path.join(homeDir, '.angular-cli.json');
if (existsSync(legacyGlobalConfigPath)) {
const legacy = readAndParseJson(legacyGlobalConfigPath);
if (!isJsonObject(legacy)) {
return null;
}
if (
legacy.packageManager &&
typeof legacy.packageManager === 'string' &&
legacy.packageManager !== 'default'
) {
return legacy.packageManager;
}
}
}
return null;
}

View File

@ -6,16 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/
import { execSync } from 'child_process';
import { existsSync } from 'fs';
import { exec as execCb, execSync } from 'child_process';
import { constants, promises as fs } from 'fs';
import { join } from 'path';
import { satisfies, valid } from 'semver';
import { promisify } from 'util';
import { PackageManager } from '../lib/config/workspace-schema';
import { getConfiguredPackageManager } from './config';
function supports(name: string): boolean {
const exec = promisify(execCb);
async function supports(name: PackageManager): Promise<boolean> {
try {
execSync(`${name} --version`, { stdio: 'ignore' });
await exec(`${name} --version`);
return true;
} catch {
@ -23,38 +25,68 @@ function supports(name: string): boolean {
}
}
export function supportsYarn(): boolean {
return supports('yarn');
}
async function hasLockfile(root: string, packageManager: PackageManager): Promise<boolean> {
try {
let lockfileName: string;
switch (packageManager) {
case PackageManager.Yarn:
lockfileName = 'yarn.lock';
break;
case PackageManager.Pnpm:
lockfileName = 'pnpm-lock.yaml';
break;
case PackageManager.Npm:
default:
lockfileName = 'package-lock.json';
break;
}
export function supportsNpm(): boolean {
return supports('npm');
await fs.access(join(root, lockfileName), constants.F_OK);
return true;
} catch {
return false;
}
}
export async function getPackageManager(root: string): Promise<PackageManager> {
let packageManager = (await getConfiguredPackageManager()) as PackageManager | null;
const packageManager = await getConfiguredPackageManager();
if (packageManager) {
return packageManager;
}
const hasYarn = supportsYarn();
const hasYarnLock = existsSync(join(root, 'yarn.lock'));
const hasNpm = supportsNpm();
const hasNpmLock = existsSync(join(root, 'package-lock.json'));
const [hasYarnLock, hasNpmLock, hasPnpmLock] = await Promise.all([
hasLockfile(root, PackageManager.Yarn),
hasLockfile(root, PackageManager.Npm),
hasLockfile(root, PackageManager.Pnpm),
]);
const hasYarn = await supports(PackageManager.Yarn);
if (hasYarn && hasYarnLock && !hasNpmLock) {
packageManager = PackageManager.Yarn;
} else if (hasNpm && hasNpmLock && !hasYarnLock) {
packageManager = PackageManager.Npm;
} else if (hasYarn && !hasNpm) {
packageManager = PackageManager.Yarn;
} else if (hasNpm && !hasYarn) {
packageManager = PackageManager.Npm;
return PackageManager.Yarn;
}
const hasPnpm = await supports(PackageManager.Pnpm);
if (hasPnpm && hasPnpmLock && !hasNpmLock) {
return PackageManager.Pnpm;
}
const hasNpm = await supports(PackageManager.Npm);
if (hasNpm && hasNpmLock && !hasYarnLock && !hasPnpmLock) {
return PackageManager.Npm;
}
if (hasYarn && !hasNpm && !hasPnpm) {
return PackageManager.Yarn;
}
if (hasPnpm && !hasYarn && !hasNpm) {
return PackageManager.Pnpm;
}
// 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 || PackageManager.Npm;
return PackageManager.Npm;
}
/**