Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 35 additions & 24 deletions packages/angular/cli/src/utilities/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,23 @@

import { isJsonObject, json } from '@angular-devkit/core';
import { execSync, spawn } from 'node:child_process';
import { existsSync, promises as fs, realpathSync, rmSync } from 'node:fs';
import { promises as fs, readdirSync, realpathSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { PackageManager } from '../../lib/config/workspace-schema';
import { AngularWorkspace, getProjectByCwd } from './config';
import { memoize } from './memoize';

/**
* A map of package managers to their corresponding lockfile names.
*/
const LOCKFILE_NAMES: Readonly<Record<PackageManager, string | readonly string[]>> = {
[PackageManager.Yarn]: 'yarn.lock',
[PackageManager.Pnpm]: 'pnpm-lock.yaml',
[PackageManager.Bun]: ['bun.lockb', 'bun.lock'],
[PackageManager.Npm]: 'package-lock.json',
};

interface PackageManagerOptions {
saveDev: string;
install: string;
Expand All @@ -29,7 +39,13 @@ export interface PackageManagerUtilsContext {
root: string;
}

/**
* Utilities for interacting with various package managers.
*/
export class PackageManagerUtils {
/**
* @param context The context for the package manager utilities, including workspace and global configuration.
*/
constructor(private readonly context: PackageManagerUtilsContext) {}

/** Get the package manager name. */
Expand Down Expand Up @@ -216,10 +232,12 @@ export class PackageManagerUtils {
return packageManager;
}

const hasNpmLock = this.hasLockfile(PackageManager.Npm);
const hasYarnLock = this.hasLockfile(PackageManager.Yarn);
const hasPnpmLock = this.hasLockfile(PackageManager.Pnpm);
const hasBunLock = this.hasLockfile(PackageManager.Bun);
const filesInRoot = readdirSync(this.context.root);

const hasNpmLock = this.hasLockfile(PackageManager.Npm, filesInRoot);
const hasYarnLock = this.hasLockfile(PackageManager.Yarn, filesInRoot);
const hasPnpmLock = this.hasLockfile(PackageManager.Pnpm, filesInRoot);
const hasBunLock = this.hasLockfile(PackageManager.Bun, filesInRoot);

// PERF NOTE: `this.getVersion` spawns the package a the child_process which can take around ~300ms at times.
// Therefore, we should only call this method when needed. IE: don't call `this.getVersion(PackageManager.Pnpm)` unless truly needed.
Expand Down Expand Up @@ -265,25 +283,18 @@ export class PackageManagerUtils {
return PackageManager.Npm;
}

private hasLockfile(packageManager: PackageManager): boolean {
let lockfileName: string;
switch (packageManager) {
case PackageManager.Yarn:
lockfileName = 'yarn.lock';
break;
case PackageManager.Pnpm:
lockfileName = 'pnpm-lock.yaml';
break;
case PackageManager.Bun:
lockfileName = 'bun.lockb';
break;
case PackageManager.Npm:
default:
lockfileName = 'package-lock.json';
break;
}

return existsSync(join(this.context.root, lockfileName));
/**
* Checks if a lockfile for a specific package manager exists in the root directory.
* @param packageManager The package manager to check for.
* @param filesInRoot An array of file names in the root directory.
* @returns True if the lockfile exists, false otherwise.
*/
private hasLockfile(packageManager: PackageManager, filesInRoot: string[]): boolean {
const lockfiles = LOCKFILE_NAMES[packageManager];

return typeof lockfiles === 'string'
? filesInRoot.includes(lockfiles)
: lockfiles.some((lockfile) => filesInRoot.includes(lockfile));
}

private getConfiguredPackageManager(): PackageManager | undefined {
Expand Down