@svenroth-ai/shipwright 0.24.1-next.0 → 0.24.2-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/shipwright.mjs +33 -2
  2. package/package.json +1 -1
@@ -13,7 +13,7 @@
13
13
  * the installed plugin set changed.
14
14
  */
15
15
 
16
- import { readFileSync } from "node:fs";
16
+ import { readFileSync, realpathSync } from "node:fs";
17
17
  import path from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
19
 
@@ -178,8 +178,39 @@ export async function main(argv = process.argv.slice(2), log = (m) => console.lo
178
178
  return exitCode;
179
179
  }
180
180
 
181
+ /**
182
+ * True when this module is the process entry point — INCLUDING when it is
183
+ * invoked through a symlink. npm/npx install the executable as a symlink in
184
+ * `node_modules/.bin/`, so on macOS/Linux `process.argv[1]` is that symlink
185
+ * while Node realpaths the entry for `import.meta.url` (the real file). A plain
186
+ * `path.resolve` comparison of the two therefore never matches and `main()`
187
+ * never runs — the bootstrapper exits 0 having done NOTHING (the silent-no-op
188
+ * bug: `npx @svenroth-ai/shipwright` printed nothing on the Mac). Windows hid it
189
+ * because its npm `.cmd` shim invokes node with the REAL path, not a symlink.
190
+ *
191
+ * Fix: realpath BOTH sides before comparing, so a symlink and its target compare
192
+ * equal. The realpath is applied SYMMETRICALLY: if EITHER side cannot be
193
+ * realpath'd (`realpathSync` throws — ENOENT from an odd launcher, or a
194
+ * virtualized path such as Yarn PnP that has no on-disk realpath, or EACCES /
195
+ * ELOOP), BOTH sides fall back to plain `path.resolve`. A mixed branch — one
196
+ * side realpath'd, the other only resolved — would spuriously mismatch and
197
+ * re-introduce the very silent-no-op this fixes; symmetry rules that out. An
198
+ * entry-point guard must also never crash the bootstrapper on import, hence the
199
+ * catch. The real npx entry always has an existing argv[1], so both sides
200
+ * realpath and canonicalize identically.
201
+ */
202
+ export function isMainModule(argv1, metaUrl) {
203
+ if (!argv1) return false;
204
+ const self = fileURLToPath(metaUrl);
205
+ try {
206
+ return realpathSync(argv1) === realpathSync(self);
207
+ } catch {
208
+ return path.resolve(argv1) === path.resolve(self);
209
+ }
210
+ }
211
+
181
212
  // Run only when invoked as a script — importing (tests) stays side-effect free.
182
- if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
213
+ if (isMainModule(process.argv[1], import.meta.url)) {
183
214
  main().then((code) => process.exit(code)).catch((e) => {
184
215
  console.error(`[shipwright] fatal: ${String(e?.stack ?? e)}`);
185
216
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svenroth-ai/shipwright",
3
- "version": "0.24.1-next.0",
3
+ "version": "0.24.2-next.0",
4
4
  "description": "One command to install AND update Shipwright — the /shipwright-* SDLC plugins and the Command Center. Run `npx @svenroth-ai/shipwright@latest`.",
5
5
  "type": "module",
6
6
  "bin": {