Skip to content

Give TypeScript test modules their own lockfile identity - #16

Merged
arcaputo3 merged 1 commit into
agent/bun-symlink-stagingfrom
agent/bun-test-lockfile
Aug 26, 2026
Merged

Give TypeScript test modules their own lockfile identity#16
arcaputo3 merged 1 commit into
agent/bun-symlink-stagingfrom
agent/bun-test-lockfile

Conversation

@arcaputo3

Copy link
Copy Markdown
Contributor

Stack 3/N. Base is agent/bun-symlink-staging (#15), not main.

The bug

BunTypeScriptTests.npmInstall builds a package.json that is a strict superset of the outer module's — outer deps plus test-only deps. But bunLockfile is not a member of Mill's TypeScriptTests, so the unqualified reference resolved through Scala's outer scope to the enclosing module's bun.lock.

Installing a superset package.json against that lock under --frozen-lockfile fails:

error: lockfile had changes, but lockfile is frozen

And there was no test.bunLock to generate a matching one. Any TypeScript test module declaring a dependency the outer module lacks was broken under the shipped default. Verified against real Bun before fixing.

Why CI never saw it

typescript-test-deps is exactly this shape — outer is-even, test is-odd. It passed only because build.mill set MILL_BUN_REQUIRE_LOCKFILE=false for the whole suite.

The fix

  • bunTestPackageJson is one task, shared by npmInstall and bunLock. The install and the lockfile can no longer describe different dependency sets — that divergence is the bug's root.
  • The test trait declares its own bunLockfile at <test module>/bun.lock, plus its own bunLock command. Defined directly rather than by mixing in BunToolchainModule, which would fork bunVersion/bunExecutable into separate task instances and re-download managed Bun per test module.
  • A test module that adds nothing delegates to the outer install, so bare test modules don't suddenly need a second lockfile. Covered by a new test against the typescript-tests fixture.
  • Every Bun member in the test trait is now qualified outer. or this.. That split is what caused this, and it's worth making legible — note that only bunLockfile was ever wrong: transitiveUnmanagedDeps and friends are declared by Mill's TypeScriptTests and correctly bind to the test module, so outer.transitiveUnmanagedDeps() ++ this.transitiveUnmanagedDeps() is not the duplicate it looks like.
  • requireBunLockfile takes the expected path, so the error names the test module's lockfile rather than pointing the user at the wrong file.

Proof

typescript-test-deps now pins bunRequireLockfile = true in the fixture, following the typescript-lock precedent, so the strict path is exercised regardless of how the suite is invoked. Against the parent commit:

X BunTypeScriptIntegrationTests.test deps are devDependencies
X BunTypeScriptIntegrationTests.test modules with extra deps own their lockfile

The new test asserts the two locks are distinct by comparing their workspaces blocks, which record declared dependencies — a whole-file substring match would not distinguish them, since is-odd also arrives transitively through is-even.

Verification

Unit 67 passing, integration 39 passing (was 37).

🤖 Generated with Claude Code

`BunTypeScriptTests.npmInstall` builds a package.json that is a strict
superset of the outer module's, but `bunLockfile` is not a member of Mill's
`TypeScriptTests`, so the unqualified reference resolved through Scala's
outer scope to the *enclosing* module's bun.lock. Installing a superset
package.json against that lock under --frozen-lockfile fails with:

    error: lockfile had changes, but lockfile is frozen

and there was no `test.bunLock` to generate a matching one. Any TypeScript
test module declaring a dependency the outer module lacks was broken under
the shipped default.

- `bunTestPackageJson` is now one task shared by `npmInstall` and `bunLock`,
  so the install and the lockfile cannot describe different dependency sets.
- The test trait declares its own `bunLockfile` at `<test module>/bun.lock`
  and its own `bunLock` command. Defined directly rather than by mixing in
  BunToolchainModule, which would fork bunVersion/bunExecutable into separate
  task instances and re-download managed Bun per test module.
- A test module that adds nothing now delegates to the outer install instead
  of demanding a second lockfile, so bare test modules are unaffected.
- Every Bun member in the test trait is qualified `outer.` or `this.`. The
  outer-vs-inherited split is what caused this, and only `bunLockfile` was
  ever wrong -- `transitiveUnmanagedDeps` and friends are declared by Mill's
  TypeScriptTests and correctly bind to the test module.
- `requireBunLockfile` takes the expected path, so the error names the test
  module's lockfile rather than the outer one.

The typescript-test-deps fixture (outer is-even, test is-odd) now pins
bunRequireLockfile itself, following the typescript-lock precedent, so the
strict path is exercised regardless of the suite's env. On the parent commit
both new tests fail.

Unit 67 passing, integration 39 passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@arcaputo3
arcaputo3 merged commit 36666ff into agent/bun-symlink-staging Aug 26, 2026
@arcaputo3
arcaputo3 deleted the agent/bun-test-lockfile branch August 26, 2026 22:11
@arcaputo3
arcaputo3 restored the agent/bun-test-lockfile branch August 26, 2026 22:16
@arcaputo3
arcaputo3 deleted the agent/bun-test-lockfile branch August 26, 2026 22:16
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