From 259f198be5498d78b934ffcfef9d3d3273e4d9a6 Mon Sep 17 00:00:00 2001 From: Richie Caputo Date: Fri, 17 Apr 2026 17:17:30 -0400 Subject: [PATCH] feat(scalajs): Overridable test-time JS env (bunTestJsEnv) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exposes a new `BunScalaJSTests.bunTestJsEnv: T[Map[String, String]]` task that feeds the Scala.js test process's environment, and overrides `jsEnvConfig` inside `BunScalaJSTests` so tests source their env from it. Defaults to `outer.bunJsEnv()` unchanged — no behavior change for existing users. Opt-in downstream overrides look like: object test extends BunScalaJSTests: override def bunTestJsEnv = Task { super.bunTestJsEnv() + ("NODE_ENV" -> "production") } The motivating case: tests that spin up an in-process `Bun.serve({...})` want `NODE_ENV=production` so Bun's `development` option defaults to `false`. Otherwise any fetch-handler Promise rejection gets rewritten into a ~100 KB HTML `BunError` React-overlay `Response`, and the test asserts against the overlay HTML instead of the real error — masking the bug. Giving tests their own env map surfaces those rejections as proper errors without disturbing `bunRun`/production invocations on the outer module. Also adds a CHANGELOG entry documenting the 0.2.1 semantics. Verified via a downstream `fast-mcp-scala` `publishLocal` round-trip — a previously-blocked Scala.js HTTP test that POSTs an MCP initialize request to an in-process `Bun.serve` now passes. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 29 ++++++++++++++ .../scalajslib/bun/BunScalaJSModule.scala | 38 +++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb9f985..e276dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Tag-driven release workflow for Maven Central publishing and GitHub releases. - Release runbook covering secrets, version sweep, annotated tags, and verification. +## [0.2.1] - Overridable test-time JS env + +### Added + +- `BunScalaJSTests.bunTestJsEnv` — a test-scoped override for the Scala.js + test process environment. Defaults to the outer module's `bunJsEnv` + unchanged (no behavior change for existing users). +- `BunScalaJSTests` now owns its own `jsEnvConfig` that sources its env + from `bunTestJsEnv`, so overriding that map is enough to diverge test + env from production `bunRun`. + +### Why + +Tests that spin up an in-process `Bun.serve({...})` typically want +`NODE_ENV=production`, which flips Bun's `development` default to +`false`. Otherwise a fetch-handler Promise rejection is rewritten into +a ~100 KB HTML `BunError` React-overlay `Response` — the test then +asserts against the overlay HTML instead of the real error, which +hides bugs. + +Downstream libraries can now opt in with one override: + +```scala +object test extends BunScalaJSTests: + override def bunTestJsEnv = Task { + super.bunTestJsEnv() + ("NODE_ENV" -> "production") + } +``` + diff --git a/millbun/src/mill/scalajslib/bun/BunScalaJSModule.scala b/millbun/src/mill/scalajslib/bun/BunScalaJSModule.scala index 98a1706..c4fe0a3 100644 --- a/millbun/src/mill/scalajslib/bun/BunScalaJSModule.scala +++ b/millbun/src/mill/scalajslib/bun/BunScalaJSModule.scala @@ -450,6 +450,44 @@ trait BunScalaJSModule extends ScalaJSConfigModule with BunToolchainModule { out } } + /** + * Runtime environment for the JS process that drives the Scala.js test + * framework. Defaults to the outer module's [[bunJsEnv]] unchanged, so + * plain test runs behave exactly like before. + * + * Override this when your tests need environment variables that differ + * from production `bunRun` invocations. A common case: set + * `NODE_ENV=production` so that in-process [[https://bun.sh/docs/api/http + * `Bun.serve({...})`]] calls default `development: false`. Otherwise + * Bun's dev-mode error overlay rewrites any fetch-handler Promise + * rejection into a ~100 KB HTML `Response` (the `BunError` React + * bundle), which masks the real error inside a running test and makes + * HTTP-level assertions impossible. Example: + * + * {{{ + * object test extends BunScalaJSTests: + * override def bunTestJsEnv = Task { + * super.bunTestJsEnv() + ("NODE_ENV" -> "production") + * } + * }}} + */ + def bunTestJsEnv: T[Map[String, String]] = Task { outer.bunJsEnv() } + + /** + * Scala.js environment used specifically for the test framework process. + * Mirrors [[outer.jsEnvConfig]] but sources its `env` from + * [[bunTestJsEnv]] so tests can diverge (e.g. override `NODE_ENV`) + * without affecting `bunRun` on the outer module. + */ + override def jsEnvConfig: T[JsEnvConfig] = Task { + JsEnvConfig.NodeJs( + executable = outer.bunExecutable(), + args = outer.bunJsEnvArgs().toList, + env = bunTestJsEnv(), + sourceMap = outer.scalaJSSourceMap() + ) + } + override protected def testLinkTask: Task[Report] = Task.Anon { val linkConfig = outer.moduleKind() match {