Skip to content

Commit 721bbcf

Browse files
committed
- Add Bun as a third WASM runtime (--wasm-runtime bun / //> using wasmRuntime bun)
- Add BunNotFoundError with install hint - Add integration test for Bun (conditional on bun being on PATH) - Add actions/setup-node@v6 node-version:24 to all Linux integration test jobs: the default Node.js on ubuntu-24.04 runners is too old for Scala.js WASM GC (which requires Node.js >= 22). Matches docs-tests job which already pins node-version: 24
1 parent 1e359b0 commit 721bbcf

10 files changed

Lines changed: 149 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ jobs:
158158
if: env.SHOULD_RUN == 'true'
159159
with:
160160
jvm: "temurin:17"
161+
- uses: actions/setup-node@v6
162+
with:
163+
node-version: 24
161164
- name: JVM integration tests
162165
if: env.SHOULD_RUN == 'true'
163166
run: ./mill -i integration.test.jvmBootstrapped
@@ -196,6 +199,9 @@ jobs:
196199
if: env.SHOULD_RUN == 'true'
197200
with:
198201
jvm: "temurin:17"
202+
- uses: actions/setup-node@v6
203+
with:
204+
node-version: 24
199205
- name: JVM integration tests
200206
if: env.SHOULD_RUN == 'true'
201207
run: ./mill -i integration.test.jvm
@@ -234,6 +240,9 @@ jobs:
234240
if: env.SHOULD_RUN == 'true'
235241
with:
236242
jvm: "temurin:17"
243+
- uses: actions/setup-node@v6
244+
with:
245+
node-version: 24
237246
- name: JVM integration tests
238247
if: env.SHOULD_RUN == 'true'
239248
run: ./mill -i integration.test.jvm
@@ -272,6 +281,9 @@ jobs:
272281
if: env.SHOULD_RUN == 'true'
273282
with:
274283
jvm: "temurin:17"
284+
- uses: actions/setup-node@v6
285+
with:
286+
node-version: 24
275287
- name: JVM integration tests
276288
if: env.SHOULD_RUN == 'true'
277289
run: ./mill -i integration.test.jvm
@@ -310,6 +322,9 @@ jobs:
310322
if: env.SHOULD_RUN == 'true'
311323
with:
312324
jvm: "temurin:17"
325+
- uses: actions/setup-node@v6
326+
with:
327+
node-version: 24
313328
- name: JVM integration tests
314329
if: env.SHOULD_RUN == 'true'
315330
run: ./mill -i integration.test.jvm
@@ -348,6 +363,9 @@ jobs:
348363
if: env.SHOULD_RUN == 'true'
349364
with:
350365
jvm: "temurin:17"
366+
- uses: actions/setup-node@v6
367+
with:
368+
node-version: 24
351369
- name: JVM integration tests
352370
if: env.SHOULD_RUN == 'true'
353371
run: ./mill -i integration.test.jvm
@@ -436,6 +454,9 @@ jobs:
436454
with:
437455
name: linux-launchers
438456
path: artifacts/
457+
- uses: actions/setup-node@v6
458+
with:
459+
node-version: 24
439460
- name: Native integration tests
440461
if: env.SHOULD_RUN == 'true'
441462
run: ./mill -i nativeIntegrationTests
@@ -482,6 +503,9 @@ jobs:
482503
with:
483504
name: linux-launchers
484505
path: artifacts/
506+
- uses: actions/setup-node@v6
507+
with:
508+
node-version: 24
485509
- name: Native integration tests
486510
if: env.SHOULD_RUN == 'true'
487511
run: ./mill -i nativeIntegrationTests
@@ -528,6 +552,9 @@ jobs:
528552
with:
529553
name: linux-launchers
530554
path: artifacts/
555+
- uses: actions/setup-node@v6
556+
with:
557+
node-version: 24
531558
- name: Native integration tests
532559
if: env.SHOULD_RUN == 'true'
533560
run: ./mill -i nativeIntegrationTests
@@ -574,6 +601,9 @@ jobs:
574601
with:
575602
name: linux-launchers
576603
path: artifacts/
604+
- uses: actions/setup-node@v6
605+
with:
606+
node-version: 24
577607
- name: Native integration tests
578608
if: env.SHOULD_RUN == 'true'
579609
run: ./mill -i nativeIntegrationTests
@@ -620,6 +650,9 @@ jobs:
620650
with:
621651
name: linux-launchers
622652
path: artifacts/
653+
- uses: actions/setup-node@v6
654+
with:
655+
node-version: 24
623656
- name: Native integration tests
624657
if: env.SHOULD_RUN == 'true'
625658
run: ./mill -i nativeIntegrationTests
@@ -713,6 +746,9 @@ jobs:
713746
with:
714747
name: linux-aarch64-launchers
715748
path: artifacts/
749+
- uses: actions/setup-node@v6
750+
with:
751+
node-version: 24
716752
- name: Native integration tests
717753
if: env.SHOULD_RUN == 'true'
718754
run: ./mill -i nativeIntegrationTests
@@ -759,6 +795,9 @@ jobs:
759795
with:
760796
name: linux-aarch64-launchers
761797
path: artifacts/
798+
- uses: actions/setup-node@v6
799+
with:
800+
node-version: 24
762801
- name: Native integration tests
763802
if: env.SHOULD_RUN == 'true'
764803
run: ./mill -i nativeIntegrationTests
@@ -1417,6 +1456,9 @@ jobs:
14171456
- name: Build slim docker image
14181457
if: env.SHOULD_RUN == 'true'
14191458
run: .github/scripts/generate-slim-docker-image.sh
1459+
- uses: actions/setup-node@v6
1460+
with:
1461+
node-version: 24
14201462
- name: Native integration tests
14211463
if: env.SHOULD_RUN == 'true'
14221464
run: ./mill -i integration.test.nativeMostlyStatic
@@ -1475,6 +1517,9 @@ jobs:
14751517
with:
14761518
name: mostly-static-launchers
14771519
path: artifacts/
1520+
- uses: actions/setup-node@v6
1521+
with:
1522+
node-version: 24
14781523
- name: Native integration tests
14791524
if: env.SHOULD_RUN == 'true'
14801525
run: ./mill -i integration.test.nativeMostlyStatic
@@ -1563,6 +1608,9 @@ jobs:
15631608
- name: Build docker image
15641609
if: env.SHOULD_RUN == 'true'
15651610
run: .github/scripts/generate-docker-image.sh
1611+
- uses: actions/setup-node@v6
1612+
with:
1613+
node-version: 24
15661614
- name: Native integration tests
15671615
if: env.SHOULD_RUN == 'true'
15681616
run: ./mill -i integration.test.nativeStatic
@@ -1624,6 +1672,9 @@ jobs:
16241672
- name: Build docker image
16251673
if: env.SHOULD_RUN == 'true'
16261674
run: .github/scripts/generate-docker-image.sh
1675+
- uses: actions/setup-node@v6
1676+
with:
1677+
node-version: 24
16271678
- name: Native integration tests
16281679
if: env.SHOULD_RUN == 'true'
16291680
run: ./mill -i integration.test.nativeStatic

modules/build/src/main/scala/scala/build/internal/Runner.scala

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,55 @@ object Runner {
435435
}
436436
}
437437

438+
def bunCommand(
439+
entrypoint: File,
440+
args: Seq[String]
441+
): Seq[String] = {
442+
val bunPath = findInPath("bun").fold("bun")(_.toString)
443+
Seq(bunPath, "run", entrypoint.getAbsolutePath) ++ args
444+
}
445+
446+
def runBun(
447+
entrypoint: File,
448+
args: Seq[String],
449+
logger: Logger,
450+
allowExecve: Boolean = false
451+
): Either[BuildException, Process] = either {
452+
val bunPath: String =
453+
value(findInPath("bun")
454+
.map(_.toString)
455+
.toRight(BunNotFoundError()))
456+
457+
val command = Seq(bunPath, "run", entrypoint.getAbsolutePath) ++ args
458+
459+
if (allowExecve && Execve.available()) {
460+
logger.log(
461+
s"Running ${command.mkString(" ")}",
462+
" Running" + System.lineSeparator() +
463+
command.iterator.map(_ + System.lineSeparator()).mkString
464+
)
465+
466+
logger.debug("execve available")
467+
Execve.execve(
468+
command.head,
469+
"bun" +: command.tail.toArray,
470+
sys.env.toArray.sorted.map { case (k, v) => s"$k=$v" }
471+
)
472+
sys.error("should not happen")
473+
}
474+
else {
475+
logger.log(
476+
s"Running ${command.mkString(" ")}",
477+
" Running" + System.lineSeparator() +
478+
command.iterator.map(_ + System.lineSeparator()).mkString
479+
)
480+
481+
new ProcessBuilder(command*)
482+
.inheritIO()
483+
.start()
484+
}
485+
}
486+
438487
def runNative(
439488
launcher: File,
440489
args: Seq[String],

modules/cli/src/main/scala/scala/cli/commands/run/Run.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
508508
Left(Runner.denoCommand(outputPath.toIO, args))
509509
case WasmRuntime.Node =>
510510
Left(Runner.jsCommand(outputPath.toIO, args, jsDom = false, emitWasm = true))
511+
case WasmRuntime.Bun =>
512+
Left(Runner.bunCommand(outputPath.toIO, args))
511513
}
512514
else {
513515
val process = value {
@@ -531,6 +533,13 @@ object Run extends ScalaCommand[RunOptions] with BuildCommandHelpers {
531533
esModule = esModule,
532534
emitWasm = true
533535
)
536+
case WasmRuntime.Bun =>
537+
Runner.runBun(
538+
outputPath.toIO,
539+
args,
540+
logger,
541+
allowExecve = effectiveAllowExecve
542+
)
534543
}
535544
}
536545
process.onExit().thenApply(_ => if os.exists(jsDest) then os.remove(jsDest))

modules/cli/src/main/scala/scala/cli/commands/shared/WasmOptions.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ final case class WasmOptions(
1515

1616
@Group(HelpGroup.Wasm.toString)
1717
@Tag(tags.experimental)
18-
@HelpMessage("WASM runtime to use: node (default), deno")
18+
@HelpMessage("WASM runtime to use: node (default), deno, bun")
1919
wasmRuntime: Option[String] = None
2020
)
2121
// format: on
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package scala.build.errors
2+
3+
final class BunNotFoundError extends BuildException(
4+
"Bun was not found on the PATH. Install Bun from https://bun.sh/ or use --wasm-runtime node"
5+
)

modules/directives/src/main/scala/scala/build/preprocessing/directives/Wasm.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ import scala.cli.commands.SpecificationLevel
1010
@DirectiveExamples("//> using wasm")
1111
@DirectiveExamples("//> using wasmRuntime node")
1212
@DirectiveExamples("//> using wasmRuntime deno")
13+
@DirectiveExamples("//> using wasmRuntime bun")
1314
@DirectiveUsage(
1415
"//> using wasm|wasmRuntime _value_",
1516
"""
1617
|`//> using wasm` _true|false_
1718
|
1819
|`//> using wasm`
1920
|
20-
|`//> using wasmRuntime` _node|deno_
21+
|`//> using wasmRuntime` _node|deno|bun_
2122
|""".stripMargin
2223
)
2324
@DirectiveDescription("Add WebAssembly options")

modules/integration/src/test/scala/scala/cli/integration/RunScalaJsTestDefinitions.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,30 @@ trait RunScalaJsTestDefinitions { this: RunTestDefinitions =>
452452
}
453453
}
454454

455+
if (TestUtil.fromPath("bun").isDefined)
456+
test("Run with --wasm-runtime bun") {
457+
val inputs = TestInputs(
458+
os.rel / "Hello.scala" ->
459+
"""object Hello {
460+
| def main(args: Array[String]): Unit = println("Hello from Bun WASM!")
461+
|}
462+
|""".stripMargin
463+
)
464+
inputs.fromRoot { root =>
465+
val output = os.proc(
466+
TestUtil.cli,
467+
"--power",
468+
"run",
469+
"Hello.scala",
470+
"--wasm",
471+
"--wasm-runtime",
472+
"bun",
473+
extraOptions
474+
).call(cwd = root).out.trim()
475+
expect(output == "Hello from Bun WASM!")
476+
}
477+
}
478+
455479
test("WASM multiple source files") {
456480
val inputs = TestInputs(
457481
os.rel / "Greeter.scala" ->

modules/options/src/main/scala/scala/build/options/WasmRuntime.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,24 @@ import java.util.Locale
77
* JS-based runtimes (work now with Scala.js WASM backend):
88
* - Node: Uses Node.js (V8 engine) with JavaScript loader
99
* - Deno: Uses Deno (V8 engine) with ES module support
10+
* - Bun: Uses Bun (JavaScriptCore engine) with ES module support
1011
*/
1112
sealed abstract class WasmRuntime(val name: String)
1213

1314
object WasmRuntime {
1415
case object Node extends WasmRuntime("node")
1516
case object Deno extends WasmRuntime("deno")
17+
case object Bun extends WasmRuntime("bun")
1618

17-
val all: Seq[WasmRuntime] = Seq(Node, Deno)
19+
val all: Seq[WasmRuntime] = Seq(Node, Deno, Bun)
1820

1921
def default: WasmRuntime = Node
2022

2123
def parse(s: String): Option[WasmRuntime] =
2224
s.trim.toLowerCase(Locale.ROOT) match {
2325
case "node" | "nodejs" => Some(Node)
2426
case "deno" => Some(Deno)
27+
case "bun" => Some(Bun)
2528
case _ => None
2629
}
2730

website/docs/reference/cli-options.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1995,7 +1995,7 @@ Enable WebAssembly output (Scala.js WASM backend). Uses Node.js by default. To s
19951995

19961996
### `--wasm-runtime`
19971997

1998-
WASM runtime to use: node (default), deno
1998+
WASM runtime to use: node (default), deno, bun
19991999

20002000
## Watch options
20012001

website/docs/reference/directives.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ Add WebAssembly options
716716

717717
`//> using wasm`
718718

719-
`//> using wasmRuntime` _node|deno_
719+
`//> using wasmRuntime` _node|deno|bun_
720720

721721

722722
#### Examples
@@ -726,6 +726,8 @@ Add WebAssembly options
726726

727727
`//> using wasmRuntime deno`
728728

729+
`//> using wasmRuntime bun`
730+
729731
### Watch additional inputs
730732

731733
Watch additional files or directories when using watch mode

0 commit comments

Comments
 (0)