Skip to content

Fix CLI monorepo detection when the repo path contains spaces - #3989

Open
AryanKansagara wants to merge 2 commits into
Shopify:mainfrom
AryanKansagara:fix-cli-monorepo-path-with-spaces
Open

Fix CLI monorepo detection when the repo path contains spaces#3989
AryanKansagara wants to merge 2 commits into
Shopify:mainfrom
AryanKansagara:fix-cli-monorepo-path-with-spaces

Conversation

@AryanKansagara

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Fixes #3988

packages/cli/src/lib/build.ts derives the monorepo root from import.meta.url:

const monorepoPackagesPath = new URL('../../..', import.meta.url).pathname;

URL.pathname is percent-encoded. A checkout at /Users/me/Open Source/hydrogen produces /Users/me/Open%20Source/hydrogen/packages/, which does not exist on disk, so the existsSync check for templates/skeleton fails and isHydrogenMonorepo becomes false. Building packages/cli then fails in tsup.config.ts with "Trying to use skeleton source dir outside of Hydrogen monorepo", and the CLI test suite fails as well.

Measured on one machine, toggling only that expression:

packages/cli build packages/cli tests
Current main fails 11 files / 39 tests failed
With this change succeeds 57 files / 434 tests passed

This affects contributors only. Installed npm packages are unaffected, since isHydrogenMonorepo is false there regardless. The failure is easy to misread, because the error names the monorepo layout rather than the directory name.

I kept fileURLToPath out of this path, since the existing comment says it is avoided deliberately for Windows backslash handling. Decoding the pathname preserves that while fixing the encoding.

I checked for the same pattern elsewhere. packages/hydrogen/src/vite/plugin.ts also reads new URL(...).pathname, but is not affected: it only tests endsWith('/packages/'), and an encoded segment always appears earlier in the path. I left it alone rather than making an unnecessary change.

WHAT is this pull request doing?

  • Decodes the percent-encoded pathname before it is used for monorepo detection, via a small exported decodedPathname helper.
  • Adds packages/cli/src/lib/build.test.ts covering an encoded path and a plain one. I verified the test fails if the decodeURIComponent call is removed.
  • Adds a patch changeset for @shopify/cli-hydrogen and @shopify/create-hydrogen. build.ts is reachable from the init path through lib/template-downloader.ts, so per CLAUDE.md both are bumped.

The helper exists mainly so the behaviour is testable, since the original expression runs at module scope off import.meta.url and cannot be exercised directly.

HOW to test your changes?

git clone https://github.com/Shopify/hydrogen.git "/tmp/Open Source/hydrogen"
cd "/tmp/Open Source/hydrogen"
pnpm install
pnpm --dir packages/cli build   # fails on main, succeeds with this change
pnpm --dir packages/cli test

The added unit test also covers the decoding directly:

pnpm --dir packages/cli test src/lib/build.test.ts

Checklist

  • I've read the Contributing Guidelines
  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've added a changeset if this PR contains user-facing or functional changes. Test changes or internal-only config changes do not require a changeset.
  • I've added tests to cover my changes
  • I've added or updated the documentation

On cross-platform impact: Windows paths percent-encode too, so the same decode applies. fileURLToPath was deliberately avoided here per the existing comment, and this change keeps that. Documentation is unchecked because there is nothing user-facing to document.

`monorepoPackagesPath` was read from `new URL(...).pathname`, which is
percent-encoded. When the repository is checked out to a path containing a
space, the encoded path does not exist on disk, so `isHydrogenMonorepo` is
false and `getSkeletonSourceDir()` throws "Trying to use skeleton source dir
outside of Hydrogen monorepo."

Decode the pathname before use. `fileURLToPath` is still avoided here for the
Windows backslash reasons noted in the existing comment.
@AryanKansagara
AryanKansagara requested a review from a team as a code owner September 3, 2026 18:04

@fredericoo fredericoo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great, just please make sure the test would actually catch a regression then im happy to approve

Comment thread packages/cli/src/lib/build.test.ts Outdated
import {describe, it, expect} from 'vitest';
import {decodedPathname} from './build.js';

describe('decodedPathname()', () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking: this test can still pass if monorepo detection regresses. It only exercises decodedPathname() in isolation, so production could go back to consuming raw .pathname while both assertions remain green. Let's test the value actually consumed by monorepo detection instead, so the test fails when the production path stops decoding.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, you're right that the old test could not have failed on that.

Detection now takes the module URL as an argument, so the test runs the real detectHydrogenMonorepo against a temporary checkout created at a path containing a space, rather than calling the decode helper directly.

I checked it fails under both regressions:

  • removing decodeURIComponent from the helper
  • leaving the helper intact but having detection consume new URL('../../..', moduleUrl).pathname again, which is the case you described

Only the space assertion goes red in each; the no-space and negative cases stay green.

Full packages/cli suite passes: 57 files, 435 tests.

The previous test only called the decoding helper directly, so it stayed
green if production went back to consuming raw `.pathname`.

Take the module URL as an argument in `getMonorepoPackagesPath` and
`detectHydrogenMonorepo` so detection can run against a real directory, and
assert against a temporary checkout whose path contains a space.

Verified the test fails both when the decode is removed from the helper and
when detection bypasses the helper entirely.
@AryanKansagara

Copy link
Copy Markdown
Contributor Author

@fredericoo please check again I have added a regression test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI build and tests fail when the repo is cloned to a path containing spaces

2 participants