Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ dev = "npm run dev"

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

// Load specifications once — this is expensive (loads all extension specs from disk)
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/cli/services/app/config/use.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('use', () => {
}
writeFileSync(joinPath(tmp, 'package.json'), '{}')
writeFileSync(joinPath(tmp, 'shopify.app.toml'), '')
writeFileSync(joinPath(tmp, 'package-lock.json'), '')

// When
await use(options)
Expand Down
17 changes: 16 additions & 1 deletion packages/cli-kit/src/public/node/node-package-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
checkForCachedNewVersion,
inferPackageManager,
PackageManager,
npmLockfile,
} from './node-package-manager.js'
import {captureOutput, exec} from './system.js'
import {inTemporaryDirectory, mkdir, touchFile, writeFile} from './fs.js'
Expand Down Expand Up @@ -845,8 +846,9 @@ describe('writePackageJSON', () => {
describe('getPackageManager', () => {
test('finds if npm is being used', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given
// Given — pin NPM in the temp project
await writePackageJSON(tmpDir, {name: 'mock name'})
await writeFile(joinPath(tmpDir, npmLockfile), '')

// Then
const packageManager = await getPackageManager(tmpDir)
Expand Down Expand Up @@ -878,6 +880,19 @@ describe('getPackageManager', () => {
})
})

test('finds pnpm from a nested workspace package when the lockfile is only at the repo root', async () => {
await inTemporaryDirectory(async (tmpDir) => {
await writePackageJSON(tmpDir, {name: 'root'})
await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), '')
const nested = joinPath(tmpDir, 'extensions', 'cart-transformer')
await mkdir(nested)
await writePackageJSON(nested, {name: 'cart-transformer'})

const packageManager = await getPackageManager(nested)
expect(packageManager).toEqual('pnpm')
})
})

test('falls back to packageManagerFromUserAgent when no package.json is found', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// Given — no package.json in tmpDir, stub user agent to yarn
Expand Down
26 changes: 17 additions & 9 deletions packages/cli-kit/src/public/node/node-package-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {AbortError, BugError} from './error.js'
import {AbortController, AbortSignal} from './abort.js'
import {exec} from './system.js'
import {fileExists, readFile, writeFile, findPathUp, glob} from './fs.js'
import {fileExists, readFile, writeFile, findPathUp, glob, fileExistsSync} from './fs.js'
import {dirname, joinPath} from './path.js'
import {runWithTimer} from './metadata.js'
import {inferPackageManagerForGlobalCLI} from './is-global.js'
Expand Down Expand Up @@ -111,21 +111,29 @@ export function packageManagerFromUserAgent(env = process.env): PackageManager {

/**
* Returns the dependency manager used in a directory.
* Walks upward from `fromDirectory` so workspace packages (e.g. `extensions/my-fn/package.json`)
* still resolve to the repo root lockfile (`pnpm-lock.yaml`).
* If no lockfile is found, it falls back to the package manager from the user agent.
* If the package manager from the user agent is unknown, it returns 'npm'.
* @param fromDirectory - The starting directory
* @returns The dependency manager
*/
export async function getPackageManager(fromDirectory: string): Promise<PackageManager> {
const packageJsonPath = await findPathUp('package.json', {cwd: fromDirectory, type: 'file'})
if (!packageJsonPath) {
return packageManagerFromUserAgent()
let current = fromDirectory
outputDebug(outputContent`Looking for a lockfile in ${outputToken.path(current)}...`)
while (true) {
if (fileExistsSync(joinPath(current, yarnLockfile))) return 'yarn'
if (fileExistsSync(joinPath(current, pnpmLockfile))) return 'pnpm'
if (fileExistsSync(joinPath(current, bunLockfile))) return 'bun'
if (fileExistsSync(joinPath(current, npmLockfile))) return 'npm'
const parent = dirname(current)
if (parent === current) break
current = parent
}

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

if (await fileExists(joinPath(directory, yarnLockfile))) return 'yarn'
if (await fileExists(joinPath(directory, pnpmLockfile))) return 'pnpm'
if (await fileExists(joinPath(directory, bunLockfile))) return 'bun'
return 'npm'
}

Expand Down
Loading