Skip to content

Commit e8fd655

Browse files
Fix getPackageManager logic with workspaces
1 parent ed0a5b4 commit e8fd655

File tree

4 files changed

+36
-10
lines changed

4 files changed

+36
-10
lines changed

packages/app/src/cli/models/project/project-integration.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ dev = "npm run dev"
7979

8080
// package.json (needed by the loader)
8181
await writeFile(joinPath(dir, 'package.json'), JSON.stringify({name: 'test-app', dependencies: {}}))
82+
// Pin npm: getPackageManager walks up to ancestors if no lockfile is found
83+
await writeFile(joinPath(dir, 'package-lock.json'), '')
8284
}
8385

8486
// Load specifications once — this is expensive (loads all extension specs from disk)

packages/app/src/cli/services/app/config/use.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ describe('use', () => {
4545
}
4646
writeFileSync(joinPath(tmp, 'package.json'), '{}')
4747
writeFileSync(joinPath(tmp, 'shopify.app.toml'), '')
48+
writeFileSync(joinPath(tmp, 'package-lock.json'), '')
4849

4950
// When
5051
await use(options)

packages/cli-kit/src/public/node/node-package-manager.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
checkForCachedNewVersion,
2121
inferPackageManager,
2222
PackageManager,
23+
npmLockfile,
2324
} from './node-package-manager.js'
2425
import {captureOutput, exec} from './system.js'
2526
import {inTemporaryDirectory, mkdir, touchFile, writeFile} from './fs.js'
@@ -845,8 +846,9 @@ describe('writePackageJSON', () => {
845846
describe('getPackageManager', () => {
846847
test('finds if npm is being used', async () => {
847848
await inTemporaryDirectory(async (tmpDir) => {
848-
// Given
849+
// Given — pin NPM in the temp project
849850
await writePackageJSON(tmpDir, {name: 'mock name'})
851+
await writeFile(joinPath(tmpDir, npmLockfile), '')
850852

851853
// Then
852854
const packageManager = await getPackageManager(tmpDir)
@@ -878,6 +880,19 @@ describe('getPackageManager', () => {
878880
})
879881
})
880882

883+
test('finds pnpm from a nested workspace package when the lockfile is only at the repo root', async () => {
884+
await inTemporaryDirectory(async (tmpDir) => {
885+
await writePackageJSON(tmpDir, {name: 'root'})
886+
await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), '')
887+
const nested = joinPath(tmpDir, 'extensions', 'cart-transformer')
888+
await mkdir(nested)
889+
await writePackageJSON(nested, {name: 'cart-transformer'})
890+
891+
const packageManager = await getPackageManager(nested)
892+
expect(packageManager).toEqual('pnpm')
893+
})
894+
})
895+
881896
test('falls back to packageManagerFromUserAgent when no package.json is found', async () => {
882897
await inTemporaryDirectory(async (tmpDir) => {
883898
// Given — no package.json in tmpDir, stub user agent to yarn

packages/cli-kit/src/public/node/node-package-manager.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {AbortError, BugError} from './error.js'
22
import {AbortController, AbortSignal} from './abort.js'
33
import {exec} from './system.js'
4-
import {fileExists, readFile, writeFile, findPathUp, glob} from './fs.js'
4+
import {fileExists, readFile, writeFile, findPathUp, glob, fileExistsSync} from './fs.js'
55
import {dirname, joinPath} from './path.js'
66
import {runWithTimer} from './metadata.js'
77
import {inferPackageManagerForGlobalCLI} from './is-global.js'
@@ -111,21 +111,29 @@ export function packageManagerFromUserAgent(env = process.env): PackageManager {
111111

112112
/**
113113
* Returns the dependency manager used in a directory.
114+
* Walks upward from `fromDirectory` so workspace packages (e.g. `extensions/my-fn/package.json`)
115+
* still resolve to the repo root lockfile (`pnpm-lock.yaml`).
116+
* If no lockfile is found, it falls back to the package manager from the user agent.
117+
* If the package manager from the user agent is unknown, it returns 'npm'.
114118
* @param fromDirectory - The starting directory
115119
* @returns The dependency manager
116120
*/
117121
export async function getPackageManager(fromDirectory: string): Promise<PackageManager> {
118-
const packageJsonPath = await findPathUp('package.json', {cwd: fromDirectory, type: 'file'})
119-
if (!packageJsonPath) {
120-
return packageManagerFromUserAgent()
122+
let current = fromDirectory
123+
outputDebug(outputContent`Looking for a lockfile in ${outputToken.path(current)}...`)
124+
while (true) {
125+
if (fileExistsSync(joinPath(current, yarnLockfile))) return 'yarn'
126+
if (fileExistsSync(joinPath(current, pnpmLockfile))) return 'pnpm'
127+
if (fileExistsSync(joinPath(current, bunLockfile))) return 'bun'
128+
if (fileExistsSync(joinPath(current, npmLockfile))) return 'npm'
129+
const parent = dirname(current)
130+
if (parent === current) break
131+
current = parent
121132
}
122133

123-
const directory = dirname(packageJsonPath)
124-
outputDebug(outputContent`Obtaining the dependency manager in directory ${outputToken.path(directory)}...`)
134+
const pm: PackageManager = packageManagerFromUserAgent()
135+
if (pm !== 'unknown') return pm
125136

126-
if (await fileExists(joinPath(directory, yarnLockfile))) return 'yarn'
127-
if (await fileExists(joinPath(directory, pnpmLockfile))) return 'pnpm'
128-
if (await fileExists(joinPath(directory, bunLockfile))) return 'bun'
129137
return 'npm'
130138
}
131139

0 commit comments

Comments
 (0)