Validate a package before publishing to npm. Catches workspace dependency leaks, missing dist files, source leaks, and bundling issues. TRIGGER when: publishing to npm, modifying package.json dependencies of a publishable package, changing bundler config (vite.config, tsup.config), debugging npm install failures (E404, missing packages), or reviewing release-please PRs.
77
96%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Mandatory checklist before publishing any package to npm. This skill exists
because workspace dependencies have leaked into published packages before,
breaking npm install for consumers.
Before changing dependencies in a publishable MoltNet package, also load
../rendered-pack-3252bc08/SKILL.md. It records the workspace protocol,
private-package bundling, and lockfile rules that apply here.
pnpm publish or npm publish on any packagepackage.json of a publishable packageA package is publishable if it has a files field in package.json and is
not marked "private": true. Do not maintain a package list in this skill; it
goes stale. Discover the current set from apps/*/package.json,
libs/*/package.json, and packages/*/package.json, then confirm each project
has a check:pack target with pnpm exec nx show project <name> --json.
The native CLI platform packages under packages/cli/npm/* are assembled and
validated by the Go CLI release job. Do not mistake them for ordinary
TypeScript package projects.
Every publishable package must identify this GitHub repository and its exact directory in the monorepo:
"repository": {
"type": "git",
"url": "git+https://github.com/getlarge/themoltnet.git",
"directory": "libs/<package-directory>"
}Use the actual apps/, libs/, or packages/ path for directory. npm
trusted publishing validates repository.url against the GitHub Actions OIDC
provenance. Missing or mismatched metadata passes tarball creation but fails the
registry PUT with E422 Error verifying sigstore provenance bundle.
check:pack validates the canonical repository URL and matching monorepo
directory for every publishable package.
Private workspace packages (@moltnet/*) must NEVER appear in dependencies
of a publishable package. They are not published to npm and will cause
npm install to fail.
Rule: If a @moltnet/* package is imported in source code and the build
bundles it (Vite SSR, esbuild, etc.), it belongs in devDependencies.
Check:
rg -n '@moltnet/' <package>/package.jsonExpected: @moltnet/* entries appear only under devDependencies, never
under dependencies.
Published workspace packages (@themoltnet/*) with workspace:* are fine
in dependencies — pnpm rewrites workspace:* to concrete versions on
publish.
Every ordinary publishable Node project with a build target must opt into
createNxDependencyChecksConfig in its local eslint.config.mjs.
Keep includeTransitiveDependencies at its default (false) unless the entire
transitive graph is intentionally shipped as manifest dependencies. A Vite SSR
package that bundles private @moltnet/* libraries will otherwise receive
false demands for their implementation dependencies.
Document every ignoredDependencies entry next to the exception. Valid
exceptions include:
@moltnet/* build inputs that are bundled into dist;Nx dependency checks compare the source/project graph with package.json.
They do not inspect emitted JS, package.json#files, code-split chunks, or
the packed manifest. A passing lint target is never publication proof.
Run through Nx:
pnpm exec nx run <project>:lintFor publishable Node packages, use this default:
@moltnet/* packages and keep them in devDependencies.@themoltnet/* packages stay in dependencies with workspace:*; third-party
packages stay in dependencies or peerDependencies.Vite SSR normally externalizes registry dependencies, but source-direct
workspace exports can still be bundled. For Vite 8/Rolldown, put public
workspace dependencies in the active build-level external option. In this
repository, use externalizeInstallableDependencies from vite.shared.ts in
build.rolldownOptions.external (or build.rollupOptions.external for configs
driven by Rollup). It derives externals from the package's dependencies,
optionalDependencies, and peerDependencies, excluding private @moltnet/*.
Do not duplicate dependency-name arrays. The predicate fails the build when a
bundled private package reaches an undeclared third-party import; promote that
package to the public manifest. Do not rely on comments and ssr.external:
inspect emitted JS.
For bundled packages (Vite SSR), verify the bundle doesn't contain runtime imports to private workspace packages:
# Build the package and its workspace deps first
pnpm exec nx run <project>:build
# Check the bundle has no @moltnet/ imports
rg '@moltnet/' <package>/distExpected: zero matches. All @moltnet/* code should be inlined.
Also verify that runtime imports for public workspace dependencies remain in the
emitted JS and that asset filenames such as .wasm, migrations, and native
bindings were not detached from the package that owns them.
pnpm exec nx run <project>:check:packThis validates:
dist/index.js exists in the tarballdist/index.d.ts exists (for library packages)src/ files leak into the tarball@moltnet/ imports in .d.ts files@moltnet/ packages in dependenciesThis relative-import check is what catches a generated dist/assets/*.js
chunk that exists after build but is excluded by package.json#files.
pnpm pack --dry-run --json | jq '.files[].path'Check that:
dist/ and package.json)Use pnpm pack, not npm pack, when validating the publish transformation:
pnpm resolves catalog: and workspace:* protocols for the packed manifest.
pnpm pack
test_dir="$(mktemp -d)"
cd "$test_dir"
npm init -y
npm install <tarball-path>
node -e "import('<package-name>')"Use a clean temporary consumer for packages with assets, native bindings, source
exports, or chained public workspace dependencies. Pack and install the whole
local dependency set so the smoke tests published dist exports rather than
workspace source shortcuts.
Importing a package or running --help only checks eagerly loaded modules.
For lazy adapters, optional native dependencies, dynamic imports, and command
branches, add a focused smoke that exercises that path. Do not claim the smoke
loads the full module graph unless it actually invokes those paths.
What happened: @themoltnet/tasks-orchestrator@0.2.0 built, tested, and
passed the old check:pack, but all three npm publish attempts failed with
E422. The Sigstore provenance bundle identified
https://github.com/getlarge/themoltnet, while the packed package.json had
no repository.url.
Why it was not caught: The validator checked tarball contents and private workspace dependency leaks, but not metadata consumed by npm trusted publishing.
Prevention: Copy the canonical repository object when making any package
publishable. Run check:pack before merge; it now checks both the repository
URL and the package directory.
What happened: @moltnet/api-client, @moltnet/crypto-service, and
@themoltnet/design-system were listed in dependencies instead of
devDependencies in @themoltnet/legreffier. Vite correctly bundled them
into dist/index.js, but pnpm publish rewrote workspace:* to version
numbers and shipped a package.json that referenced unpublished packages.
Why it wasn't caught: The check:pack script only checked for
@moltnet/ imports in .d.ts files and src/ leaks in the tarball. It
didn't check the dependencies field itself.
Prevention: The check:pack script now validates that no @moltnet/*
packages appear in dependencies. Run it before every publish.
What happened: Release CI for @themoltnet/pi-extension@0.21.0 passed
the Vite build, then failed check:pack because @moltnet/tasks appeared
in dependencies.
Why this matters: @moltnet/tasks is private. Consumers cannot install it
from npm. If pi-extension imports it and Vite bundles it via ssr.noExternal,
it belongs in devDependencies, not dependencies.
Prevention:
@moltnet/tasks like every other private @moltnet/* package.devDependencies.@themoltnet/* packages in dependencies only when runtime
consumers need to install them.private workspace packages in dependencies, fix
package.json placement before changing Vite.What happened: the daemon bundled the source-direct @themoltnet/sdk/node
entry. SDK's lazy OS-keyring import became a relative dist/assets/*.js chunk,
but agent-daemon's files allowlist shipped only dist/*.js. The local build
worked while the npm tarball failed to resolve OAuth2 client secrets.
Why lint and the old smoke missed it: Nx dependency checks were not enabled for agent-daemon and cannot inspect tarball file selection. The bin smoke ran a help path that never invoked OAuth/keyring resolution.
Prevention: externalize installable public dependencies with
externalizeInstallableDependencies, validate relative imports against actual
tarball entries, and exercise lazy runtime paths when they are release-critical.
@themoltnet/sdk does it right:
@moltnet/api-client and @moltnet/crypto-service in devDependenciesssr.noExternal: [/@moltnet\//] in vite.config.ts to explicitly bundle@noble/* (published) packages in dependenciesThe check:pack target runs in the affected pre-merge CI graph and again in the
release workflow before pnpm publish. Pre-merge CI sets
MOLTNET_SKIP_REGISTRY_SMOKE=1 because a coordinated release may reference a
workspace version that is not on npm yet; it still runs the tarball, declaration
and provenance checks. Release jobs run the registry install smokes after their
dependency packages publish and remain the last line of defense.
# From .github/workflows/release.yml
- run: pnpm exec nx run <project>:check:pack
- run: pnpm --filter <package> publish --no-git-checks --access public --provenancedependencies to devDependencies requires
updating the lockfile (pnpm install)files field in package.json controls what goes in the tarball —
keep it minimal5daa9ca
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.