Skip to content

Commit

Permalink
feat(@angular/cli): use PNPM as package manager when pnpm-lock.yaml
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
alan-agius4 authored and filipesilva committed Feb 7, 2022
1 parent fbc4c3b commit 9e69331
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 74 deletions.
17 changes: 3 additions & 14 deletions packages/angular/cli/models/architect-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
72 changes: 34 additions & 38 deletions packages/angular/cli/utilities/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -298,40 +299,35 @@ 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;
}
}
};

let result: string | undefined | null;
return null;
};

let result: PackageManager | null = null;
const workspace = await getWorkspace('local');
if (workspace) {
const project = getProjectByCwd(workspace);
if (project) {
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 {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
76 changes: 54 additions & 22 deletions packages/angular/cli/utilities/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,87 @@
* 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 {
return false;
}
}

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;
}

await fs.access(join(root, lockfileName), constants.F_OK);

export function supportsNpm(): boolean {
return supports('npm');
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;
}

/**
Expand Down

0 comments on commit 9e69331

Please sign in to comment.