A bundle could import only what the toolchain image carried. Now, when a module's package.json depends on anything beyond @novox/mesh-sdk, the build installs those production dependencies into the module's directory, inside the toolchain image, before tsc, and esbuild inlines them.
What it does (internal/builder/dependencies.go, called from one() in builder.go just before the compile):
npm ci --omit=dev when the module has a package-lock.json, otherwise npm install --omit=dev --no-package-lock. Peers are omitted, install scripts don't run, and the work happens in a scratch copy so the module's own files are never rewritten.
@novox resolves from the mesh's registry (the Npmrc scope and registry the builder is already given, read anonymously, on the host network as image builds are). Everything else comes from the public registry. With no registry configured, a scoped dependency is refused by name rather than resolved publicly.
npm's download cache lives in a named volume (mesh-builder-npm-cache). That cache is content-addressed and checked for integrity on every read. Nothing installed is kept between builds.
A module that depends on nothing but the SDK (or has no package.json) runs exactly the same commands as before.
The SDK stays the toolchain's (issue 212). Before installing, the SDK is removed from the module's dependency, peer and optional lists, so it is never fetched. Afterwards every node_modules/**/@novox/mesh-sdk is deleted. Every import of the SDK therefore resolves past the module's node_modules to the toolchain's /app/node_modules. A module cannot pin a different SDK: the toolchain's SDK is the only one used. I checked that npm ci accepts a package.json with the SDK removed from a lockfile that still lists it.
Tests (dependencies_test.go, using the fake runner): the install runs in the toolchain before the compile with the expected flags and SDK stripping; a module that depends only on the SDK runs identical commands; a scoped package with no registry is refused; an unparseable package.json is refused by name. Without the change, three of these fail; the identical-commands test passes either way by design.
Proven with a real build: this builder, with the real docker runner and a locally built mesh-tools toolchain carrying SDK 0.1.6, built mesh-catalog (pg), mongodb (mongodb) and mssql (mssql) from the catalogue branch in novox/mesh-catalog. Each bundle came out as one file per entrypoint with no node_modules. Each was launched over MCP stdio from a directory with no node_modules above it and answered against real postgres, mongo and SQL Server containers. go test ./... passes against throwaway postgres, apart from the known TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves.
Merge before the mesh-catalog PR for the last three modules.
A bundle could import only what the toolchain image carried. Now, when a module's `package.json` depends on anything beyond `@novox/mesh-sdk`, the build installs those production dependencies into the module's directory, inside the toolchain image, before `tsc`, and esbuild inlines them.
**What it does** (`internal/builder/dependencies.go`, called from `one()` in `builder.go` just before the compile):
- `npm ci --omit=dev` when the module has a `package-lock.json`, otherwise `npm install --omit=dev --no-package-lock`. Peers are omitted, install scripts don't run, and the work happens in a scratch copy so the module's own files are never rewritten.
- `@novox` resolves from the mesh's registry (the `Npmrc` scope and registry the builder is already given, read anonymously, on the host network as image builds are). Everything else comes from the public registry. With no registry configured, a scoped dependency is refused by name rather than resolved publicly.
- npm's download cache lives in a named volume (`mesh-builder-npm-cache`). That cache is content-addressed and checked for integrity on every read. Nothing installed is kept between builds.
- A module that depends on nothing but the SDK (or has no package.json) runs exactly the same commands as before.
**The SDK stays the toolchain's (issue 212).** Before installing, the SDK is removed from the module's dependency, peer and optional lists, so it is never fetched. Afterwards every `node_modules/**/@novox/mesh-sdk` is deleted. Every import of the SDK therefore resolves past the module's node_modules to the toolchain's `/app/node_modules`. A module cannot pin a different SDK: the toolchain's SDK is the only one used. I checked that `npm ci` accepts a package.json with the SDK removed from a lockfile that still lists it.
**Tests** (`dependencies_test.go`, using the fake runner): the install runs in the toolchain before the compile with the expected flags and SDK stripping; a module that depends only on the SDK runs identical commands; a scoped package with no registry is refused; an unparseable package.json is refused by name. Without the change, three of these fail; the identical-commands test passes either way by design.
**Proven with a real build:** this builder, with the real docker runner and a locally built mesh-tools toolchain carrying SDK 0.1.6, built mesh-catalog (`pg`), mongodb (`mongodb`) and mssql (`mssql`) from the catalogue branch in novox/mesh-catalog. Each bundle came out as one file per entrypoint with no node_modules. Each was launched over MCP stdio from a directory with no node_modules above it and answered against real postgres, mongo and SQL Server containers. `go test ./...` passes against throwaway postgres, apart from the known `TestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves`.
Merge before the mesh-catalog PR for the last three modules.
A bundle could import only what the toolchain image carried: the compiler and the bundler resolve an import from the module's directory and then the toolchain's node_modules, and nothing ever put anything in the first. So a module needing a database driver (pg, mongodb, mssql) could not be a bundle, and kept a container whose recipe installed it (hq ADR 0198 §4: the backend's own driver inside the bundle).
Now, when a module's package.json depends on anything beyond the SDK, the build installs its production dependencies into the module's directory, in the toolchain image, before the compile: npm ci from the lockfile when there is one, npm install from the ranges otherwise, the mesh's registry for the SDK's scope and the public one for the rest, install scripts off. esbuild then inlines them. A module depending only on the SDK runs exactly the commands it did before.
The SDK stays the toolchain's (hq issue 212): it is taken out of what is installed and any copy something pulls in is removed, so every import of it resolves past the module's node_modules to the one the toolchain carries; a module's own range never shadows it. npm's verified download cache is a named volume; nothing installed is kept between builds. Without a registry, a scoped package is refused rather than resolved on the public registry.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
A bundle could import only what the toolchain image carried. Now, when a module's
package.jsondepends on anything beyond@novox/mesh-sdk, the build installs those production dependencies into the module's directory, inside the toolchain image, beforetsc, and esbuild inlines them.What it does (
internal/builder/dependencies.go, called fromone()inbuilder.gojust before the compile):npm ci --omit=devwhen the module has apackage-lock.json, otherwisenpm install --omit=dev --no-package-lock. Peers are omitted, install scripts don't run, and the work happens in a scratch copy so the module's own files are never rewritten.@novoxresolves from the mesh's registry (theNpmrcscope and registry the builder is already given, read anonymously, on the host network as image builds are). Everything else comes from the public registry. With no registry configured, a scoped dependency is refused by name rather than resolved publicly.mesh-builder-npm-cache). That cache is content-addressed and checked for integrity on every read. Nothing installed is kept between builds.The SDK stays the toolchain's (issue 212). Before installing, the SDK is removed from the module's dependency, peer and optional lists, so it is never fetched. Afterwards every
node_modules/**/@novox/mesh-sdkis deleted. Every import of the SDK therefore resolves past the module's node_modules to the toolchain's/app/node_modules. A module cannot pin a different SDK: the toolchain's SDK is the only one used. I checked thatnpm ciaccepts a package.json with the SDK removed from a lockfile that still lists it.Tests (
dependencies_test.go, using the fake runner): the install runs in the toolchain before the compile with the expected flags and SDK stripping; a module that depends only on the SDK runs identical commands; a scoped package with no registry is refused; an unparseable package.json is refused by name. Without the change, three of these fail; the identical-commands test passes either way by design.Proven with a real build: this builder, with the real docker runner and a locally built mesh-tools toolchain carrying SDK 0.1.6, built mesh-catalog (
pg), mongodb (mongodb) and mssql (mssql) from the catalogue branch in novox/mesh-catalog. Each bundle came out as one file per entrypoint with no node_modules. Each was launched over MCP stdio from a directory with no node_modules above it and answered against real postgres, mongo and SQL Server containers.go test ./...passes against throwaway postgres, apart from the knownTestTheResolverIsToldEveryMachineOnTheNetworkAndToldAgainWhenOneLeaves.Merge before the mesh-catalog PR for the last three modules.