@@ -57,7 +57,7 @@ export type DependencyType = 'dev' | 'prod' | 'peer'
5757 */
5858export const packageManager = [ 'yarn' , 'npm' , 'pnpm' , 'bun' , 'homebrew' , 'unknown' ] as const
5959export type PackageManager = ( typeof packageManager ) [ number ]
60- type ProjectPackageManager = Extract < PackageManager , 'yarn' | 'npm' | 'pnpm' | 'bun' >
60+ export type ProjectPackageManager = Extract < PackageManager , 'yarn' | 'npm' | 'pnpm' | 'bun' >
6161
6262/**
6363 * Returns an abort error that's thrown when the package manager can't be determined.
@@ -145,6 +145,17 @@ function packageManagerBinaryCommand(
145145 }
146146}
147147
148+ function getProjectPackageManagerAtDirectory ( directory : string ) : ProjectPackageManager | undefined {
149+ if ( fileExistsSync ( joinPath ( directory , yarnLockfile ) ) ) return 'yarn'
150+ if ( fileExistsSync ( joinPath ( directory , pnpmLockfile ) ) || fileExistsSync ( joinPath ( directory , pnpmWorkspaceFile ) ) ) {
151+ return 'pnpm'
152+ }
153+ if ( hasBunLockfileSync ( directory ) ) return 'bun'
154+ if ( fileExistsSync ( joinPath ( directory , npmLockfile ) ) ) return 'npm'
155+
156+ return undefined
157+ }
158+
148159/**
149160 * Returns the dependency manager used in a directory.
150161 * Walks upward from `fromDirectory` so workspace packages (e.g. `extensions/my-fn/package.json`)
@@ -158,12 +169,9 @@ export async function getPackageManager(fromDirectory: string): Promise<PackageM
158169 let current = fromDirectory
159170 outputDebug ( outputContent `Looking for a lockfile in ${ outputToken . path ( current ) } ...` )
160171 while ( true ) {
161- if ( fileExistsSync ( joinPath ( current , yarnLockfile ) ) ) return 'yarn'
162- if ( fileExistsSync ( joinPath ( current , pnpmLockfile ) ) || fileExistsSync ( joinPath ( current , pnpmWorkspaceFile ) ) ) {
163- return 'pnpm'
164- }
165- if ( hasBunLockfileSync ( current ) ) return 'bun'
166- if ( fileExistsSync ( joinPath ( current , npmLockfile ) ) ) return 'npm'
172+ const detectedPackageManager = getProjectPackageManagerAtDirectory ( current )
173+ if ( detectedPackageManager ) return detectedPackageManager
174+
167175 const parent = dirname ( current )
168176 if ( parent === current ) break
169177 current = parent
@@ -175,6 +183,25 @@ export async function getPackageManager(fromDirectory: string): Promise<PackageM
175183 return 'npm'
176184}
177185
186+ /**
187+ * Resolves the package manager for a directory already known to be the project root.
188+ *
189+ * Use this when the caller already knows the root directory and wants to inspect that root
190+ * directly rather than walking upward from a child directory.
191+ *
192+ * @param rootDirectory - The known project root directory.
193+ * @returns The package manager detected from root markers, defaulting to `npm` when no marker exists.
194+ * @throws PackageJsonNotFoundError if the provided directory does not contain a package.json.
195+ */
196+ export async function getPackageManagerForProjectRoot ( rootDirectory : string ) : Promise < ProjectPackageManager > {
197+ const packageJsonPath = joinPath ( rootDirectory , 'package.json' )
198+ if ( ! ( await fileExists ( packageJsonPath ) ) ) {
199+ throw new PackageJsonNotFoundError ( rootDirectory )
200+ }
201+
202+ return getProjectPackageManagerAtDirectory ( rootDirectory ) ?? 'npm'
203+ }
204+
178205/**
179206 * Builds the command and argv needed to execute a local binary using the package manager
180207 * detected from the provided directory or its ancestors.
@@ -733,7 +760,7 @@ export async function findUpAndReadPackageJson(fromDirectory: string): Promise<{
733760}
734761
735762export async function addResolutionOrOverride ( directory : string , dependencies : Record < string , string > ) : Promise < void > {
736- const packageManager = await getPackageManager ( directory )
763+ const packageManager = await getPackageManagerForProjectRoot ( directory )
737764 const packageJsonPath = joinPath ( directory , 'package.json' )
738765 const packageJsonContent = await readAndParsePackageJson ( packageJsonPath )
739766
0 commit comments