-
Notifications
You must be signed in to change notification settings - Fork 415
Fix package manager detection in hydrogen upgrade for monorepos
#3794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
JohnCashmore
wants to merge
5
commits into
Shopify:main
Choose a base branch
from
JohnCashmore:fix/upgrade-monorepo-package-manager-detection
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+295
−11
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f55e825
test: add tests for lockfile walk-up package manager detection
JohnCashmore 59a95aa
fix: add lockfile walk-up package manager detection helper
JohnCashmore 1749500
fix: use lockfile walk-up detection in hydrogen upgrade
JohnCashmore 4ea629b
chore: add changeset for upgrade monorepo package manager fix
JohnCashmore 3483396
test: add end-to-end regression coverage for monorepo package manager…
JohnCashmore File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@shopify/cli-hydrogen': patch | ||
| --- | ||
|
|
||
| Fix `hydrogen upgrade` package manager detection in monorepos. The command now searches parent directories for a lockfile (such as a pnpm workspace root), so upgrades run with your workspace's package manager instead of falling back to npm. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import {describe, it, expect} from 'vitest'; | ||
| import {inTemporaryDirectory, writeFile, mkdir} from '@shopify/cli-kit/node/fs'; | ||
| import {joinPath} from '@shopify/cli-kit/node/path'; | ||
| import {findPackageManagerByLockfile} from './package-managers.js'; | ||
|
|
||
| describe('findPackageManagerByLockfile()', () => { | ||
| it('detects a lockfile in the directory itself (single-repo)', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(tmpDir)).resolves.toBe('pnpm'); | ||
| }); | ||
| }); | ||
|
|
||
| it('walks up to an ancestor lockfile (monorepo workspace root)', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| const appDir = joinPath(tmpDir, 'apps', 'storefront'); | ||
| await mkdir(appDir); | ||
| await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(appDir)).resolves.toBe('pnpm'); | ||
| }); | ||
| }); | ||
|
|
||
| it('detects yarn workspaces via an ancestor yarn.lock', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| const appDir = joinPath(tmpDir, 'apps', 'storefront'); | ||
| await mkdir(appDir); | ||
| await writeFile(joinPath(tmpDir, 'yarn.lock'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(appDir)).resolves.toBe('yarn'); | ||
| }); | ||
| }); | ||
|
|
||
| it('detects bun workspaces via an ancestor bun.lockb', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| const appDir = joinPath(tmpDir, 'apps', 'storefront'); | ||
| await mkdir(appDir); | ||
| await writeFile(joinPath(tmpDir, 'bun.lockb'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(appDir)).resolves.toBe('bun'); | ||
| }); | ||
| }); | ||
|
|
||
| it('prefers the nearest lockfile (app dir beats ancestor)', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| const appDir = joinPath(tmpDir, 'apps', 'storefront'); | ||
| await mkdir(appDir); | ||
| await writeFile(joinPath(tmpDir, 'pnpm-lock.yaml'), ''); | ||
| await writeFile(joinPath(appDir, 'package-lock.json'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(appDir)).resolves.toBe('npm'); | ||
| }); | ||
| }); | ||
|
|
||
| it('resolves "unknown" when no lockfile exists anywhere', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| // stopAt keeps the walk hermetic against lockfiles above the OS temp dir | ||
| await expect( | ||
| findPackageManagerByLockfile(tmpDir, {stopAt: tmpDir}), | ||
| ).resolves.toBe('unknown'); | ||
| }); | ||
| }); | ||
|
|
||
| it('detects bun.lock (text-based alternative lockfile)', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| await writeFile(joinPath(tmpDir, 'bun.lock'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(tmpDir)).resolves.toBe('bun'); | ||
| }); | ||
| }); | ||
|
|
||
| it('detects npm-shrinkwrap.json (alternative npm lockfile)', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| await writeFile(joinPath(tmpDir, 'npm-shrinkwrap.json'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(tmpDir)).resolves.toBe('npm'); | ||
| }); | ||
| }); | ||
|
|
||
| it('matches cli-kit precedence when multiple lockfiles coexist (yarn wins over npm)', async () => { | ||
| await inTemporaryDirectory(async (tmpDir) => { | ||
| await writeFile(joinPath(tmpDir, 'yarn.lock'), ''); | ||
| await writeFile(joinPath(tmpDir, 'package-lock.json'), ''); | ||
|
|
||
| await expect(findPackageManagerByLockfile(tmpDir)).resolves.toBe('yarn'); | ||
| }); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blocking: this still lets the regression pass if the upgrade command is not actually wired to the new detector everywhere.
The helper tests prove
findPackageManagerByLockfile()can walk up to an ancestor lockfile, but theupgradeNodeModules()tests mockrenderTasks, so the install/remove task callbacks that call the detector never run. The final undo-message path also isn't asserted, so it could still printnpm ifor a pnpm workspace.Let's add regression coverage that proves the command uses ancestor-lockfile detection end-to-end: execute the upgrade task callback and assert
execis called withpnpm, and assert the summary/undo instructions renderpnpm ifor the same monorepo shape.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch @fredericoo — I've added regression coverage that runs the real detector against a monorepo shape (lockfile at the workspace root, app nested without its own): it asserts the upgrade/remove tasks invoke pnpm and that the undo instructions render pnpm i rather than npm i.
I think this covers what you were after, but happy to adjust if there's a project standard for this kind of coverage I should follow instead.