Skip to content

Deliver unmanagedDeps through package.json, not positional install args - #21

Merged
arcaputo3 merged 1 commit into
agent/idiomatic-scalajs-mill-workflowsfrom
agent/bun-unmanaged-deps
Aug 27, 2026
Merged

Deliver unmanagedDeps through package.json, not positional install args#21
arcaputo3 merged 1 commit into
agent/idiomatic-scalajs-mill-workflowsfrom
agent/bun-unmanaged-deps

Conversation

@arcaputo3

Copy link
Copy Markdown
Contributor

Onto the #13 branch (the collapsed stack), targeting agent/idiomatic-scalajs-mill-workflows.

The bug

unmanagedDeps was unconditionally broken under a lockfile — in every module type. Local paths
were passed as positional arguments to bun install, which turns the invocation into
bun add, and bun add is always rejected by --frozen-lockfile. Without a lockfile it
"worked", but a lock produced by bunLock recorded the absolute checkout path, so the lock
was 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:

  • Each entry is staged into vendor/ beside the generated package.json and declared as
    file:./vendor/<name> (name read from the package's own package.json; scoped names map to
    one path segment, @scope/pkgscope+pkg, matching the workspace layout convention).
  • The lock records file:vendor/<name> — validated against real Bun 1.4.0: the lockfile
    survives relocating the repository, and frozen installs resolve the package.
  • The staged copy excludes the package's own node_modules; bun resolves the package's declared
    dependencies 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-side
unmanaged 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 both
standalone 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-unmanaged fixture (new, real Bun, bunRequireLockfile = true): bunLock
    records file:vendor/local-lib and no absolute path; a frozen npmInstall materializes the
    package; 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 the
    member manifest carries the file: specifier, the vendor tree is staged in the install, and
    no positional args reach the (recorded) install invocation. Fails on the parent commit.
  • Unit tests for name extraction (incl. scoped), the missing-package.json and name-collision
    errors, and node_modules exclusion during staging.

Full suites green: unit and integration, exit 0.

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 arcaputo3 closed this Aug 26, 2026
@arcaputo3 arcaputo3 reopened this Aug 26, 2026
@arcaputo3
arcaputo3 merged commit ddc64dd into agent/idiomatic-scalajs-mill-workflows Aug 27, 2026
8 checks passed
@arcaputo3
arcaputo3 deleted the agent/bun-unmanaged-deps branch August 27, 2026 00:56
@arcaputo3
arcaputo3 restored the agent/bun-unmanaged-deps branch August 27, 2026 00:57
@arcaputo3
arcaputo3 deleted the agent/bun-unmanaged-deps branch August 27, 2026 00:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant