Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions millbun/integration/resources/scalajs-bunfig/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Marker .npmrc for integration test — should NOT propagate beyond install
registry=https://registry.npmjs.org/
28 changes: 28 additions & 0 deletions millbun/integration/resources/scalajs-bunfig/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//| mill-version: 1.1.5
//| mill-jvm-version: system
//| mvnDeps:
//| - com.tjclp::mill-bun_mill1:0.1.0-SNAPSHOT

package build

import mill.*
import mill.scalalib.*
import mill.scalajslib.*
import mill.scalajslib.api.*
import mill.scalajslib.bun.*

object app extends BunScalaJSModule {
override def moduleDir = build.moduleDir
def scalaVersion = "3.8.2"

override def mainClass = Some("Main")
override def moduleKind = Task { ModuleKind.ESModule }
override def bunBundleTarget = Task { "bun" }

object test extends BunScalaJSTests, TestModule.Utest {
def mvnDeps = Seq(
mvn"com.lihaoyi::utest::0.8.5"
)
def testFramework = "utest.runner.Framework"
}
}
3 changes: 3 additions & 0 deletions millbun/integration/resources/scalajs-bunfig/bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Marker bunfig for integration test — verifies propagation to Scala.js workspaces
[install]
auto = "disable"
3 changes: 3 additions & 0 deletions millbun/integration/resources/scalajs-bunfig/src/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Main extends App {
println("Hello from scala.js bunfig")
}
3 changes: 3 additions & 0 deletions millbun/integration/resources/scalajs-bunfig/src/Words.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Words {
def greeting(name: String): String = s"Hello, $name!"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import utest.*

object WordsTests extends TestSuite {
val tests: Tests = Tests {
test("greeting") {
assert(Words.greeting("Bun") == "Hello, Bun!")
}
}
}
2 changes: 2 additions & 0 deletions millbun/integration/resources/typescript-bunfig/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Marker .npmrc for integration test — should NOT propagate beyond install
registry=https://registry.npmjs.org/
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("Hello from Bun config propagation!");
14 changes: 14 additions & 0 deletions millbun/integration/resources/typescript-bunfig/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//| mill-version: 1.1.5
//| mill-jvm-version: system
//| mvnDeps:
//| - com.tjclp::mill-bun_mill1:0.1.0-SNAPSHOT

package build

import mill.*
import mill.javascriptlib.bun.*

object app extends BunTypeScriptModule {
// Keep bunfig/.npmrc at the workspace root so the test proves explicit propagation.
override def moduleDir = build.moduleDir / "app"
}
3 changes: 3 additions & 0 deletions millbun/integration/resources/typescript-bunfig/bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Marker bunfig for integration test — verifies propagation to workspaces
[install]
auto = "disable"
21 changes: 21 additions & 0 deletions millbun/integration/resources/typescript-test-deps/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//| mill-version: 1.1.5
//| mill-jvm-version: system
//| mvnDeps:
//| - com.tjclp::mill-bun_mill1:0.1.0-SNAPSHOT

package build

import mill.*
import mill.javascriptlib.bun.*

object app extends BunTypeScriptModule {
override def moduleDir = build.moduleDir

// Production dependency
override def npmDeps = Task { Seq("is-even@1.0.0") }

object test extends BunTypeScriptTests {
// Test-only dependency — should land in devDependencies, not dependencies
override def npmDeps = Task { Seq("is-odd@3.0.1") }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import isEven from "is-even";

export function checkEven(n: number): boolean {
return isEven(n);
}

console.log(`4 is even: ${checkEven(4)}`);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { expect, test } from "bun:test";
import isOdd from "is-odd";
import { checkEven } from "../src/main";

test("checkEven", () => {
expect(checkEven(4)).toBe(true);
expect(checkEven(3)).toBe(false);
});

test("test-only dep is available", () => {
expect(isOdd(3)).toBe(true);
});
14 changes: 14 additions & 0 deletions millbun/integration/resources/typescript-tsx/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//| mill-version: 1.1.5
//| mill-jvm-version: system
//| mvnDeps:
//| - com.tjclp::mill-bun_mill1:0.1.0-SNAPSHOT

package build

import mill.*
import mill.javascriptlib.bun.*

object app extends BunTypeScriptModule {
override def moduleDir = build.moduleDir
override def bunBundleTarget = Task { "bun" }
}
3 changes: 3 additions & 0 deletions millbun/integration/resources/typescript-tsx/src/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function label(framework: string): string {
return `Hello from ${framework}`;
}
4 changes: 4 additions & 0 deletions millbun/integration/resources/typescript-tsx/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// TSX entrypoint — verifies .tsx fallback resolution works with Bun's native JSX.
import { label } from "./helper";

console.log(label("TSX"));
29 changes: 29 additions & 0 deletions millbun/integration/src/mill/bun/BunScalaJSIntegrationTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,34 @@ object BunScalaJSIntegrationTests extends TestSuite {
val res = tester.eval("app.test.bunTest")
assert(res.isSuccess)
}

test("bunfig propagates to Scala.js workspaces without leaking .npmrc") {
val tester = this.tester("scalajs-bunfig")

val installRes = tester.eval("app.bunInstall")
assert(installRes.isSuccess)
val installDir = tester.workspacePath / "out" / "app" / "bunInstall.dest"
assert(os.exists(installDir / ".npmrc"))
assert(os.exists(installDir / "bunfig.toml"))

val linkRes = tester.eval("app.fastLinkJS")
assert(linkRes.isSuccess)
val linkedDir = tester.workspacePath / "out" / "app" / "fastLinkJS.dest"
assert(os.exists(linkedDir / "bunfig.toml"))
assert(!os.exists(linkedDir / ".npmrc"))

val compileRes = tester.eval("app.bunCompileExecutable")
assert(compileRes.isSuccess)
val compileWorkspace = tester.workspacePath / "out" / "app" / "bunCompileExecutable.dest" / "workspace"
assert(os.exists(compileWorkspace / "bunfig.toml"))
assert(!os.exists(compileWorkspace / ".npmrc"))

val testRes = tester.eval("app.test.bunTest")
assert(testRes.isSuccess)
val testRoot = tester.workspacePath / "out" / "app" / "test"
assert(os.exists(testRoot))
assert(os.walk(testRoot).exists(_.last == "bunfig.toml"))
assert(!os.walk(testRoot).exists(_.last == ".npmrc"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,56 @@ object BunTypeScriptIntegrationTests extends TestSuite {
assert(betaRun.out.text().trim == "Beta worker")
}

test("tsx entrypoint fallback") {
val tester = this.tester("typescript-tsx")
val res = tester.eval("app.run")
assert(res.isSuccess)

val log = os.read(commandLogPath(tester, "app.run")).trim
assert(log.contains("Hello from TSX"))
}

test("bunfig propagates to compile workspace without leaking .npmrc") {
val tester = this.tester("typescript-bunfig")
val res = tester.eval("app.compile")
assert(res.isSuccess)

val installDir = tester.workspacePath / "out" / "app" / "npmInstall.dest"
val compileDir = tester.workspacePath / "out" / "app" / "compile.dest"

// Install workspace keeps both configs.
assert(os.exists(installDir / ".npmrc"))
assert(os.exists(installDir / "bunfig.toml"))
// Compile workspace should only get bunfig.
assert(os.exists(compileDir / "bunfig.toml"))
assert(!os.exists(compileDir / ".npmrc"))
}

test("test deps are devDependencies") {
val tester = this.tester("typescript-test-deps")

// Outer module should have is-even in dependencies
val outerRes = tester.eval("app.npmInstall")
assert(outerRes.isSuccess)
val outerPkg = ujson.read(os.read(tester.workspacePath / "out" / "app" / "npmInstall.dest" / "package.json"))
assert(outerPkg("dependencies").obj.contains("is-even"))
assert(!outerPkg("dependencies").obj.contains("is-odd"))

// Test module should have is-odd in devDependencies (not dependencies)
val testRes = tester.eval("app.test.npmInstall")
assert(testRes.isSuccess)
val testPkg = ujson.read(os.read(tester.workspacePath / "out" / "app" / "test" / "npmInstall.dest" / "package.json"))
assert(testPkg("devDependencies").obj.contains("is-odd"))
assert(!testPkg("dependencies").obj.contains("is-odd"))
assert(!testPkg("devDependencies").obj.contains("is-even"))
// Outer deps should also be present
assert(testPkg("dependencies").obj.contains("is-even"))

// Tests should actually run (both deps available)
val runRes = tester.eval("app.test.test")
assert(runRes.isSuccess)
}

test("bunEnv") {
val tester = this.tester("typescript-env")
val res = tester.eval("app.bundle")
Expand Down
19 changes: 16 additions & 3 deletions millbun/src/mill/bun/BunToolchainModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,21 @@ object BunToolchainModule {
(parts(0), ujson.Str(parts.lift(1).getOrElse("")))
}

/** Resolve an executable name from PATH. */
/** Build candidate executable names from a base name and PATHEXT extensions.
* PATHEXT is Windows-specific and always semicolon-delimited regardless of platform. */
def executableCandidates(name: String, pathExt: String): Seq[String] = {
val extensions = pathExt.split(";").filter(_.nonEmpty)
if (extensions.nonEmpty) Seq(name) ++ extensions.map(ext => name + ext.toLowerCase)
else Seq(name)
}

/** Resolve an executable name from PATH, respecting PATHEXT on Windows. */
def findOnPath(name: String): Option[os.Path] = {
val pathDirs = sys.env.getOrElse("PATH", "").split(java.io.File.pathSeparator)
val candidates = executableCandidates(name, sys.env.getOrElse("PATHEXT", ""))

pathDirs.iterator
.map(dir => os.Path(dir) / name)
.flatMap(dir => candidates.iterator.map(c => os.Path(dir) / c))
.find(os.exists(_))
}

Expand Down Expand Up @@ -88,8 +98,11 @@ trait BunToolchainModule extends Module {
*
* Bun works without a bunfig, but copying root configs makes the generated
* task workspaces closer to the source workspace.
*
* Declared as Task.Input so Mill's sandbox checker allows reading from the
* workspace root and re-evaluates when the files change.
*/
def bunfigFiles: T[Seq[PathRef]] = Task {
def bunfigFiles: T[Seq[PathRef]] = Task.Input {
Seq(BuildCtx.workspaceRoot / "bunfig.toml", BuildCtx.workspaceRoot / ".bunfig.toml")
.filter(os.exists)
.map(PathRef(_))
Expand Down
Loading
Loading