4.2.0 (2026-09-02)
- add: say gc collected the channel instead of blaming the user (#515) (15a29b7), closes #450
- build: add windows/arm64 to the make release target (#524) (e07af8c), closes #517
- update: refuse to replace a Homebrew or Scoop install (#518) (02f5c09)
4.1.0 (2026-09-02)
Installing with Homebrew or Scoop? 4.1.0 publishes a Homebrew cask and a Scoop manifest, so both now work alongside the install scripts and
go install. Homebrew is one command,brew install pedrosousa13/tap/lnpm. Scoop resolves only against buckets you have added, so it is two:scoop bucket add pedrosousa13 https://github.com/pedrosousa13/scoop-bucket scoop install lnpmDo not run
lnpm updateon an install that came from either one. It replaces the binary in place and has no notion of a package manager, so Homebrew and Scoop go on reporting the version they installed while the binary onPATHis a newer one, and the nextbrew upgradeoverwrites the self-updated binary and puts you back on the version brew recorded. Both commands report success throughout. Usebrew upgrade lnpmorscoop update lnpminstead. #508 tracks teachinglnpm updateto detect a managed install and refuse.The macOS binaries are not signed or notarized, so the cask clears the quarantine attribute on install. Without that, the first run reports lnpm as damaged. It also gives up the Gatekeeper check on this binary.
Nothing changes for an existing install. The store format, the database and
lnpm.lockare all unchanged from 4.0.0.
4.0.0 (2026-09-02)
- every package hash changes. Store entries, database rows and lnpm.lock entries written by 3.x no longer resolve.
- 4.0.0 store-format migration — frame the hash, strip before hashing, refuse 3.x state (#498) (8d15f74)
3.1.1 (2026-09-02)
3.1.0 (2026-08-28)
- install: resolve a relative install directory before anything cd's (#486) (f963c64), closes #477
- install: stop the cleanup trap re-evaluating the temp directory path (#485) (ab14514), closes #476
- update: bound release asset reads before verification (#487) (d3bcde0), closes #478
- update: cap the release API response before decoding it (#495) (1bacca6), closes #488
- update: refuse a release redirect that leaves https (#489) (2e13b03), closes #479
- update: warn that the go-install update path is not signature-verified (#484) (7027958), closes #475
3.0.0 (2026-08-27)
- remove:
lnpm removeno longer runs the package manager's install automatically. Pass --install to keep the old behaviour. - pack: package names containing uppercase are now rejected, and names are composed to NFC before they reach the store or the lock file.
- pack: reject uppercase package names and compose them to NFC (#462) (88c517a), closes #327
- remove: gate the package-manager install behind --install (#386) (fed8d5e), closes #336
2.4.0 (2026-08-27)
- cli: add 'lnpm forget' to drop the record of a project whose drive is gone (#455) (9c8d8c2), closes #382
- pin: keep a rollback until the user moves off it (#451) (b57a06e)
- ci: carry a release's upgrade notes into its published body (#418) (3db6df9)
- cli: refuse a retreat that would delete through a symlinked node_modules (#414) (31753b2)
- db: leave a link index entry DeleteLink cannot parse alone (#416) (a782c82)
- db: stop GetProjectByPath handing back a project it could not parse (#415) (57a2935), closes #391
- doctor: re-hash stored content instead of only checking an entry is finished (#448) (2b5b03f)
- gc: say what declining the orphaned-link prompt achieves (#422) (789e224), closes #362
- hooks: run prepack before prepare, as npm does (#452) (14dcf60)
- link: verify a reuse candidate's content before carrying it over (#445) (b55273b)
- pack: expand a directory a wildcard files entry matches, as npm does (#433) (3f1d7ec)
- pack: hard-reserve the git metadata files so a files entry warns (#430) (f97e34e)
- pack: let the files field beat an ignore file in the project-rules check (#424) (ca1c913), closes #347
- pack: never publish a lockfile, matching npm's short list (#417) (39f842d)
- pack: read a slash run in a files entry the way npm does (#432) (2ff5885)
- pack: reject Windows-reserved package names and trailing dot or space (#419) (f047e13)
- pack: skip an unenterable excluded directory instead of aborting (#434) (06826d7)
- pack: stop a root history.db shipping past a files whitelist (#411) (c1e20c5)
- pack: warn for a nested hard-reserved files entry written with ./ (#431) (07946cb)
- shellcmd: make Command and QuoteArg compose on Windows (#413) (32488bb)
- store: preserve the manifest's mode when stripping lifecycle scripts (#421) (09e04f0)
- store: write protect an entry's content so a consumer cannot rewrite it (#442) (f7c4f14)
- tests: give the turborepo fixtures the entry point they declare (#425) (e6032e8), closes #365
- update: stop a git describe build offering itself an older release (#441) (3a865c8)
- workspace: refuse members that resolve outside the workspace root (#420) (7e3ef3f)
2.3.0 (2026-08-24)
Using a
filesfield? Three changes land together, and the packed set of an existing package can move in both directions. Check withlnpm publishbefore relying on it.A
filesentry now overrides the built-in "excluded unless you say otherwise" list — but only for a path it names directly.files: [".env.example"]ships that template;files: ["dist"]still keepsdist/.envout, because naming a build directory is not a statement about what landed inside it. Amainentry is not a way in: it loses to both built-in lists and warns instead. See #321.An entry written
./distused to select nothing, publishing a package that held onlypackage.json. It now selects whatdistdoes. A bare.still selects nothing, which is what npm does with it. See #346.
filesentries now glob through the same engine as your ignore patterns, so**spans zero or more path segments:files: ["lib/**/*.js"]now selectslib/top.jsas well aslib/sub/a.js. Brace alternation comes with it, sofiles: ["weird{a,b}.txt"]matchesweirda.txtandweirdb.txt— the file of that literal name is still selected too, by an exact-path compare npm does not have. One gap remains: afilesentry ending in a wildcard does not expand a directory it matched into that directory's subtree, where npm does, so["*"]ships only the package root's own files. See #350 and #406.A publish no longer aborts on an unreadable directory it already excludes. A root-owned
coverage/, or a.cache/left by a tool running as another user, used to fail the whole publish by name even when your ignore file excluded it. It is now skipped with a warning. A directory that would have been packed still aborts, because a package silently missing a file is worse than a failed command. See #348.Symlinked your
.lnpmdirectory? Read-only link queries now refuse to resolve through it, closing the same hole #339 closed for writes in 2.2.1. There is no override for this one: thefollow_symlinked_node_modulessetting governsnode_modules, not.lnpm. See #340.
- db: report unreadable link data instead of dropping it (#393) (bbf03c1), closes #355
- gc: stop claiming a package row it did not delete (#395) (30e0ae8), closes #358
- link: refuse read-only link queries through a symlinked .lnpm (#401) (9fa924e), closes #340
- pack: glob a "files" entry with the engine ignore patterns use (#350) (50bf8b7)
- pack: let a files entry override defaultExcludes, or warn when it cannot (#400) (5ecf0a0), closes #321
- pack: resolve a leading "./" in a "files" entry (#346) (37b3f91)
- pack: skip an unreadable directory the package already excludes (#348) (6ce2a3b)
- tests: stop test binaries reading the machine's own config (#396) (f86c2ec), closes #371
2.2.1 (2026-08-23)
Relocated your
node_modules? lnpm now refuses to link or unlink through anode_modules— or anode_modules/@scope— that is not a real directory, because a symlink there redirects lnpm's writes and deletes outside your project. If you relocatenode_modulesdeliberately, setfollow_symlinked_node_modules: truein~/.lnpm/config.yamlto restore the previous behaviour. The refusal message names the file and the key. See #339.Upgrading from 1.x? Store entries written before 2.0.0 carry no completeness marker, and lnpm now checks that marker before serving an entry. The first command that opens your store migrates it in one pass; until then
lnpm doctorreports the store as pending rather than damaged. If it reports that the migration cannot run, a directory in your store could not be read — make it readable and run any command again. See #330.
lnpm gcis now more conservative. It will not collect a package whose only consuming project sits on a filesystem that is not mounted where it was linked — an unplugged drive or an unmounted network share no longer costs you the store entry. Those links are reported as skipped. The trade is that a drive gone for good leaves its entries uncollectable for now; see #382.
- cli: write package.json through a temp file and rename (#377) (fb6bbe5), closes #324
- gc: do not collect when a project's filesystem is not mounted (#383) (9e3ccf6), closes #335
- link: refuse a symlinked node_modules or scope directory (#388) (4ad8ffb), closes #339
- pack: reject package names whose segments begin with a dot (#379) (7f1b524), closes #325
- store: check the completeness marker on the read path (#381) (68b3cb8), closes #330
2.2.0 (2026-08-23)
Packages that excluded
package.json: lnpm now always packs the package root's ownpackage.json, whatever your.npmignore,.gitignoreorfilesfield says, and refuses to pack at all if the manifest is missing. If one of your packages was excluding its manifest, its content hash changes with this release — the manifest is what carries the version string into the hashed content — so its next publish writes a new store entry instead of updating the old one. Only packages exhibiting the bug are affected. See #301.
- publish: add --dry-run and print the packed file list (3354320)
- lockfile: bound lock-file and workspace-config size before parsing (032190d)
- pack: always pack the manifest, whatever the ignore rules say (b47a0c3)
2.1.0 (2026-08-23)
- gc: abort when a package's links cannot be read (683f5c8)
- gc: abort when a project row cannot be read (7445dbe)
- gc: confirm before deleting orphaned links and report what was removed (ccdcd0b)
- link: refuse to link through a symlinked .lnpm or scope directory (#341) (d9beea2)
- pack: anchor the always-included set to the package root (c3dfe77)
- pack: force-include the main entry point under a files whitelist (2156e15)
- pack: honour .npmignore and .gitignore in every directory (#352) (a35b938), closes #315
- pack: implement ** in ignore patterns as zero or more path segments (#351) (faedffa), closes #316
- pack: make the files whitelist win over .npmignore and .gitignore (#349) (60c85a5), closes #318
- pack: match the force-exclude guards case-insensitively (e40905e)
- retreat: validate lock-file package names before deleting the path (#343) (f0ab237)
- workspace: fail on a broken config in the directory Detect started from (#344) (e1f242c)
2.0.0 (2026-08-21)
Upgrading from 1.9.x or older? Those versions compare version numbers byte-wise, so
lnpm updatereportsAlready up to dateand will never offer you this release. Reinstall manually with the install script orgo install github.com/pedrosousa13/lnpm/cmd/lnpm@latest. 1.10.0 and later upgrade normally. See #297.This release also migrates the package database on first open and makes
gccollect superseded versions, so downgrading to 1.x after running it is not supported.
- Add
lnpm pullto sync linked packages from the store (#264) (fc1c89f) - link: relink only the files that changed since the last link (#295) (b60c58c)
- list: add version history and roll back with add <pkg>@<hash> (#298) (53e73bc)
- Point
add --linkat the package's live source directory (#265) (c0fb6e1) - Resolve
workspace:dependency specifiers when publishing (#266) (718f4d7) - restore: add
lnpm restoreto re-link packages afterretreat(#294) (472f1be)
- Abort destructive operations in non-interactive mode instead of auto-confirming (#223) (361672f)
- add: roll a package back when its package.json update fails in multi-add (#244) (68039f3)
- add: save the lock file before rewriting package.json (#243) (4c233f1)
- align build-time ldflags and surface commit and date in --version (#281) (a8b72ac), closes #177
- chmod linked and cloned files so umask cannot strip mode bits (#274) (54c11fe), closes #139
- Compare versions with a semver library instead of text ordering (#216) (258156a)
- fail doctor when it finds issues and print its markers through the icon helpers (#277) (fba8385), closes #162
- fall back to the user completion directory when the system one refuses the write (#278) (c1439d8), closes #169
- Fix nil-pointer crash in retreat when lnpm.lock is corrupt (#221) (70971e2)
- Fix versioned package spec being treated as a content hash in multi-package add (#222) (c82ec62)
- fsutil: call clonefile with the right signature on macOS (#229) (cfbfa9f), closes #135
- gc: reclaim temp directories left by an interrupted publish or relink (#289) (e021c81), closes #233
- give store entries a completeness marker (#273) (6650434), closes #237
- keep GetDB's init error so every caller sees it (#290) (c73954a), closes #253
- link: populate a temp directory and rename-swap instead of clearing the live package (#232) (b3f0a47), closes #137
- Make doctor honor the configured store_path when checking the store (#249) (160ff22)
- make hooks.skip_post_add actually skip the post-add hook (#280) (7079f81), closes #171
- Make publish --push fail when every linked-project push fails (#248) (022933e)
- Normalize root-anchored and directory patterns in the files whitelist (#228) (ba1ea4d)
- Preserve package.json key order and formatting when editing dependencies (#250) (bf67c59)
- remove the updater's temp directory and match Go bin dirs by path component (#276) (b29b9da), closes #147
- remove: keep the lock entry when remove fails to restore package.json (#245) (c13b392)
- Report a real version for go install builds so they can self-update (#217) (a3e9b48)
- report scoped packages by full name and clean up emptied scope directories (#279) (ac2b4b9), closes #170 #236
- Rewrite isExcluded to satisfy gitignore and npm ignore semantics (#220) (793e18a)
- Run all applicable publish lifecycle scripts, not just the first (#251) (f8159ba)
- Stop concurrent lnpm invocations from failing with a cryptic database timeout (#254) (24cd438)
- stop hardlinking source files into the store (#213) (624a3e3)
- stop three tests from passing without checking their subject (#282) (7ef8a02), closes #186
- store: never delete the destination before the atomic rename (#238) (f6ebaf7), closes #138
- stream copies with io.Copy and unlink half-made reflink clones (#275) (9a41948), closes #140
- treat a degenerate files entry as including everything (#272) (a9f9a52), closes #227
- update: report update-check failures instead of "Already up to date" (#239) (400cdf1), closes #144
- update: stage the updated binary next to the target to avoid cross-filesystem rename failures (#240) (6b4e102)
- Verify release checksums in the install scripts before running the binary (#218) (be7f7d2)
- workspace: abort ListPackages on a member that will not read or parse (#293) (cd1aca9)
- workspace: fail expansion when a workspace glob pattern will not parse (#287) (16c83a3), closes #241
- workspace: subtract negation patterns in publish --all instead of dropping them (#242) (812d21d)
1.12.0 (2026-07-23)
- config: support editor commands with args (#204) (6b1727d)
- detect bun.lock text lockfile and honor list package argument (#205) (f9b0cfb)
1.11.0 (2026-06-24)
- cli: NO_COLOR/tty-aware output, rune-safe truncate, consistent confirmations, wire pre/post_publish hooks (#53, #51) (9cd95a2)
- DeletePackage cleans up links + add GetProjectByID (#44) (7d84361)
- honor store_path config option (#51 partial) (8a2b83d)
- make content hash correct and deterministic (#45, #46) (0c9450d)
- make store writes atomic via temp dir + rename (#47) (8324b9c)
- non-zero exit on partial failure; silence usage on error (#48) (faa800f)
- reflink dead on Linux — pass FICLONE source fd by value (#38) (4d08c1a)
- resolve add pkg@version by version; remove dead --tag (#39) (a29a0ad)
- skip symlinks during packing to prevent file exfiltration (#42) (1451c9b)
- validate package names to prevent path traversal (#40) (7547650)
- verify SHA-256 checksum on self-update + add HTTP timeout (#41) (5a57a87)
1.10.0 (2026-03-05)
1.9.0 (2026-01-30)
1.8.2 (2026-01-19)
1.8.1 (2026-01-19)
1.8.0 (2026-01-19)
- add manage_gitignore config option (cb6f51f)
- ci: skip concurrent package.json write test (79360bb)
- ci: skip flaky concurrent test in CI (8b11c09)
- remove unused fmt import (2122a51)
- revert db path and remove t.Parallel() (0e33339)
- skip flaky symlink test in CI (dec5411)
- test failures and permission handling (9c4030b)
- tests: path normalization & test fixes (f239a04)
- wrap all unchecked error returns (a8b3431)
- wrap defer os.Chmod in anonymous functions (ffb08da)
- wrap defer os.Chmod in store tests (ca75562)
1.7.7 (2026-01-15)
- race condition in pack (e5f6793)
1.7.6 (2026-01-15)
- parallel hashing/linking, remove npm pack dep, fix defer errors (342272b)
1.7.5 (2026-01-15)
- correct symlink depth for scoped packages (3d3162d)
1.7.4 (2026-01-15)
- move goreleaser to release-please workflow (2062221)
1.7.3 (2026-01-15)
- merge release workflow into CI, ensure CI blocks release (6b244ff)
- release-please not running after PR merge (219f321)
1.7.2 (2026-01-15)
- deprecated filepath.HasPrefix, align CI with release-please (a2f7739)
1.7.1 (2026-01-15)
- filepath.hasprefix (46fac9c)
1.7.0 (2026-01-15)
- better completions (0a8ec9b)
1.6.1 (2026-01-15)
- lnpm update auto (7ca5607)
1.6.0 (2026-01-15)
- npm pack + tests for all kinds of monorepo (a7c3cb8)
- CI failures (ff1d541)
1.5.0 (2026-01-15)
- check files against stage (5b2016b)
1.4.0 (2026-01-15)
- keep pushing for perf improvements (78ce20b)
- lnpm update should always check for latest version (24ac467)
1.3.0 (2026-01-15)
- improve perf further (e7503d0)
1.2.0 (2026-01-15)
- Reflink (Copy-on-Write) support for instant file operations on APFS (macOS) and Btrfs/XFS (Linux)
- Hard link support during publish - Store operations now use hard links when source and store are on same filesystem
- Parallel copy operations - Up to 8 concurrent workers for 4-8x faster copying when linking isn't possible
- Intelligent linking strategy - Automatic priority system: reflink → hardlink → parallel copy
- Config integration -
link_modeconfiguration option is now properly respected - Enhanced user feedback - Clear warnings and tips when falling back from linking to copying
- Cross-filesystem detection - Automatic detection and helpful messages for cross-filesystem scenarios
- Up to 1000x faster for packages with 10,000+ files on modern filesystems (APFS/Btrfs/XFS)
- Instant publishing when source and store are on same filesystem
- 4-8x faster copying when cross-filesystem operations are required
1.1.1 (2026-01-15)
- remove unused collectFiles function (64c2bca)
1.1.0 (2026-01-15)
- progress indicators + perf improvements (82d0571)
1.0.2 (2026-01-15)
- run goreleaser in release-please workflow (9133260)
1.0.1 (2026-01-15)
- build error (7b553b4)