From d22feef8bf631893eef240ec8c584d4a591f6ea5 Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:50:18 +0000 Subject: [PATCH] fix(@angular/cli): improve bun lockfile detection and optimize lockfile checks This commit refactors the package manager lockfile detection logic to: - Introduce a `LOCKFILE_NAMES` constant for better maintainability and clarity. - Enhance Bun lockfile detection by checking for both `bun.lockb` and `bun.lock`. - Optimize lockfile checks by reading the root directory files once and passing them to the `hasLockfile` method, reducing redundant file system operations. This addresses issues where Bun lockfiles might not have been correctly identified. closes #31128 --- .../cli/src/utilities/package-manager.ts | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/angular/cli/src/utilities/package-manager.ts b/packages/angular/cli/src/utilities/package-manager.ts index d95205f95184..430ff91c7d48 100644 --- a/packages/angular/cli/src/utilities/package-manager.ts +++ b/packages/angular/cli/src/utilities/package-manager.ts @@ -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> = { + [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; @@ -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. */ @@ -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. @@ -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 {