Skip to content

Commit 461fc5b

Browse files
dadachiclaude
andauthored
Fix bin entry-guard to handle npm symlinked invocation (v0.1.1) (#54)
Found post-publish: 0.1.0's CLI exits 0 with no output when invoked via npm bin symlink (e.g. ./node_modules/.bin/nativeapptemplate-agent or `npx nativeapptemplate-agent`). The pipeline never runs. Root cause: src/index.ts entry-guard compared the wrong shapes: if (import.meta.url === `file://${process.argv[1]}`) { main() } - import.meta.url resolves to the actual module file URL (e.g. file:///.../node_modules/nativeapptemplate-agent/dist/index.js) - process.argv[1] under npm bin invocation is the symlink path (e.g. /tmp/x/node_modules/.bin/nativeapptemplate-agent) - The two never match, the guard fails silently, main() is never called, the script exits with no error. Direct `node dist/index.js` worked because both sides resolved to the same path; symlinked bin invocation didn't, but the bug was invisible until the package was published and tested via `npx`. Fix: resolve both sides to canonical (symlink-followed) paths using realpathSync + fileURLToPath: function isEntryPoint(): boolean { const argv1 = process.argv[1]; if (!argv1) return false; try { const modulePath = fileURLToPath(import.meta.url); const argv1Real = realpathSync(argv1); return argv1Real === modulePath; } catch { return false; } } Verified post-fix: - direct invocation: `node dist/index.js "test spec"` → full output, overall: PASS, exit 0. - symlink invocation: ln -s dist/index.js /tmp/x/nat-bin && /tmp/x/nat-bin "test spec" → identical output. Confirms the fix works for the published-package code path. - npm run ci: 19/19 green. Bumps version 0.1.0 -> 0.1.1 for the patch release. Out of scope (next, manual): - User runs `npm publish` to release 0.1.1. - Once 0.1.1 is on the registry, deprecate 0.1.0: `npm deprecate nativeapptemplate-agent@0.1.0 \ "Silent no-op when invoked via npm bin symlink. Use 0.1.1+."` Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1db714c commit 461fc5b

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nativeapptemplate-agent",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"description": "A Claude Code agent that turns a natural-language SaaS spec into a working three-platform implementation (Rails API + SwiftUI iOS + Jetpack Compose Android).",
55
"type": "module",
66
"main": "dist/index.js",

src/index.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/usr/bin/env node
2+
import { realpathSync } from "node:fs";
3+
import { fileURLToPath } from "node:url";
24
import { dispatch } from "./dispatch.js";
35
import { loadDotenvIfPresent } from "./env.js";
46

@@ -23,9 +25,27 @@ export async function main(spec?: string): Promise<void> {
2325
console.log(`overall: ${result.overallPass ? 'PASS' : 'FAIL'}`);
2426
}
2527

26-
if (import.meta.url === `file://${process.argv[1]}`) {
28+
// Entry guard: run main() when this file is the program entry point. Resolve
29+
// both sides to canonical (symlink-followed) paths so npm bin symlinks (e.g.
30+
// node_modules/.bin/nativeapptemplate-agent → ../nativeapptemplate-agent/dist/
31+
// index.js) compare equal to import.meta.url. Without realpath, the guard
32+
// fails silently on symlinked invocations and main() never runs — the CLI
33+
// would exit 0 with no output.
34+
if (isEntryPoint()) {
2735
main().catch((err) => {
2836
console.error(err);
2937
process.exitCode = 1;
3038
});
3139
}
40+
41+
function isEntryPoint(): boolean {
42+
const argv1 = process.argv[1];
43+
if (!argv1) return false;
44+
try {
45+
const modulePath = fileURLToPath(import.meta.url);
46+
const argv1Real = realpathSync(argv1);
47+
return argv1Real === modulePath;
48+
} catch {
49+
return false;
50+
}
51+
}

0 commit comments

Comments
 (0)