Skip to content

Commit 4278fd1

Browse files
talzucursoragent
andcommitted
v0.2.3: fix pnpm exec design-loop ERR_MODULE_NOT_FOUND on github: install
Reported by a first-time user after a fresh `pnpm add github:...` install: `pnpm exec design-loop --help` crashed with ERR_MODULE_NOT_FOUND. The workaround was `node node_modules/@ekolabs/.../dist/cli.js`. Root cause: pnpm's `github:` install layout encodes the commit SHA in the directory name as a `#<sha>` suffix (e.g. `.../claude-design-loop.git#ca4380c_<hash>/`). The bin shim was passing a raw absolute filesystem path to `await import()`. Node converts that to a `file://` URL internally and treats the `#` as a URL fragment — truncating the path mid-way through node_modules and throwing ERR_MODULE_NOT_FOUND. Fix: route the dynamic import through `pathToFileURL(dist).href` so `#` (and any other special chars, e.g. spaces on Windows) are properly percent-encoded into a valid file URL. `config.ts` already does this correctly when loading the consumer's config — the bug was only in the bin entry point. Affected every consumer using `pnpm exec design-loop`, `pnpm design`, or any pnpm-driven invocation of the bin. npm / direct-`node` invocations were unaffected. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ca4380c commit 4278fd1

4 files changed

Lines changed: 28 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
## [0.2.3] - 2026-05-15
10+
11+
### Fixed
12+
- **`pnpm exec design-loop` crashed with `ERR_MODULE_NOT_FOUND` after a
13+
fresh `github:` install.** pnpm's git-install layout puts the commit
14+
SHA in the directory name (e.g. `.../claude-design-loop.git#<sha>/`).
15+
The bin shim was passing a raw absolute path to `await import()`,
16+
which Node converts to a `file://` URL — and treats the `#` as a URL
17+
fragment, truncating the path. Now goes through `pathToFileURL` so
18+
`#` (and any other special chars, e.g. spaces on Windows) are
19+
properly percent-encoded. Affected every consumer using
20+
`pnpm exec design-loop`, `pnpm design`, or any pnpm-driven invocation
21+
of the bin. `node node_modules/@ekolabs/.../dist/cli.js` was the
22+
workaround. npm / direct-`node` invocations were unaffected.
23+
924
### Changed
1025
- README: clarified that `pnpm install` / `npm install` does NOT
1126
re-fetch the latest commit when tracking `main`, because lockfiles

bin/design-loop.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
#!/usr/bin/env node
22
// Thin shim: import the built CLI from dist/. tsup emits ESM with a top-level
33
// program.parseAsync() that runs on import, so a bare import is enough.
4-
import { fileURLToPath } from 'node:url';
4+
//
5+
// IMPORTANT: pnpm's `github:` install layout puts a `#<sha>` segment in the
6+
// `node_modules/.pnpm/...` directory name (e.g. `...claude-design-loop.git
7+
// #ca4380c_<hash>/...`). When passing a raw filesystem path to `await
8+
// import()`, Node converts it to a `file://` URL and treats the `#` as a
9+
// URL fragment — truncating the path mid-way and throwing
10+
// `ERR_MODULE_NOT_FOUND`. Always go through `pathToFileURL` so the `#`
11+
// (and any other special chars, e.g. spaces on Windows) are properly
12+
// percent-encoded.
13+
import { fileURLToPath, pathToFileURL } from 'node:url';
514
import { dirname, resolve } from 'node:path';
615
import { existsSync } from 'node:fs';
716

@@ -15,4 +24,4 @@ if (!existsSync(dist)) {
1524
process.exit(1);
1625
}
1726

18-
await import(dist);
27+
await import(pathToFileURL(dist).href);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ekolabs/claude-design-loop",
3-
"version": "0.2.2",
3+
"version": "0.2.3",
44
"description": "Round-trip design loop between your IDE and claude.ai/design. Capture a route, send a brief, iterate visually with Claude, fetch the handoff bundle, translate it into framework-native scaffolds, and verify the result.",
55
"license": "SEE LICENSE IN LICENSE",
66
"private": false,

src/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const program = new Command();
6767
program
6868
.name('design-loop')
6969
.description('Round-trip design loop between your IDE and claude.ai/design')
70-
.version('0.2.2');
70+
.version('0.2.3');
7171

7272
// Default action: when no subcommand is supplied, drop into the wizard.
7373
program

0 commit comments

Comments
 (0)