Make the examples runnable, wire them into CI, finish the scaffolding - #26
Merged
arcaputo3 merged 11 commits intoAug 27, 2026
Merged
Conversation
…v hooks The TypeScript install task is canonically bunInstall; Mill's inherited npmInstall delegates to it and stays usable. All internal call sites use the canonical name, so install artifacts live in bunInstall.dest. Both module kinds share one test entrypoint name: testForked. The TypeScript test command `test` is a deprecated alias, and the Scala.js bunTest — which duplicated the inherited testForked exactly, since the overridden jsEnvConfig and testLinkTask already put every test run on Bun — is deprecated in its favor. TypeScript bunBundleFormat is Option[String], matching Scala.js. The type disagreement was the one rename no alias could bridge; 0.3.0 takes the one-time break, and None now lets `bun build` infer the format. BunWorkersModule.workerBundleFormat follows it directly. Environment hooks share one vocabulary: bunToolEnv (install, lock, build subprocesses) is defined once on BunToolchainModule and used by every runBun call site — Scala.js, TypeScript, workspace, publish, and workers modules previously mixed bunEnv() and bunToolEnv() — and the TypeScript bunRuntimeEnv is public instead of protected. Integration tests exercise the canonical names throughout, with explicit alias regression coverage: npmInstall must resolve to bunInstall.dest, and the deprecated test / bunTest commands must keep working until their removal at 1.0. MIGRATING-0.3.md documents the new rows and the bunBundleFormat break with a before/after example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
build.mill forced MILL_BUN_REQUIRE_LOCKFILE=false across the entire integration suite, so the strict-lockfile default — the plugin's headline 0.3.0 behavior — was exercised by almost nothing. Every install-path bug fixed earlier in this stack lived in exactly that gap. All 13 real-bun dependency-bearing fixtures now commit a bun.lock generated by the pinned Bun 1.4.0 and install frozen in CI. The RegenerateFixtureLocks entry point re-locks every fixture in one command (MILL_BUN_REGENERATE_LOCKS=1 gates it; without the env it lists what it would do and touches nothing). The forkEnv default is removed; scalajs-dependency-manifests opts out in its own build.mill because its stub cannot produce a real lock. scalajs-transitive is restructured: lib and app both claimed the workspace root as moduleDir, so both resolved the same bun.lock and only one dependency set could be recorded there. lib now lives in lib/ with its own lockfile identity. Committed locks make the Bun pin load-bearing — bun.lock is forward- but not backward-compatible, and the raw UnknownLockfileVersion error from an older Bun never mentions how to recover. A frozen install against a lock the pinned Bun cannot read now fails before bun runs, telling the user to regenerate with bunLock or raise bunVersion. The supported-lockfile-version table lives beside the checksum table, and a unit test forces every bundled Bun to declare its entry. The new typescript-stale-lock fixture proves the guard fires before any bun subprocess is reached, and fails with the guard neutralized. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The plugin carries Windows-specific code — bun.exe extraction, executableCandidates, the mill.bat fork path in the integration harness — with zero coverage. Both jobs now run on windows-latest with fail-fast off, using bash as the step shell so one ./mill invocation works on all three platforms. The official Mill 1.1.5 mill.bat launcher is committed; build.mill already selects it on Windows, but it never existed, so the integration suite could not have forked a fixture build there at all. Both jobs also cache ~/.cache/mill-bun keyed on the toolchain source, so a managed-Bun run downloads each pinned archive once per platform rather than once per CI run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sh wrapper fetches Mill's native launcher, which has no Windows build — every Windows step died instantly with "This native mill launcher supports only Linux and macOS." A launcher-selection step now points Windows at the committed mill.bat and everything else at ./mill. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All five fixture suites copy-pasted the same tester/outputPath/ commandLogPath helpers and each re-encoded Mill's out/<module>/<task> layout; BunIntegrationSuite now owns both, and the regenerator uses it too. bundle and bundleFast differed only in linker and --bytecode across 30 duplicated lines of args assembly; both now delegate to one Task.Anon body. bundleFast never bytecode-compiles — it exists for iteration speed — and its scaladoc says so. First-ever coverage for three shipped features: - BunSQLiteModule: typescript-compile mixes it in with a discovered data/app.db and asserts it lands in the compile workspace. The fixture chains bunCompileResources through super — an unqualified override silently drops the mixin's contribution, which is exactly the mistake a user would make, so the fixture models the right form. - npmOverrides end-to-end: the new typescript-overrides fixture has lib and app pinning conflicting is-odd specifiers; the override resolves the install against a committed lock, and the generated package.json carries both the resolved specifier and the overrides block. - bunDoctor: evaluated in the managed-toolchain suite. Also fixes the two scaladoc examples that still used Mill 0.x's millSourcePath (BunSQLiteModule, BunWorkersModule) — neither compiled. Re-running RegenerateFixtureLocks against the committed locks produces zero diffs, confirming lock generation is deterministic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t there os.perms throws on NTFS, and bun.exe needs no executable bit — the permission half of the cross-filesystem publish test now runs only on POSIX. First genuine Windows finding; the managed-bun leg (download, extraction, shared cache, eviction recovery, mill.bat forking) already passes on windows-latest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
example-scalajs and example-typescript contained only a build.mill — the TypeScript one still using the pre-break bunBundleFormat: String form, so it did not even compile — while the README called them runnable. Both are now the README quick-starts made real: sources, tests, committed bun.lock, and their own mill/mill.bat wrappers. Both verified end to end (lock, run, testForked) against a locally published 0.3.0. The examples/ showcase needed locks for its two dependency-bearing modules — the strict default caught them with exactly the intended error — and now compiles fully. A new examples CI job publishes the plugin locally at whatever version the examples pin (extracted from the build header, so a release-version sweep cannot desynchronize the leg), runs both quick-starts, and compiles examples/. publishVersion is Task.Input: a plain Task captured PUBLISH_VERSION once and served the stale value until a manual clean — the runbook documented the workaround, and this session hit it for real when a 0.3.0 publishLocal left publishLocalTestRepo without the 0.0.0-NIGHTLY that every fixture pins. The fixtures then silently resolved a five-month-old jar from ~/.ivy2/local and 32 integration tests failed mysteriously. The fixture repo fallback also drops ivy2Local, so a stale local jar can never shadow the test repo again; the runbook note about cleaning cached publish metadata is gone because the footgun is. Scaffolding: CHANGELOG promoted to [0.3.0]; README's development section shows the shipped-defaults suite and one-command lock regeneration; RELEASING.md notes that mill-bun_mill1_3 is Mill's Scala 3 artifact mangling rather than a typo; the tracked-but-ignored .mill-jvm-version gitignore entry and the never-referenced millExecutable object are removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fixture's bun-proxy is a POSIX sh script, which CreateProcess cannot execute — the only failure in the entire Windows integration suite. The fixture now carries bun-proxy.cmd with identical semantics (log first-arg:marker, delegate to PATH bun, propagate the exit code) and picks the right one per platform. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
arcaputo3
changed the base branch from
agent/bun-test-harness
to
agent/idiomatic-scalajs-mill-workflows
August 27, 2026 00:57
arcaputo3
merged commit Aug 27, 2026
a46b3b4
into
agent/idiomatic-scalajs-mill-workflows
7 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.
Stacked on #25 (
agent/bun-test-harness). The last planned PR of the 0.3.0 sweep.Runnable examples, actually runnable
example-scalajs/andexample-typescript/contained only abuild.mill(the TypeScript onestill using the pre-break
bunBundleFormat: Stringform, so it did not even compile) while theREADME called them runnable. Both are now the README quick-starts made real — sources, tests,
committed
bun.lock, and their ownmill/mill.batwrappers:@JSImportwith thebun"…"interpolator;runprintsthrough lodash and
test.testForkedruns utest on the Bun JS runtime.runexecutes andtest.testForkedasserts the route'sresponse via
bun:test.Both verified locally end to end (lock → run → test) against a locally published 0.3.0. The
examples/showcase project needed locks for its two dependency-bearing modules — the strictdefault caught them with exactly the intended error — and now compiles fully (516/516 tasks).
Examples CI leg
A new
examplesjob publishes the plugin locally at whatever version the examples pin(extracted from the build header, so a release-version sweep cannot desynchronize the leg), runs
both quick-starts'
run+testForked, and compiles the fullexamples/project.Scaffolding and docs
[Unreleased]promoted to[0.3.0].lock regeneration.
docs/RELEASING.mdgains the note thatmill-bun_mill1_3is Mill's Scala 3 artifact mangling,not a typo — so nobody "fixes" it.
millSourcePathwere fixed in Share the integration harness and cover the last untested features #25; this PR removesthe remaining scaffolding debris: the tracked-but-ignored
.mill-jvm-versiongitignore entryand the never-referenced
object millExecutablein build.mill.Deliberately deferred
A tree-wide scalafmt landing now would conflict with every open stacked PR and drown their
diffs; it should land as the final commit before #13 un-drafts, together with the format check.
Found the hard way:
publishVersionis nowTask.InputWhile verifying the examples, a
PUBLISH_VERSION=0.3.0 publishLocalleftpublishLocalTestRepowithout the
0.0.0-NIGHTLYevery fixture pins — becausepublishVersionreadTask.envfrom aplain cached
Task, which captured the env once and served the stale value until a manualclean. The runbook even documented that workaround. The fixtures then silently resolved afive-month-old jar from
~/.ivy2/localand 32 integration tests failed with errors that hadnothing to do with the cause.
Two durable fixes:
publishVersionisTask.Input, so switchingPUBLISH_VERSIONbetween runs re-evaluatesinstead of poisoning downstream publish tasks. The runbook's clean-workaround note is deleted
because the footgun is.
ivy2Local(publishLocalTestRepo|central), so a stalelocal jar can never shadow the test repo again, no matter what state a developer's
~/.ivy2/localis in.