Skip to content

Commit 9e69331

Browse files
alan-agius4filipesilva
authored andcommitted
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.
1 parent fbc4c3b commit 9e69331

File tree

3 files changed

+91
-74
lines changed

3 files changed

+91
-74
lines changed

packages/angular/cli/models/architect-command.ts

+3-14
Original file line numberDiff line numberDiff line change
@@ -261,20 +261,9 @@ export abstract class ArchitectCommand<
261261
}
262262

263263
const packageManager = await getPackageManager(basePath);
264-
let installSuggestion = 'Try installing with ';
265-
switch (packageManager) {
266-
case 'npm':
267-
installSuggestion += `'npm install'`;
268-
break;
269-
case 'yarn':
270-
installSuggestion += `'yarn'`;
271-
break;
272-
default:
273-
installSuggestion += `the project's package manager`;
274-
break;
275-
}
276-
277-
this.logger.warn(`Node packages may not be installed. ${installSuggestion}.`);
264+
this.logger.warn(
265+
`Node packages may not be installed. Try installing with '${packageManager} install'.`,
266+
);
278267
}
279268

280269
async run(options: ArchitectCommandOptions & Arguments) {

packages/angular/cli/utilities/config.ts

+34-38
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { json, workspaces } from '@angular-devkit/core';
1010
import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
1111
import * as os from 'os';
1212
import * as path from 'path';
13+
import { PackageManager } from '../lib/config/workspace-schema';
1314
import { findUp } from './find-up';
1415
import { JSONFile, readAndParseJson } from './json-file';
1516

@@ -298,40 +299,35 @@ export function getProjectByCwd(workspace: AngularWorkspace): string | null {
298299
return null;
299300
}
300301

301-
export async function getConfiguredPackageManager(): Promise<string | null> {
302-
const getPackageManager = (source: json.JsonValue | undefined): string | undefined => {
302+
export async function getConfiguredPackageManager(): Promise<PackageManager | null> {
303+
const getPackageManager = (source: json.JsonValue | undefined): PackageManager | null => {
303304
if (isJsonObject(source)) {
304305
const value = source['packageManager'];
305306
if (value && typeof value === 'string') {
306-
return value;
307+
return value as PackageManager;
307308
}
308309
}
309-
};
310310

311-
let result: string | undefined | null;
311+
return null;
312+
};
312313

314+
let result: PackageManager | null = null;
313315
const workspace = await getWorkspace('local');
314316
if (workspace) {
315317
const project = getProjectByCwd(workspace);
316318
if (project) {
317319
result = getPackageManager(workspace.projects.get(project)?.extensions['cli']);
318320
}
319321

320-
result = result ?? getPackageManager(workspace.extensions['cli']);
322+
result ??= getPackageManager(workspace.extensions['cli']);
321323
}
322324

323-
if (result === undefined) {
325+
if (!result) {
324326
const globalOptions = await getWorkspace('global');
325327
result = getPackageManager(globalOptions?.extensions['cli']);
326-
327-
if (!workspace && !globalOptions) {
328-
// Only check legacy if updated workspace is not found
329-
result = getLegacyPackageManager();
330-
}
331328
}
332329

333-
// Default to null
334-
return result ?? null;
330+
return result;
335331
}
336332

337333
export function migrateLegacyGlobalConfig(): boolean {
@@ -385,30 +381,6 @@ export function migrateLegacyGlobalConfig(): boolean {
385381
return false;
386382
}
387383

388-
// Fallback, check for packageManager in config file in v1.* global config.
389-
function getLegacyPackageManager(): string | null {
390-
const homeDir = os.homedir();
391-
if (homeDir) {
392-
const legacyGlobalConfigPath = path.join(homeDir, '.angular-cli.json');
393-
if (existsSync(legacyGlobalConfigPath)) {
394-
const legacy = readAndParseJson(legacyGlobalConfigPath);
395-
if (!isJsonObject(legacy)) {
396-
return null;
397-
}
398-
399-
if (
400-
legacy.packageManager &&
401-
typeof legacy.packageManager === 'string' &&
402-
legacy.packageManager !== 'default'
403-
) {
404-
return legacy.packageManager;
405-
}
406-
}
407-
}
408-
409-
return null;
410-
}
411-
412384
export async function getSchematicDefaults(
413385
collection: string,
414386
schematic: string,
@@ -480,3 +452,27 @@ export async function isWarningEnabled(warning: string): Promise<boolean> {
480452
// All warnings are enabled by default
481453
return result ?? true;
482454
}
455+
456+
// Fallback, check for packageManager in config file in v1.* global config.
457+
function getLegacyPackageManager(): string | null {
458+
const homeDir = os.homedir();
459+
if (homeDir) {
460+
const legacyGlobalConfigPath = path.join(homeDir, '.angular-cli.json');
461+
if (existsSync(legacyGlobalConfigPath)) {
462+
const legacy = readAndParseJson(legacyGlobalConfigPath);
463+
if (!isJsonObject(legacy)) {
464+
return null;
465+
}
466+
467+
if (
468+
legacy.packageManager &&
469+
typeof legacy.packageManager === 'string' &&
470+
legacy.packageManager !== 'default'
471+
) {
472+
return legacy.packageManager;
473+
}
474+
}
475+
}
476+
477+
return null;
478+
}

packages/angular/cli/utilities/package-manager.ts

+54-22
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,87 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { execSync } from 'child_process';
10-
import { existsSync } from 'fs';
9+
import { exec as execCb, execSync } from 'child_process';
10+
import { constants, promises as fs } from 'fs';
1111
import { join } from 'path';
1212
import { satisfies, valid } from 'semver';
13+
import { promisify } from 'util';
1314
import { PackageManager } from '../lib/config/workspace-schema';
1415
import { getConfiguredPackageManager } from './config';
1516

16-
function supports(name: string): boolean {
17+
const exec = promisify(execCb);
18+
async function supports(name: PackageManager): Promise<boolean> {
1719
try {
18-
execSync(`${name} --version`, { stdio: 'ignore' });
20+
await exec(`${name} --version`);
1921

2022
return true;
2123
} catch {
2224
return false;
2325
}
2426
}
2527

26-
export function supportsYarn(): boolean {
27-
return supports('yarn');
28-
}
28+
async function hasLockfile(root: string, packageManager: PackageManager): Promise<boolean> {
29+
try {
30+
let lockfileName: string;
31+
switch (packageManager) {
32+
case PackageManager.Yarn:
33+
lockfileName = 'yarn.lock';
34+
break;
35+
case PackageManager.Pnpm:
36+
lockfileName = 'pnpm-lock.yaml';
37+
break;
38+
case PackageManager.Npm:
39+
default:
40+
lockfileName = 'package-lock.json';
41+
break;
42+
}
43+
44+
await fs.access(join(root, lockfileName), constants.F_OK);
2945

30-
export function supportsNpm(): boolean {
31-
return supports('npm');
46+
return true;
47+
} catch {
48+
return false;
49+
}
3250
}
3351

3452
export async function getPackageManager(root: string): Promise<PackageManager> {
35-
let packageManager = (await getConfiguredPackageManager()) as PackageManager | null;
53+
const packageManager = await getConfiguredPackageManager();
3654
if (packageManager) {
3755
return packageManager;
3856
}
3957

40-
const hasYarn = supportsYarn();
41-
const hasYarnLock = existsSync(join(root, 'yarn.lock'));
42-
const hasNpm = supportsNpm();
43-
const hasNpmLock = existsSync(join(root, 'package-lock.json'));
58+
const [hasYarnLock, hasNpmLock, hasPnpmLock] = await Promise.all([
59+
hasLockfile(root, PackageManager.Yarn),
60+
hasLockfile(root, PackageManager.Npm),
61+
hasLockfile(root, PackageManager.Pnpm),
62+
]);
4463

64+
const hasYarn = await supports(PackageManager.Yarn);
4565
if (hasYarn && hasYarnLock && !hasNpmLock) {
46-
packageManager = PackageManager.Yarn;
47-
} else if (hasNpm && hasNpmLock && !hasYarnLock) {
48-
packageManager = PackageManager.Npm;
49-
} else if (hasYarn && !hasNpm) {
50-
packageManager = PackageManager.Yarn;
51-
} else if (hasNpm && !hasYarn) {
52-
packageManager = PackageManager.Npm;
66+
return PackageManager.Yarn;
67+
}
68+
69+
const hasPnpm = await supports(PackageManager.Pnpm);
70+
if (hasPnpm && hasPnpmLock && !hasNpmLock) {
71+
return PackageManager.Pnpm;
72+
}
73+
74+
const hasNpm = await supports(PackageManager.Npm);
75+
if (hasNpm && hasNpmLock && !hasYarnLock && !hasPnpmLock) {
76+
return PackageManager.Npm;
77+
}
78+
79+
if (hasYarn && !hasNpm && !hasPnpm) {
80+
return PackageManager.Yarn;
81+
}
82+
83+
if (hasPnpm && !hasYarn && !hasNpm) {
84+
return PackageManager.Pnpm;
5385
}
5486

5587
// TODO: This should eventually inform the user of ambiguous package manager usage.
5688
// Potentially with a prompt to choose and optionally set as the default.
57-
return packageManager || PackageManager.Npm;
89+
return PackageManager.Npm;
5890
}
5991

6092
/**

0 commit comments

Comments
 (0)