Deliver unmanagedDeps through package.json, not positional install args - #21
Merged
arcaputo3 merged 1 commit intoAug 27, 2026
Conversation
Local paths were passed as positional arguments to bun install, which turns the invocation into bun add — unconditionally rejected by --frozen-lockfile. unmanagedDeps therefore never worked against a lockfile, in any module type, and a lock produced by bunLock recorded the absolute checkout path. Each entry is now staged into vendor/ beside the generated package.json and declared as file:./vendor/<name>, so the lock records the checkout-independent file:vendor/<name>. Validated against real Bun 1.4.0: the lock survives relocating the repository and frozen installs resolve the package. The staging excludes the local package's own node_modules; bun resolves its declared dependencies through the lockfile instead. Applied uniformly: Scala.js and TypeScript installs and bunLock, the TypeScript test trait (union of outer and test-side deps, preserving the bare-module reuse equality), the publish module's vendored runtime install, and Bun workspaces — where vendor trees are staged in the layout beside each member's package.json. A name declared both as an npm dependency and an unmanaged dependency is rejected, as are two unmanaged directories claiming the same package name. Published manifests are unaffected: they are built from the npm dependency lists, so file: specifiers cannot leak to consumers. This is the first coverage unmanagedDeps has ever had. The new typescript-unmanaged fixture locks, frozen-installs, bundles, and runs against a local package with bunRequireLockfile enabled; the mixed-workspace fixture asserts the member manifest and staged vendor tree and that no positional args reach the stubbed install. Both fail on the parent commit — the TypeScript lock never contains file:vendor/local-lib, and the workspace member manifest has no shared-local entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
arcaputo3
merged commit Aug 27, 2026
ddc64dd
into
agent/idiomatic-scalajs-mill-workflows
8 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Onto the #13 branch (the collapsed stack), targeting
agent/idiomatic-scalajs-mill-workflows.The bug
unmanagedDepswas unconditionally broken under a lockfile — in every module type. Local pathswere passed as positional arguments to
bun install, which turns the invocation intobun add, andbun addis always rejected by--frozen-lockfile. Without a lockfile it"worked", but a lock produced by
bunLockrecorded the absolute checkout path, so the lockwas unusable on any other machine. No fixture, example, or README section ever exercised the
feature, which is how this survived.
The fix
Local packages now travel through the generated package.json, like every other dependency:
vendor/beside the generated package.json and declared asfile:./vendor/<name>(name read from the package's own package.json; scoped names map toone path segment,
@scope/pkg→scope+pkg, matching the workspace layout convention).file:vendor/<name>— validated against real Bun 1.4.0: the lockfilesurvives relocating the repository, and frozen installs resolve the package.
node_modules; bun resolves the package's declareddependencies through the lockfile instead.
Applied uniformly across all six install/lock sites: Scala.js (
bunInstall/bunLock),TypeScript (
npmInstall/bunLock), the TypeScript test trait (union of outer and test-sideunmanaged deps — the bare-module reuse equality from #20 is preserved), the publish module's
vendored-runtime install, and Bun workspaces, where vendor trees are staged in the layout beside
each member's package.json so member-relative
file:./vendor/…specifiers resolve in bothstandalone and workspace installs.
Guard rails: a name declared both as an npm dependency and an unmanaged directory is rejected
(one resolution source per package), as are two unmanaged directories claiming the same name.
Tarballs are rejected with guidance to unpack them. Published manifests are unaffected — they
are built from the npm dependency lists, so
file:specifiers cannot leak to consumers.Tests — first-ever coverage for this feature
typescript-unmanagedfixture (new, real Bun,bunRequireLockfile = true):bunLockrecords
file:vendor/local-liband no absolute path; a frozennpmInstallmaterializes thepackage; the bundle runs and prints from the local package. Fails on the parent commit
(the lock never contains
file:vendor/local-lib).mixed-workspace: the Scala.js member declares an unmanaged dep; the test asserts themember manifest carries the
file:specifier, the vendor tree is staged in the install, andno positional args reach the (recorded) install invocation. Fails on the parent commit.
errors, and node_modules exclusion during staging.
Full suites green: unit and integration, exit 0.