Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/olive-pugs-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@shopify/cli-hydrogen': patch
'@shopify/create-hydrogen': patch
---

Fix Hydrogen monorepo detection when the repository path contains spaces or other characters that are percent-encoded in URLs. The path is now decoded before it is used to look for the skeleton template.
52 changes: 52 additions & 0 deletions packages/cli/src/lib/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {describe, it, expect} from 'vitest';
import {pathToFileURL} from 'node:url';
import {inTemporaryDirectory, mkdir} from '@shopify/cli-kit/node/fs';
import {joinPath} from '@shopify/cli-kit/node/path';
import {detectHydrogenMonorepo, getMonorepoPackagesPath} from './build.js';

// Mirrors the location of this file inside the monorepo, since monorepo
// detection resolves `packages/` relative to the module that asks for it.
const MODULE_PATH_IN_REPO = ['packages', 'cli', 'src', 'lib', 'build.ts'];

function moduleUrlFor(repoRoot: string) {
return pathToFileURL(joinPath(repoRoot, ...MODULE_PATH_IN_REPO));
}

async function createFakeMonorepo(repoRoot: string) {
await mkdir(joinPath(repoRoot, ...MODULE_PATH_IN_REPO.slice(0, -1)));
await mkdir(joinPath(repoRoot, 'templates', 'skeleton'));
}

describe('monorepo detection', () => {
it('detects the monorepo when the path contains a space', async () => {
await inTemporaryDirectory(async (tmpDir) => {
// The space is what regressed: `URL.pathname` percent-encodes it, and
// the encoded path does not exist on disk.
const repoRoot = joinPath(tmpDir, 'Open Source', 'hydrogen');
await createFakeMonorepo(repoRoot);

expect(detectHydrogenMonorepo(moduleUrlFor(repoRoot))).toBe(true);
expect(getMonorepoPackagesPath(moduleUrlFor(repoRoot))).toContain(
'Open Source',
);
});
});

it('detects the monorepo when the path contains no space', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const repoRoot = joinPath(tmpDir, 'hydrogen');
await createFakeMonorepo(repoRoot);

expect(detectHydrogenMonorepo(moduleUrlFor(repoRoot))).toBe(true);
});
});

it('does not detect a monorepo without templates/skeleton', async () => {
await inTemporaryDirectory(async (tmpDir) => {
const repoRoot = joinPath(tmpDir, 'Open Source', 'not-hydrogen');
await mkdir(joinPath(repoRoot, ...MODULE_PATH_IN_REPO.slice(0, -1)));

expect(detectHydrogenMonorepo(moduleUrlFor(repoRoot))).toBe(false);
});
});
});
19 changes: 14 additions & 5 deletions packages/cli/src/lib/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ import {AbortError} from '@shopify/cli-kit/node/error';
import {dirname, joinPath} from '@shopify/cli-kit/node/path';
import {execAsync} from './process.js';

// Avoid using fileURLToPath here to prevent backslashes nightmare on Windows
const monorepoPackagesPath = new URL('../../..', import.meta.url).pathname;
// Resolve the `packages/` directory that contains this file.
// Avoid using fileURLToPath here to prevent backslashes nightmare on Windows.
// URL pathnames are percent-encoded, so decode to support directories that
// contain spaces or other reserved characters.
export function getMonorepoPackagesPath(moduleUrl: string | URL) {
return decodeURIComponent(new URL('../../..', moduleUrl).pathname);
}

// Check if we're in the Hydrogen monorepo by checking the path structure
// This was the original logic before PR #3074 that worked for over a year
// Check if we're in the monorepo by looking for the templates/skeleton directory
// This works both in development and when built as npm package
export const isHydrogenMonorepo = (() => {
export function detectHydrogenMonorepo(moduleUrl: string | URL) {
try {
const skeletonPath = joinPath(
dirname(monorepoPackagesPath),
dirname(getMonorepoPackagesPath(moduleUrl)),
'templates',
'skeleton',
);
Expand All @@ -25,7 +30,11 @@ export const isHydrogenMonorepo = (() => {
} catch {
return false;
}
})();
}

const monorepoPackagesPath = getMonorepoPackagesPath(import.meta.url);

export const isHydrogenMonorepo = detectHydrogenMonorepo(import.meta.url);
export const hydrogenPackagesPath = isHydrogenMonorepo
? monorepoPackagesPath
: undefined;
Expand Down
Loading