Skip to content

Why Smuggle?

Testing local packages in a real consumer project usually means npm link, pnpm link, yalc, or file: references. Each one leaves a trail.

Compare against:
beforenpm link
node_modules/my-pkg
-real folder
+→ ../../my-pkg (symlink)
Bundlers and resolvers behave differently
package.json
"my-pkg": "^1.0.0"
Range stays — but resolves to a symlink, not a release
pnpm-lock.yaml / package-lock.json
(usually unchanged)
Easy to commit a symlinked install by accident
aftersmuggle install
node_modules/my-pkg
real folder, real files
Bundlers, Vite, Node — all see a normal install
package.json
"my-pkg": "^1.0.0"
Untouched. Same diff as if you ran nothing.
pnpm-lock.yaml / package-lock.json
(unchanged)
No churn. Nothing to revert before committing.

Smuggle packs your local package the same way npm publish would, then directly replaces the installed copy in node_modules. Your lockfile and package.json stay untouched.

The trade-offs of the alternatives

  • npm link / pnpm link — creates symlinks that confuse bundlers, break with pnpm's content-addressable store, and behave differently from a real install.
  • file: references — pollute your lockfile and package.json with local paths that break for other contributors.
  • yalc — closer, but still modifies your lockfile and requires manual cleanup.

Design principles

No symlinks
Packages are real files in node_modules — bundlers, Vite, and Node treat them like any other install.
No lockfile changes
pnpm-lock.yaml, package-lock.json, yarn.lock — never touched. Nothing to revert.
No .npmrc tweaks
No registry overrides, no scoped resolution rules.
No package.json edits
Version ranges are preserved. (smuggle add is the exception — it adds the dep on purpose.)
Automatic cleanup
Originals are restored on exit, even on Ctrl-C.
Hash-based change detection
Cache busts and Vite restarts only when the packed output actually changes.
Workspace aware
Detects pnpm workspaces and scans all member packages for matches.