Give TypeScript test modules their own lockfile identity - #16
Merged
Conversation
`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>
This was referenced Aug 26, 2026
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.
Stack 3/N. Base is
agent/bun-symlink-staging(#15), notmain.The bug
BunTypeScriptTests.npmInstallbuilds a package.json that is a strict superset of the outer module's — outer deps plus test-only deps. ButbunLockfileis not a member of Mill'sTypeScriptTests, so the unqualified reference resolved through Scala's outer scope to the enclosing module'sbun.lock.Installing a superset package.json against that lock under
--frozen-lockfilefails:And there was no
test.bunLockto 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-depsis exactly this shape — outeris-even, testis-odd. It passed only becausebuild.millsetMILL_BUN_REQUIRE_LOCKFILE=falsefor the whole suite.The fix
bunTestPackageJsonis one task, shared bynpmInstallandbunLock. The install and the lockfile can no longer describe different dependency sets — that divergence is the bug's root.bunLockfileat<test module>/bun.lock, plus its ownbunLockcommand. Defined directly rather than by mixing inBunToolchainModule, which would forkbunVersion/bunExecutableinto separate task instances and re-download managed Bun per test module.typescript-testsfixture.outer.orthis.. That split is what caused this, and it's worth making legible — note that onlybunLockfilewas ever wrong:transitiveUnmanagedDepsand friends are declared by Mill'sTypeScriptTestsand correctly bind to the test module, soouter.transitiveUnmanagedDeps() ++ this.transitiveUnmanagedDeps()is not the duplicate it looks like.requireBunLockfiletakes the expected path, so the error names the test module's lockfile rather than pointing the user at the wrong file.Proof
typescript-test-depsnow pinsbunRequireLockfile = truein the fixture, following thetypescript-lockprecedent, so the strict path is exercised regardless of how the suite is invoked. Against the parent commit:The new test asserts the two locks are distinct by comparing their
workspacesblocks, which record declared dependencies — a whole-file substring match would not distinguish them, sinceis-oddalso arrives transitively throughis-even.Verification
Unit 67 passing, integration 39 passing (was 37).
🤖 Generated with Claude Code