@xwai/nalc
v0.0.1
Published
Registry-first local package development through an embedded Verdaccio proxy.
Readme
nalc
Registry-first local package development powered by an embedded Verdaccio proxy.
nalc is a hard fork focused on one thing only: making local package testing behave as close as possible to a real npm, pnpm, or bun install.
Instead of copying files into a local store or wiring file: / link: dependencies, nalc always goes through a local registry pipeline:
- publish a local prerelease build to an embedded Verdaccio instance
- let the consumer project install that version through its own package manager
- track the relationship between source package, local registry version, and consumer state
What Problem It Solves
Local package workflows based on symlinks or file copies often differ from real installs in subtle but important ways:
- nested dependencies may resolve differently
- peer dependency warnings can disappear or appear inconsistently
- bundlers may optimize the linked source differently from a published package
- package manager lockfiles do not reflect the real package graph
nalc solves this by making the consumer run a normal install against a local registry, so the runtime dependency graph is much closer to production.
Architecture Summary
nalc has two sides.
1. Publisher Side
When you run nalc publish, the source package is packed, normalized, and published into the local registry.
Key behaviors:
- starts or reuses an embedded Verdaccio runtime under
~/.nalc/registry - rewrites the package version to a local prerelease such as
1.2.3-nalc.20260328.deadbeef - resolves local
workspace:andcatalog:references before publishing - optionally strips development-only manifest fields
- records publish metadata in
~/.nalc/registry/state.json
2. Consumer Side
When you run nalc add or nalc update, nalc updates the consumer manifest and then delegates the real installation to the consumer's own package manager.
Key behaviors:
- persists tracked state in
.nalc/state.json - remembers the consumer package manager so future installs can fall back to the same tool when lockfiles are temporarily absent
- saved entries are written to
package.jsonas exact local versions such as1.2.3-nalc.20260328.deadbeef - the lockfile still records the exact resolved version
- after install,
nalcsyncs the exact installed version back into consumer state - stale tracked
.pnpmlocal instances are cleaned up after install
Versioning Strategy
Published local builds use this format:
<baseVersion>-nalc.<yyyymmdd>.<buildId>Example:
0.2.0-nalc.20260328.deadbeefSaved consumer manifests use the exact local version:
{
"dependencies": {
"@scope/pkg": "0.2.0-nalc.20260328.deadbeef"
}
}Why:
package.jsoncannot drift back to a remote stable release during install- the lockfile captures the same exact local version that nalc requested
consumerState.localSpecstores the exact installed version used by cleanup and future updates
Installation
npm i -g @xwai/nalc
# or
pnpm add -g @xwai/nalc
# or
bun add -g @xwai/nalcTypical Workflows
Single package
# in the library
nalc publish
# in the consumer
nalc add my-package
# later, after changes
nalc publish --push
# or
nalc update my-packageMonorepo batch publish
nalc ws ../my-monorepo --workspace-resolveWatch and auto-push
nalc watch ../my-monorepo --workspace-resolveFinish local verification
nalc passCLI Reference
nalc serve
Start or reuse the embedded Verdaccio runtime.
nalc serve
nalc serve --port 4874Behavior:
- reuses the healthy runtime recorded in
~/.nalc/registry/state.json - nalc keeps a single Verdaccio runtime per nalc home directory
--portonly affects the next spawn when no healthy runtime is currently recorded- if the chosen port is occupied, nalc scans upward for the next free port, similar to Vite
nalc publish [path]
Publish a package to the local registry.
nalc publish
nalc publish ../my-package
nalc publish --no-scripts
nalc publish --push
nalc publish --changedImportant options:
--scripts: run lifecycle scripts, defaulttrue--dev-mod: strip development-only manifest fields, defaulttrue--workspace-resolve: resolveworkspace:dependencies before publish, defaulttrue--content: print packed file list--private: publish even whenprivate: true--changed: skip publish if the publishable content hash did not change--push: update tracked consumers after publish--ignore <pkg...>: skip selected packages duringws/watch
Lifecycle hooks executed before publish:
prepublishprepareprepublishOnlyprepackprenalcpublish
Lifecycle hooks executed after publish:
postnalcpublishpostpack
Deliberately not executed:
publishpostpublish
nalc push [path]
Publish and immediately push the new local version into tracked consumers.
nalc push
nalc push --changedCompared with publish, push defaults to --scripts=false and always behaves as --push.
nalc add <package...>
Install one or more published local packages into the current project.
nalc add my-package
nalc add my-package -DImportant options:
-D,--dev,--save-dev: write todevDependencies
Behavior:
- records the original dependency spec in
.nalc/state.json - persists the exact
<localVersion>topackage.jsonwhile the project is under nalc management - the actual install is performed by
npm,pnpm, orbun
nalc update [package...]
Update tracked consumer entries to the latest locally published versions.
nalc update
nalc update my-packageBehavior:
- updates tracked packages to the latest published
localVersion - keeps each entry's
dependencyType - refreshes the consumer install with the new exact local versions
nalc remove [package...]
Stop tracking selected local package overrides and restore their original dependency specs.
nalc remove my-package
nalc remove --allBehavior:
- restores
originalSpecif the dependency existed before nalc management - removes the dependency entirely if nalc introduced it from scratch
- keeps the project in nalc mode for any remaining tracked packages
- updates the consumer install after restoring the manifest
nalc pass
Restore the current project to normal dependency specs and remove nalc state files.
nalc passBehavior:
- restores every tracked package back to its original dependency range
- reinstalls the consumer with the recorded package manager
- removes
.nalc/state.jsonand drops the empty.nalcdirectory - removes stale nalc-owned
.pnpmpackage instances
nalc ws [path]
Detect workspace packages and publish them in dependency order.
nalc ws
nalc ws ../workspace --ignore docs-site playgroundnalc watch [path]
Watch the current package or workspace and automatically republish changed packages.
nalc watch
nalc watch ../workspace --ignore app-shellBehavior:
- republish is debounced
watchalways enableschanged: true,push: true, andworkspaceResolve: true
nalc refresh [path]
Touch tsconfig.json, jsconfig.json, or package.json so editors rebuild their local dependency graph after nalc add or nalc update.
nalc refresh
nalc refresh ../consumerBehavior:
- prefers
tsconfig.json, thentsconfig.base.json, thenjsconfig.json, thenpackage.json - prints the touched file, detected package manager, and tracked package count
- the same refresh step also runs automatically after successful consumer installs
nalc dir
Print the nalc home directory.
State Files
Global state
Stored at:
~/.nalc/registry/state.jsonTracks:
- active Verdaccio runtime metadata
- published package metadata
- tracked consumer directories for each package
Consumer state
Stored at:
<consumer>/.nalc/state.jsonTracks:
dependencyType: target field such asdependenciesordevDependenciesoriginalSpec: dependency range before nalc took overlocalSpec: exact installed nalc versionmanifestSpec: exact local version written topackage.jsonwhile the project is under nalc managementsourcePath: source package pathregistryUrl: local registry URL used by the consumerinstalledAt: last install timestamp
Configuration
Use .nalcrc in the current working directory to define defaults:
workspace-resolve=true
dev-mod=true
save=true
scripts=true
quiet=false
[ignore]
packages = docs-site, playground
files = .DS_Store, *.logSupported flags read from .nalcrc:
portworkspace-resolvedev-modsavescriptsquietmodeignore
Use --dir <path> to override the default nalc home directory.
Programmatic API
nalc also exposes a small programmable surface from lib/index.js.
import {
publishPackage,
smartPublish,
watchPackages,
ensureRegistryRuntime,
addRegistryPackages,
updateRegistryPackages,
removeRegistryPackages,
pushRegistryPackages,
refreshConsumerProjectGraph,
readGlobalRegistryState,
writeGlobalRegistryState,
readConsumerRegistryState,
writeConsumerRegistryState,
} from 'nalc'Main groups:
- publish pipeline:
publishPackage,smartPublish,watchPackages - runtime control:
ensureRegistryRuntime - consumer operations:
addRegistryPackages,updateRegistryPackages,removeRegistryPackages,pushRegistryPackages,refreshConsumerProjectGraph - state access:
read*State,write*State, tracked consumer helpers
For detailed API signatures and examples, see:
docs/design.mddocs/programmatic-api.md
Detailed Docs
- Chinese overview:
README_CN.md - Design notes:
docs/design.md - Programmatic API:
docs/programmatic-api.md - Future direction:
ROADMAP.md
Status
Phase 1 supports:
npmpnpmbun- monorepo detection and bulk publish
- watch and push through the registry pipeline
Phase 1 does not support:
- Yarn PnP
- legacy link, file, copy-store, or vendorized local package flows
License
MIT
