Skip to content

fix(cli): run when reached through a symlinked entry path - #22

Merged
altaywtf merged 3 commits into
mainfrom
fix/cli-entry-realpath
Jul 28, 2026
Merged

fix(cli): run when reached through a symlinked entry path#22
altaywtf merged 3 commits into
mainfrom
fix/cli-entry-realpath

Conversation

@altaywtf

Copy link
Copy Markdown
Member

Closes #21

Summary

vref's CLI guarded its entry point with:

if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href)

Node canonicalises import.meta.url through symlinks but leaves process.argv[1] exactly as the caller wrote it. So the comparison fails whenever the CLI is reached via a symlink — which is the normal case under pnpm, where node_modules/<pkg> links into node_modules/.pnpm/….

Whether the CLI worked came down to which path the generated bin shim used:

shim target guard result
…/.bin/../.pnpm/@putdotio+vref@1.1.0/…/dist/cli.mjs passes CLI runs
…/.bin/../@putdotio/vref/dist/cli.mjs fails exits 0, does nothing

Both occur in practice. putio-roku got the first. A fresh pnpm add -D @putdotio/vref in putio-ios got the second, where vref describe, vref build and vref validate all silently no-op — same pnpm 11.2.2, same Node 24.18.0, same package checksum.

The silence is the dangerous part: a CI step running vref build --check passes while validating nothing.

Changed

  • New exported isDirectInvocation(moduleUrl, entryPath) canonicalises both sides before comparing, so the behaviour is testable without spawning a process
  • A missing or non-existent argv[1] falls through to the plain comparison instead of throwing during startup

Review aids

Built dist/cli.mjs, symlinked it, and invoked through the link — the case that produced no output before:

$ node "$TMP/linked-vref.mjs" describe --output json --fields name
{ "ok": true, "result": { "name": "vref" } }

$ node dist/cli.mjs describe --output json --fields name
{ "ok": true, "result": { "name": "vref" } }

Verification

pnpm run verify green — format, lint, typecheck, 22 tests (3 new), npm pack --dry-run.

New tests cover a symlinked entry path (direct invocation), the real path (still direct), an unrelated path and undefined (both not direct), and a non-existent path (returns false rather than throwing).

Complexity

Low. One guard, one exported helper.

The CLI guarded its entry point by comparing `import.meta.url` against
`pathToFileURL(process.argv[1])`. Node canonicalises the former through symlinks
but leaves the latter exactly as the caller wrote it, so the comparison fails
whenever the CLI is reached through a symlink — the norm under pnpm, which links
`node_modules/<pkg>` into `node_modules/.pnpm/…`.

Whether it worked depended on which path the generated bin shim happened to use.
`putio-roku` got a shim pointing at the real `.pnpm` path and works; a fresh
`pnpm add -D @putdotio/vref` in `putio-ios` got one pointing at the symlink,
where `vref describe`, `vref build`, and `vref validate` all exited 0 having done
nothing. Same pnpm, same Node, same package checksum.

Silent success is the dangerous part: a CI step running `vref build --check`
would pass while validating nothing at all.

Canonicalise both sides before comparing, via an exported `isDirectInvocation`
so the behaviour is testable without spawning a process. A missing `argv[1]`
falls through to the plain comparison rather than throwing during startup.

Closes #21
Copilot AI review requested due to automatic review settings July 28, 2026 09:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a long-standing CLI entry-point guard bug where vref could silently no-op when invoked through a symlinked path (commonly produced by pnpm’s node_modules/@scope/pkg symlink layout). It introduces a small, testable helper that canonicalizes the entry path before comparing it to import.meta.url, preventing false negatives and ensuring vref build/validate/describe actually run in CI.

Changes:

  • Added isDirectInvocation(moduleUrl, entryPath) to canonicalize the entry path via realpathSync() before comparing URLs.
  • Updated the CLI entry guard to use isDirectInvocation(import.meta.url, process.argv[1]).
  • Added regression tests covering symlinked entry paths, unrelated paths, undefined, and non-existent paths.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/cli.ts Adds isDirectInvocation and updates the CLI entry-point guard to handle symlinked invocation paths reliably.
test/vref.test.ts Adds tests validating direct-invocation detection for symlinked paths and error-tolerant behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli.ts Outdated
altaywtf added 2 commits July 28, 2026 12:25
…main

Canonicalising the entry path unconditionally broke a supported mode: with
`node --preserve-symlinks-main`, Node deliberately keeps `import.meta.url` on the
symlink, so resolving only `argv[1]` made the two disagree and skipped `main`
silently — the same class of failure this change set out to remove.

Compare the raw paths first, then fall back to the canonical comparison, so both
the pnpm symlinked-shim case and the preserve-symlinks case run.

Refs #21
The comment named only a non-existent argv[1], but the catch also covers
unreadable paths and symlink loops. Returning false in every case is
deliberate: an entry path that cannot be resolved is not proven to be this
module, so fail closed instead of throwing during startup.

Addresses a review comment on #22.
@altaywtf
altaywtf merged commit 8b55c7d into main Jul 28, 2026
2 checks passed
@altaywtf
altaywtf deleted the fix/cli-entry-realpath branch July 28, 2026 09:30
putio-releaser Bot pushed a commit that referenced this pull request Jul 28, 2026
## [1.1.1](v1.1.0...v1.1.1) (2026-07-28)

### Bug Fixes

* **cli:** run when reached through a symlinked entry path ([#22](#22)) ([8b55c7d](8b55c7d)), closes [#21](#21)
@putio-releaser

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.1.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI bin silently no-ops under pnpm's default symlinked layout

2 participants