diff --git a/BUILD.bazel b/BUILD.bazel index ffd0fb0..546f681 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1 +1,9 @@ package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "repo_runtime_files", + srcs = [ + "BUILD.bazel", + "MODULE.bazel", + ], +) diff --git a/MODULE.bazel b/MODULE.bazel index bcd6ef2..a8f360c 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -37,6 +37,13 @@ bun_install_ext.install( ) use_repo(bun_install_ext, "script_test_vite_monorepo_node_modules") +bun_install_ext.install( + name = "script_test_paraglide_monorepo_node_modules", + bun_lockfile = "//tests/script_test:paraglide_monorepo/bun.lock", + package_json = "//tests/script_test:paraglide_monorepo/package.json", +) +use_repo(bun_install_ext, "script_test_paraglide_monorepo_node_modules") + bun_install_ext.install( name = "examples_vite_monorepo_node_modules", bun_lockfile = "//examples/vite_monorepo:bun.lock", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index d9cea6e..5a2235f 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -192,7 +192,7 @@ "moduleExtensions": { "//bun:extensions.bzl%bun": { "general": { - "bzlTransitiveDigest": "64B4fTkEHdAlieIOkE/Wi2M/R9lMNZhFxeI1eXEFHRs=", + "bzlTransitiveDigest": "lzOUyaXDbkH922ruNkkwEF2cnI4m0XpzrOti0qypwtA=", "usagesDigest": "/0BcCMA6AOzLhQaRK6DquxrCfpPHJUjSUaFz4zmQrsM=", "recordedInputs": [ "REPO_MAPPING:,bazel_tools bazel_tools" @@ -218,6 +218,26 @@ "build_file_content": "\nexports_files([\"bun-linux-aarch64/bun\"])\n\nfilegroup(\n name = \"bun\",\n srcs = [\"bun-linux-aarch64/bun\"],\n visibility = [\"//visibility:public\"],\n)\n" } }, + "bun_linux_x64_musl": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-x64-musl.zip" + ], + "sha256": "48a6c32277d343db0148ce066336472ffd380358a4d26bb1329714742492d824", + "build_file_content": "\nexports_files([\"bun-linux-x64-musl/bun\"])\n\nfilegroup(\n name = \"bun\",\n srcs = [\"bun-linux-x64-musl/bun\"],\n visibility = [\"//visibility:public\"],\n)\n" + } + }, + "bun_linux_aarch64_musl": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-linux-aarch64-musl.zip" + ], + "sha256": "d2c81365a2e529b78a42330d3a0056e8dbd7896b4a6782c8e392b6532141e34d", + "build_file_content": "\nexports_files([\"bun-linux-aarch64-musl/bun\"])\n\nfilegroup(\n name = \"bun\",\n srcs = [\"bun-linux-aarch64-musl/bun\"],\n visibility = [\"//visibility:public\"],\n)\n" + } + }, "bun_darwin_x64": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { @@ -247,14 +267,24 @@ "sha256": "7a77b3e245e2e26965c93089a4a1332e8a326d3364c89fae1d1fd99cdd3cd73d", "build_file_content": "\nexports_files([\"bun-windows-x64/bun.exe\"])\n\nfilegroup(\n name = \"bun\",\n srcs = [\"bun-windows-x64/bun.exe\"],\n visibility = [\"//visibility:public\"],\n)\n" } + }, + "bun_windows_aarch64": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/oven-sh/bun/releases/download/bun-v1.3.10/bun-windows-aarch64.zip" + ], + "sha256": "6822f3aa7bd2be40fb94c194a1185aae1c6fade54ca4fc2efdc722e37f3257d2", + "build_file_content": "\nexports_files([\"bun-windows-aarch64/bun.exe\"])\n\nfilegroup(\n name = \"bun\",\n srcs = [\"bun-windows-aarch64/bun.exe\"],\n visibility = [\"//visibility:public\"],\n)\n" + } } } } }, "//bun:extensions.bzl%bun_install": { "general": { - "bzlTransitiveDigest": "64B4fTkEHdAlieIOkE/Wi2M/R9lMNZhFxeI1eXEFHRs=", - "usagesDigest": "d+DGTyl4FpB6Ygb/R/V5knxm9bGYZKO223wMX1Q6R6w=", + "bzlTransitiveDigest": "lzOUyaXDbkH922ruNkkwEF2cnI4m0XpzrOti0qypwtA=", + "usagesDigest": "f9pNm3AOxJDZmpHhL2vrrCo23IW33im/l/VYCTW2BWM=", "recordedInputs": [ "REPO_MAPPING:,bazel_tools bazel_tools" ], @@ -265,7 +295,14 @@ "package_json": "@@//tests/script_test:vite_app/package.json", "bun_lockfile": "@@//tests/script_test:vite_app/bun.lock", "install_inputs": [], - "isolated_home": true + "isolated_home": true, + "production": false, + "omit": [], + "linker": "", + "backend": "", + "ignore_scripts": false, + "install_flags": [], + "visible_repo_name": "script_test_vite_node_modules" } }, "script_test_vite_monorepo_node_modules": { @@ -274,7 +311,30 @@ "package_json": "@@//tests/script_test:vite_monorepo/package.json", "bun_lockfile": "@@//tests/script_test:vite_monorepo/bun.lock", "install_inputs": [], - "isolated_home": true + "isolated_home": true, + "production": false, + "omit": [], + "linker": "", + "backend": "", + "ignore_scripts": false, + "install_flags": [], + "visible_repo_name": "script_test_vite_monorepo_node_modules" + } + }, + "script_test_paraglide_monorepo_node_modules": { + "repoRuleId": "@@//internal:bun_install.bzl%bun_install_repository", + "attributes": { + "package_json": "@@//tests/script_test:paraglide_monorepo/package.json", + "bun_lockfile": "@@//tests/script_test:paraglide_monorepo/bun.lock", + "install_inputs": [], + "isolated_home": true, + "production": false, + "omit": [], + "linker": "", + "backend": "", + "ignore_scripts": false, + "install_flags": [], + "visible_repo_name": "script_test_paraglide_monorepo_node_modules" } }, "examples_vite_monorepo_node_modules": { @@ -283,7 +343,14 @@ "package_json": "@@//examples/vite_monorepo:package.json", "bun_lockfile": "@@//examples/vite_monorepo:bun.lock", "install_inputs": [], - "isolated_home": true + "isolated_home": true, + "production": false, + "omit": [], + "linker": "", + "backend": "", + "ignore_scripts": false, + "install_flags": [], + "visible_repo_name": "examples_vite_monorepo_node_modules" } } } diff --git a/README.md b/README.md index 49ef567..c0bb043 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bun rules for [Bazel](https://bazel.build) -`rules_bun` provides Bazel rules for running, testing, bundling, and developing -JavaScript and TypeScript code with Bun. +`rules_bun` provides Bazel rules for running, testing, building, compiling, +bundling, and developing JavaScript and TypeScript code with Bun. ## Repository layout @@ -29,10 +29,15 @@ The public entrypoint for rule authors and users is `@rules_bun//bun:defs.bzl`. `rules_bun` exports these primary rules: - `bun_binary` +- `bun_build` +- `bun_compile` - `bun_bundle` - `bun_dev` - `bun_script` - `bun_test` +- `js_binary` +- `js_test` +- `js_run_devserver` - `js_library` - `ts_library` @@ -67,9 +72,12 @@ use_repo( bun_ext, "bun_linux_x64", "bun_linux_aarch64", + "bun_linux_x64_musl", + "bun_linux_aarch64_musl", "bun_darwin_x64", "bun_darwin_aarch64", "bun_windows_x64", + "bun_windows_aarch64", ) register_toolchains( @@ -105,6 +113,36 @@ bun_install_ext.install( use_repo(bun_install_ext, "bun_deps") ``` +## `rules_js` compatibility layer + +`rules_bun` now exposes a Bun-backed compatibility layer for the most common +`rules_js` entrypoints: + +- `@rules_bun//js:defs.bzl` exports `js_binary`, `js_test`, `js_run_devserver`, + `js_library`, `ts_library`, and `JsInfo`. +- `@rules_bun//npm:extensions.bzl` exports `npm_translate_lock`, which creates a + Bun-installed external repo and generates `@//:defs.bzl` with + `npm_link_all_packages()`. + +Example: + +```starlark +load("@rules_bun//js:defs.bzl", "js_binary") +load("@npm//:defs.bzl", "npm_link_all_packages") + +npm_link_all_packages() + +js_binary( + name = "app", + entry_point = "src/main.ts", + node_modules = ":node_modules", +) +``` + +This is a compatibility subset, not a full reimplementation of `rules_js`. +Package aliases created by `npm_link_all_packages()` use sanitized target names +such as `npm__vite` or `npm__at_types_node`. + ## Legacy WORKSPACE usage For non-Bzlmod consumers, the repository exposes a legacy setup macro in @@ -122,6 +160,8 @@ bun_register_toolchains() load( "@rules_bun//bun:defs.bzl", "bun_binary", + "bun_build", + "bun_compile", "bun_bundle", "bun_dev", "bun_script", @@ -161,6 +201,32 @@ When `node_modules` is provided, executables from `node_modules/.bin` are added to `PATH`. This label typically comes from `bun_install`, which still produces a standard `node_modules/` directory. +### `bun_build` and `bun_compile` + +Use `bun_build` for general-purpose `bun build` output directories and +`bun_compile` for standalone executables built with `bun build --compile`. + +```starlark +load("@rules_bun//bun:defs.bzl", "bun_build", "bun_compile") + +bun_build( + name = "site", + entry_points = ["src/index.html"], + data = glob(["src/**"]), + splitting = True, + metafile = True, +) + +bun_compile( + name = "cli", + entry_point = "src/cli.ts", +) +``` + +`bun_build` exposes a directory output so Bun can emit HTML, CSS, assets, and +split chunks. `bun_compile` produces a single executable artifact and supports +explicit cross-compilation via `compile_executable`. + ### `bun_dev` for local development Use `bun_dev` for long-running watch or hot-reload development targets. diff --git a/bun/BUILD.bazel b/bun/BUILD.bazel index 62bf0a1..dbdc256 100644 --- a/bun/BUILD.bazel +++ b/bun/BUILD.bazel @@ -10,6 +10,19 @@ exports_files([ "version.bzl", ]) +filegroup( + name = "repo_runtime_files", + srcs = [ + "BUILD.bazel", + "defs.bzl", + "extensions.bzl", + "repositories.bzl", + "toolchain.bzl", + "version.bzl", + ], + visibility = ["//visibility:public"], +) + bzl_library( name = "toolchain_bzl", srcs = ["toolchain.bzl"], @@ -35,11 +48,14 @@ bzl_library( visibility = ["//visibility:public"], deps = [ ":toolchain_bzl", + "//internal:bun_build_support_bzl", "//internal:bun_binary_bzl", + "//internal:bun_compile_bzl", "//internal:bun_bundle_bzl", "//internal:bun_dev_bzl", "//internal:bun_script_bzl", "//internal:bun_test_bzl", + "//internal:js_compat_bzl", "//internal:js_library_bzl", ], ) diff --git a/bun/defs.bzl b/bun/defs.bzl index 104f73a..1fc3db0 100644 --- a/bun/defs.bzl +++ b/bun/defs.bzl @@ -1,21 +1,29 @@ """Public API surface for Bun Bazel rules.""" +load("//internal:bun_compile.bzl", _bun_build = "bun_build", _bun_compile = "bun_compile") load("//internal:bun_binary.bzl", _bun_binary = "bun_binary") load("//internal:bun_bundle.bzl", _bun_bundle = "bun_bundle") load("//internal:bun_dev.bzl", _bun_dev = "bun_dev") load("//internal:bun_script.bzl", _bun_script = "bun_script") load("//internal:bun_test.bzl", _bun_test = "bun_test") +load("//internal:js_compat.bzl", _JsInfo = "JsInfo", _js_binary = "js_binary", _js_run_devserver = "js_run_devserver", _js_test = "js_test") load("//internal:js_library.bzl", _js_library = "js_library", _ts_library = "ts_library") load(":toolchain.bzl", _BunToolchainInfo = "BunToolchainInfo", _bun_toolchain = "bun_toolchain") visibility("public") bun_binary = _bun_binary +bun_build = _bun_build +bun_compile = _bun_compile bun_bundle = _bun_bundle bun_dev = _bun_dev bun_script = _bun_script bun_test = _bun_test +js_binary = _js_binary +js_test = _js_test +js_run_devserver = _js_run_devserver js_library = _js_library ts_library = _ts_library +JsInfo = _JsInfo BunToolchainInfo = _BunToolchainInfo bun_toolchain = _bun_toolchain - \ No newline at end of file + diff --git a/bun/extensions.bzl b/bun/extensions.bzl index ea012cc..2009234 100644 --- a/bun/extensions.bzl +++ b/bun/extensions.bzl @@ -13,6 +13,16 @@ _BUN_ARCHIVES = { "asset": "bun-linux-aarch64.zip", "binary": "bun-linux-aarch64/bun", }, + "bun_linux_x64_musl": { + "sha256": "48a6c32277d343db0148ce066336472ffd380358a4d26bb1329714742492d824", + "asset": "bun-linux-x64-musl.zip", + "binary": "bun-linux-x64-musl/bun", + }, + "bun_linux_aarch64_musl": { + "sha256": "d2c81365a2e529b78a42330d3a0056e8dbd7896b4a6782c8e392b6532141e34d", + "asset": "bun-linux-aarch64-musl.zip", + "binary": "bun-linux-aarch64-musl/bun", + }, "bun_darwin_x64": { "sha256": "c1d90bf6140f20e572c473065dc6b37a4b036349b5e9e4133779cc642ad94323", "asset": "bun-darwin-x64.zip", @@ -28,6 +38,11 @@ _BUN_ARCHIVES = { "asset": "bun-windows-x64.zip", "binary": "bun-windows-x64/bun.exe", }, + "bun_windows_aarch64": { + "sha256": "6822f3aa7bd2be40fb94c194a1185aae1c6fade54ca4fc2efdc722e37f3257d2", + "asset": "bun-windows-aarch64.zip", + "binary": "bun-windows-aarch64/bun.exe", + }, } _BUN_GITHUB_RELEASE_URL_TEMPLATE = "https://github.com/oven-sh/bun/releases/download/bun-v{}/{}" @@ -62,6 +77,12 @@ _install = tag_class( "bun_lockfile": attr.label(mandatory = True), "install_inputs": attr.label_list(allow_files = True), "isolated_home": attr.bool(default = True), + "production": attr.bool(default = False), + "omit": attr.string_list(), + "linker": attr.string(), + "backend": attr.string(), + "ignore_scripts": attr.bool(default = False), + "install_flags": attr.string_list(), }, ) @@ -75,6 +96,13 @@ def _bun_install_impl(ctx): bun_lockfile = install.bun_lockfile, install_inputs = install.install_inputs, isolated_home = install.isolated_home, + production = install.production, + omit = install.omit, + linker = install.linker, + backend = install.backend, + ignore_scripts = install.ignore_scripts, + install_flags = install.install_flags, + visible_repo_name = install.name, ) diff --git a/bun/repositories.bzl b/bun/repositories.bzl index b475d4a..d20a7bc 100644 --- a/bun/repositories.bzl +++ b/bun/repositories.bzl @@ -12,6 +12,16 @@ _BUN_ARCHIVES = { "asset": "bun-linux-aarch64.zip", "binary": "bun-linux-aarch64/bun", }, + "bun_linux_x64_musl": { + "sha256": "48a6c32277d343db0148ce066336472ffd380358a4d26bb1329714742492d824", + "asset": "bun-linux-x64-musl.zip", + "binary": "bun-linux-x64-musl/bun", + }, + "bun_linux_aarch64_musl": { + "sha256": "d2c81365a2e529b78a42330d3a0056e8dbd7896b4a6782c8e392b6532141e34d", + "asset": "bun-linux-aarch64-musl.zip", + "binary": "bun-linux-aarch64-musl/bun", + }, "bun_darwin_x64": { "sha256": "c1d90bf6140f20e572c473065dc6b37a4b036349b5e9e4133779cc642ad94323", "asset": "bun-darwin-x64.zip", @@ -27,6 +37,11 @@ _BUN_ARCHIVES = { "asset": "bun-windows-x64.zip", "binary": "bun-windows-x64/bun.exe", }, + "bun_windows_aarch64": { + "sha256": "6822f3aa7bd2be40fb94c194a1185aae1c6fade54ca4fc2efdc722e37f3257d2", + "asset": "bun-windows-aarch64.zip", + "binary": "bun-windows-aarch64/bun.exe", + }, } _BUN_GITHUB_RELEASE_URL_TEMPLATE = "https://github.com/oven-sh/bun/releases/download/bun-v{}/{}" diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 5614f36..7405060 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -8,13 +8,17 @@ stardoc( input = "//bun:defs.bzl", symbol_names = [ "bun_binary", + "bun_build", + "bun_compile", "bun_bundle", "bun_dev", "bun_script", "bun_test", + "js_binary", + "js_run_devserver", + "js_test", "js_library", "ts_library", ], deps = ["//bun:defs_bzl"], - target_compatible_with = ["@platforms//os:linux"], ) diff --git a/docs/bun_install.md b/docs/bun_install.md index d4e6961..209d264 100644 --- a/docs/bun_install.md +++ b/docs/bun_install.md @@ -14,6 +14,7 @@ Unlike the build rules in [rules.md](rules.md), `bun_install` is not loaded from - runs `bun install --frozen-lockfile` - uses your checked-in `package.json` and `bun.lock` or `bun.lockb` - creates an external Bazel repository exposing `:node_modules` +- generates `:defs.bzl` with `npm_link_all_packages()` and `package_target_name()` - keeps dependency installation under Bun rather than npm The generated repository can then be passed to rules such as `bun_script`, @@ -28,6 +29,8 @@ bun_install_ext.install( name = "bun_deps", package_json = "//:package.json", bun_lockfile = "//:bun.lock", + production = True, + omit = ["peer"], ) use_repo(bun_install_ext, "bun_deps") @@ -112,6 +115,45 @@ repository root. - `False`: lets Bun use the host `HOME`, which can improve repeated-install performance when Bun's cache is home-scoped +### `production` + +Optional boolean controlling whether Bun installs only production dependencies. + +Example: + +```starlark +production = True +``` + +### `omit` + +Optional list of dependency groups to omit, forwarded as repeated +`--omit` flags. Common values are `dev`, `optional`, and `peer`. + +### `linker` + +Optional Bun linker strategy, forwarded as `--linker`. + +Common values: + +- `isolated` +- `hoisted` + +### `backend` + +Optional Bun install backend, forwarded as `--backend`. + +Examples include `hardlink`, `symlink`, and `copyfile`. + +### `ignore_scripts` + +Optional boolean controlling whether Bun skips lifecycle scripts in the project +manifest. + +### `install_flags` + +Optional list of additional raw flags forwarded to `bun install`. + ## Notes - `bun_install` runs Bun, not npm. diff --git a/docs/index.md b/docs/index.md index d5a0a09..db90505 100644 --- a/docs/index.md +++ b/docs/index.md @@ -36,9 +36,12 @@ use_repo( bun_ext, "bun_linux_x64", "bun_linux_aarch64", + "bun_linux_x64_musl", + "bun_linux_aarch64_musl", "bun_darwin_x64", "bun_darwin_aarch64", "bun_windows_x64", + "bun_windows_aarch64", ) register_toolchains( @@ -79,6 +82,12 @@ bun_script( `bun_script` runs from the package directory by default and adds `node_modules/.bin` to `PATH`. +## Build and compile + +Use `bun_build` when Bun may emit a directory of outputs such as HTML, CSS, +chunks, and static assets. Use `bun_compile` for standalone executables created +with `bun build --compile`. + ## Regeneration The rule reference is generated from the public Starlark symbols in diff --git a/docs/rules.md b/docs/rules.md index 3457715..61c73b7 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -1,88 +1,484 @@ -# rules_bun rule reference + -This file documents the public rules exported from `@rules_bun//bun:defs.bzl`. +Public API surface for Bun Bazel rules. + + ## bun_binary -Runs a JS/TS entry point with Bun as an executable target (`bazel run`). +
+load("@rules_bun//bun:defs.bzl", "bun_binary")
 
-Attributes:
+bun_binary(name, deps, data, conditions, entry_point, env_files, install_mode, no_env_file,
+           node_modules, preload, run_flags, smol, working_dir)
+
-- `entry_point` (label, required): path to the main JS/TS file to execute. -- `node_modules` (label, optional): package files from a `node_modules` tree, typically produced by `bun_install`, made available in runfiles. -- `data` (label_list, optional): additional runtime files. -- `working_dir` (string, default: `"workspace"`, values: `"workspace" | "entry_point"`): runtime working directory. +Runs a JS/TS entry point with Bun as an executable target. -## bun_dev +Use this rule for non-test scripts and CLIs that should run via `bazel run`. -Runs a JS/TS entry point in Bun development watch mode (`bazel run`). +**ATTRIBUTES** -Attributes: -- `entry_point` (label, required): path to the main JS/TS file. -- `watch_mode` (string, default: `"watch"`, values: `"watch" | "hot"`): Bun live-reload mode. -- `restart_on` (label_list, optional): files that trigger full process restart when changed. -- `node_modules` (label, optional): package files from a `node_modules` tree, typically produced by `bun_install`, made available in runfiles. -- `data` (label_list, optional): additional runtime files for dev process. -- `working_dir` (string, default: `"workspace"`, values: `"workspace" | "entry_point"`): runtime working directory. +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this target. | Name | required | | +| deps | Library dependencies required by the program. | List of labels | optional | `[]` | +| data | Additional runtime files required by the program. | List of labels | optional | `[]` | +| conditions | Custom package resolve conditions passed to Bun. | List of strings | optional | `[]` | +| entry_point | Path to the main JS/TS file to execute. | Label | required | | +| env_files | Additional environment files loaded with `--env-file`. | List of labels | optional | `[]` | +| install_mode | Whether Bun may auto-install missing packages at runtime. | String | optional | `"disable"` | +| no_env_file | If true, disables Bun's automatic `.env` loading. | Boolean | optional | `False` | +| node_modules | Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles. | Label | optional | `None` | +| preload | Modules to preload with `--preload` before running the entry point. | List of labels | optional | `[]` | +| run_flags | Additional raw flags forwarded to `bun run` before the entry point. | List of strings | optional | `[]` | +| smol | If true, enables Bun's lower-memory runtime mode. | Boolean | optional | `False` | +| working_dir | Working directory at runtime: `workspace` root or nearest `entry_point` ancestor containing `.env`/`package.json`. | String | optional | `"workspace"` | -## bun_script -Runs a named `package.json` script with Bun as an executable target (`bazel run`). + -Recommended for package-script based tools such as Vite (`dev`, `build`, `preview`). -When `node_modules` is provided, executables from `node_modules/.bin` are added -to `PATH`, so scripts like `vite` work without wrapper scripts. +## bun_build -Attributes: +
+load("@rules_bun//bun:defs.bzl", "bun_build")
 
-- `script` (string, required): package script name passed to `bun run 
+  
+
diff --git a/tests/bundle_test/site/main.ts b/tests/bundle_test/site/main.ts
new file mode 100644
index 0000000..3ef72b5
--- /dev/null
+++ b/tests/bundle_test/site/main.ts
@@ -0,0 +1 @@
+document.getElementById("app")?.setAttribute("data-built", "yes");
diff --git a/tests/bundle_test/site/styles.css b/tests/bundle_test/site/styles.css
new file mode 100644
index 0000000..b780b02
--- /dev/null
+++ b/tests/bundle_test/site/styles.css
@@ -0,0 +1,4 @@
+body {
+  background: #f5efe2;
+  color: #1f1b14;
+}
diff --git a/tests/bundle_test/sourcemap_bundle__main.js b/tests/bundle_test/sourcemap_bundle__main.js
new file mode 100644
index 0000000..344b5f2
--- /dev/null
+++ b/tests/bundle_test/sourcemap_bundle__main.js
@@ -0,0 +1,11 @@
+// ../../../../../../../Projects/rules_bun/tests/bundle_test/main.ts
+function greet(name) {
+  return `Hello ${name}`;
+}
+console.log(greet("bundle"));
+export {
+  greet
+};
+
+//# debugId=D8717FECBBDCEC7764756E2164756E21
+//# sourceMappingURL=sourcemap_bundle__main.js.map
diff --git a/tests/bundle_test/sourcemap_bundle__main.js.map b/tests/bundle_test/sourcemap_bundle__main.js.map
new file mode 100644
index 0000000..58d0301
--- /dev/null
+++ b/tests/bundle_test/sourcemap_bundle__main.js.map
@@ -0,0 +1,10 @@
+{
+  "version": 3,
+  "sources": ["../../../../../../../Projects/rules_bun/tests/bundle_test/main.ts"],
+  "sourcesContent": [
+    "export function greet(name: string): string {\n  return `Hello ${name}`;\n}\n\nconsole.log(greet(\"bundle\"));\n"
+  ],
+  "mappings": ";AAAO,SAAS,KAAK,CAAC,MAAsB;AAAA,EAC1C,OAAO,SAAS;AAAA;AAGlB,QAAQ,IAAI,MAAM,QAAQ,CAAC;",
+  "debugId": "D8717FECBBDCEC7764756E2164756E21",
+  "names": []
+}
\ No newline at end of file
diff --git a/tests/bundle_test/sourcemap_case/BUILD.bazel b/tests/bundle_test/sourcemap_case/BUILD.bazel
new file mode 100644
index 0000000..7600ec2
--- /dev/null
+++ b/tests/bundle_test/sourcemap_case/BUILD.bazel
@@ -0,0 +1,13 @@
+load("//bun:defs.bzl", "bun_bundle")
+
+exports_files([
+    "BUILD.bazel",
+    "entry.ts",
+])
+
+bun_bundle(
+    name = "sourcemap_bundle",
+    tags = ["manual"],
+    entry_points = ["entry.ts"],
+    sourcemap = True,
+)
diff --git a/tests/bundle_test/sourcemap_case/entry.ts b/tests/bundle_test/sourcemap_case/entry.ts
new file mode 100644
index 0000000..c4ac5c3
--- /dev/null
+++ b/tests/bundle_test/sourcemap_case/entry.ts
@@ -0,0 +1,3 @@
+const message: string = "sourcemap coverage";
+
+console.log(message);
diff --git a/tests/bundle_test/verify_bundle.sh b/tests/bundle_test/verify_bundle.sh
index b0122f9..58195ae 100755
--- a/tests/bundle_test/verify_bundle.sh
+++ b/tests/bundle_test/verify_bundle.sh
@@ -3,12 +3,12 @@ set -euo pipefail
 
 bundle="$1"
 
-if [[ ! -f "${bundle}" ]]; then
+if [[ ! -f ${bundle} ]]; then
   echo "Bundle output not found: ${bundle}" >&2
   exit 1
 fi
 
-if [[ ! -s "${bundle}" ]]; then
+if [[ ! -s ${bundle} ]]; then
   echo "Bundle output is empty: ${bundle}" >&2
   exit 1
 fi
diff --git a/tests/bundle_test/verify_external_shape.sh b/tests/bundle_test/verify_external_shape.sh
index cff7e1c..e0bef6d 100755
--- a/tests/bundle_test/verify_external_shape.sh
+++ b/tests/bundle_test/verify_external_shape.sh
@@ -4,7 +4,7 @@ set -euo pipefail
 rule_file="$1"
 build_file="$2"
 
-grep -Eq 'for package in ctx\.attr\.external:' "${rule_file}"
-grep -Eq 'args\.add\("--external"\)' "${rule_file}"
+grep -Eq 'add_bun_build_common_flags\(args, ctx\.attr\)' "${rule_file}"
+grep -Eq '"external": attr\.string_list\(' "${rule_file}"
 grep -Eq 'name = "external_bundle"' "${build_file}"
 grep -Eq 'external = \["left-pad"\]' "${build_file}"
diff --git a/tests/bundle_test/verify_flag_aquery.sh b/tests/bundle_test/verify_flag_aquery.sh
new file mode 100755
index 0000000..f9fc033
--- /dev/null
+++ b/tests/bundle_test/verify_flag_aquery.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if command -v bazel >/dev/null 2>&1; then
+  bazel_cmd=(bazel)
+elif command -v bazelisk >/dev/null 2>&1; then
+  bazel_cmd=(bazelisk)
+else
+  echo "bazel or bazelisk is required on PATH" >&2
+  exit 1
+fi
+
+find_workspace_root() {
+  local candidate
+  local module_path
+  local script_dir
+
+  for candidate in \
+    "${TEST_SRCDIR:-}/${TEST_WORKSPACE:-}" \
+    "${TEST_SRCDIR:-}/_main"; do
+    if [[ -n ${candidate} && -f "${candidate}/MODULE.bazel" ]]; then
+      printf '%s\n' "${candidate}"
+      return 0
+    fi
+  done
+
+  if [[ -n ${TEST_SRCDIR:-} ]]; then
+    module_path="$(find "${TEST_SRCDIR}" -maxdepth 3 -name MODULE.bazel -print -quit 2>/dev/null || true)"
+    if [[ -n ${module_path} ]]; then
+      dirname "${module_path}"
+      return 0
+    fi
+  fi
+
+  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
+  candidate="$(cd "${script_dir}/../.." && pwd -P)"
+  if [[ -f "${candidate}/MODULE.bazel" ]]; then
+    printf '%s\n' "${candidate}"
+    return 0
+  fi
+
+  echo "Unable to locate rules_bun workspace root" >&2
+  exit 1
+}
+
+rules_bun_root="$(find_workspace_root)"
+
+run_aquery() {
+  local mnemonic="$1"
+  local target="$2"
+
+  (
+    cd "${rules_bun_root}" &&
+      "${bazel_cmd[@]}" aquery "mnemonic(\"${mnemonic}\", ${target})" --output=textproto
+  )
+}
+
+expect_line() {
+  local output="$1"
+  local expected="$2"
+
+  if ! grep -Fq -- "${expected}" <<<"${output}"; then
+    echo "Expected aquery output to contain: ${expected}" >&2
+    exit 1
+  fi
+}
+
+build_output="$(run_aquery "BunBuild" "//tests/bundle_test:advanced_site_build")"
+
+for expected in \
+  'arguments: "--install"' \
+  'arguments: "fallback"' \
+  'arguments: "--target"' \
+  'arguments: "node"' \
+  'arguments: "--format"' \
+  'arguments: "cjs"' \
+  'arguments: "--production"' \
+  'arguments: "--splitting"' \
+  'arguments: "--root"' \
+  'arguments: "tests/bundle_test/site"' \
+  'arguments: "--sourcemap"' \
+  'arguments: "linked"' \
+  'arguments: "--banner"' \
+  'arguments: "/* bundle banner */"' \
+  'arguments: "--footer"' \
+  'arguments: "// bundle footer"' \
+  'arguments: "--public-path"' \
+  'arguments: "/static/"' \
+  'arguments: "--packages"' \
+  'arguments: "external"' \
+  'arguments: "left-pad"' \
+  'arguments: "react"' \
+  'arguments: "--entry-naming"' \
+  'arguments: "entries/[name]-[hash].[ext]"' \
+  'arguments: "--chunk-naming"' \
+  'arguments: "chunks/[name]-[hash].[ext]"' \
+  'arguments: "--asset-naming"' \
+  'arguments: "assets/[name]-[hash].[ext]"' \
+  'arguments: "--minify"' \
+  'arguments: "--minify-syntax"' \
+  'arguments: "--minify-whitespace"' \
+  'arguments: "--minify-identifiers"' \
+  'arguments: "--keep-names"' \
+  'arguments: "--css-chunking"' \
+  'arguments: "--conditions"' \
+  'arguments: "browser"' \
+  'arguments: "custom"' \
+  'arguments: "--env"' \
+  'arguments: "PUBLIC_*"' \
+  'arguments: "process.env.NODE_ENV:\"production\""' \
+  'arguments: "__DEV__:false"' \
+  'arguments: "console"' \
+  'arguments: "debugger"' \
+  'arguments: "react_fast_refresh"' \
+  'arguments: "server_components"' \
+  'arguments: ".svg:file"' \
+  'arguments: ".txt:text"' \
+  'arguments: "--jsx-factory"' \
+  'arguments: "h"' \
+  'arguments: "--jsx-fragment"' \
+  'arguments: "Fragment"' \
+  'arguments: "--jsx-import-source"' \
+  'arguments: "preact"' \
+  'arguments: "--jsx-runtime"' \
+  'arguments: "automatic"' \
+  'arguments: "--jsx-side-effects"' \
+  'arguments: "--react-fast-refresh"' \
+  'arguments: "--emit-dce-annotations"' \
+  'arguments: "--no-bundle"' \
+  'arguments: "--app"' \
+  'arguments: "--server-components"'; do
+  expect_line "${build_output}" "${expected}"
+done
+
+compile_output="$(run_aquery "BunCompile" "//tests/bundle_test:compiled_cli_with_flags")"
+
+for expected in \
+  'arguments: "--bytecode"' \
+  'arguments: "--compile-exec-argv"' \
+  'arguments: "--smol"' \
+  'arguments: "--inspect-wait"' \
+  'arguments: "--no-compile-autoload-dotenv"' \
+  'arguments: "--no-compile-autoload-bunfig"' \
+  'arguments: "--compile-autoload-tsconfig"' \
+  'arguments: "--compile-autoload-package-json"' \
+  'arguments: "--compile-executable-path"' \
+  'arguments: "tests/bundle_test/fake_cross_bun.bin"' \
+  'arguments: "--windows-hide-console"' \
+  'arguments: "--windows-icon"' \
+  'arguments: "branding/icon.ico"' \
+  'arguments: "--windows-title"' \
+  'arguments: "Rules Bun Test App"' \
+  'arguments: "--windows-publisher"' \
+  'arguments: "rules_bun"' \
+  'arguments: "--windows-version"' \
+  'arguments: "1.2.3.4"' \
+  'arguments: "--windows-description"' \
+  'arguments: "compile flag coverage"' \
+  'arguments: "--windows-copyright"' \
+  'arguments: "(c) rules_bun"'; do
+  expect_line "${compile_output}" "${expected}"
+done
diff --git a/tests/bundle_test/verify_minify.sh b/tests/bundle_test/verify_minify.sh
index eab37d7..6ccad26 100755
--- a/tests/bundle_test/verify_minify.sh
+++ b/tests/bundle_test/verify_minify.sh
@@ -4,10 +4,10 @@ set -euo pipefail
 bundle="$1"
 minified="$2"
 
-bundle_size="$(wc -c < "${bundle}")"
-minified_size="$(wc -c < "${minified}")"
+bundle_size="$(wc -c <"${bundle}")"
+minified_size="$(wc -c <"${minified}")"
 
-if (( minified_size >= bundle_size )); then
+if ((minified_size >= bundle_size)); then
   echo "Expected minified bundle (${minified_size}) to be smaller than regular bundle (${bundle_size})" >&2
   exit 1
 fi
diff --git a/tests/bundle_test/verify_site_build.sh b/tests/bundle_test/verify_site_build.sh
new file mode 100755
index 0000000..e3787de
--- /dev/null
+++ b/tests/bundle_test/verify_site_build.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+output_dir="$1"
+
+if [[ ! -d ${output_dir} ]]; then
+  echo "Expected output directory: ${output_dir}" >&2
+  exit 1
+fi
+
+if ! find -L "${output_dir}" -type f \( -name '*.js' -o -name '*.css' \) | grep -q .; then
+  echo "Expected Bun build assets in ${output_dir}" >&2
+  exit 1
+fi
diff --git a/tests/bundle_test/verify_site_build_meta.sh b/tests/bundle_test/verify_site_build_meta.sh
new file mode 100755
index 0000000..e0779f6
--- /dev/null
+++ b/tests/bundle_test/verify_site_build_meta.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+output_dir=""
+meta_json=""
+meta_md=""
+
+for path in "$@"; do
+  case "${path}" in
+  *.meta.json) meta_json="${path}" ;;
+  *.meta.md) meta_md="${path}" ;;
+  *) output_dir="${path}" ;;
+  esac
+done
+
+if [[ ! -d ${output_dir} ]]; then
+  echo "Expected directory output, got: ${output_dir}" >&2
+  exit 1
+fi
+
+if [[ ! -f ${meta_json} ]]; then
+  echo "Expected JSON metafile output" >&2
+  exit 1
+fi
+
+if [[ ! -f ${meta_md} ]]; then
+  echo "Expected markdown metafile output" >&2
+  exit 1
+fi
diff --git a/tests/bundle_test/verify_sourcemap_shape.sh b/tests/bundle_test/verify_sourcemap_shape.sh
new file mode 100755
index 0000000..0d9f8d8
--- /dev/null
+++ b/tests/bundle_test/verify_sourcemap_shape.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if command -v bazel >/dev/null 2>&1; then
+  bazel_cmd=(bazel)
+elif command -v bazelisk >/dev/null 2>&1; then
+  bazel_cmd=(bazelisk)
+else
+  echo "bazel or bazelisk is required on PATH" >&2
+  exit 1
+fi
+
+find_workspace_root() {
+  local candidate
+  local module_path
+  local script_dir
+
+  for candidate in \
+    "${TEST_SRCDIR:-}/${TEST_WORKSPACE:-}" \
+    "${TEST_SRCDIR:-}/_main"; do
+    if [[ -n ${candidate} && -f "${candidate}/MODULE.bazel" ]]; then
+      printf '%s\n' "${candidate}"
+      return 0
+    fi
+  done
+
+  if [[ -n ${TEST_SRCDIR:-} ]]; then
+    module_path="$(find "${TEST_SRCDIR}" -maxdepth 3 -name MODULE.bazel -print -quit 2>/dev/null || true)"
+    if [[ -n ${module_path} ]]; then
+      dirname "${module_path}"
+      return 0
+    fi
+  fi
+
+  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
+  candidate="$(cd "${script_dir}/../.." && pwd -P)"
+  if [[ -f "${candidate}/MODULE.bazel" ]]; then
+    printf '%s\n' "${candidate}"
+    return 0
+  fi
+
+  echo "Unable to locate rules_bun workspace root" >&2
+  exit 1
+}
+
+rules_bun_root="$(find_workspace_root)"
+
+bundle_output="$(
+  cd "${rules_bun_root}" &&
+    "${bazel_cmd[@]}" aquery 'mnemonic("BunBundle", //tests/bundle_test/sourcemap_case:sourcemap_bundle)' --output=textproto
+)"
+
+count="$(grep -Fc 'arguments: "--sourcemap"' <<<"${bundle_output}")"
+if [[ ${count} != "1" ]]; then
+  echo "Expected bun_bundle(sourcemap = True) to emit exactly one --sourcemap flag, got ${count}" >&2
+  exit 1
+fi
+
+grep -Fq 'arguments: "--outfile"' <<<"${bundle_output}"
+grep -Fq 'arguments: "tests/bundle_test/sourcemap_case/entry.ts"' <<<"${bundle_output}"
diff --git a/tests/install_extension_test/BUILD.bazel b/tests/install_extension_test/BUILD.bazel
index bdf75d8..9b1e30d 100644
--- a/tests/install_extension_test/BUILD.bazel
+++ b/tests/install_extension_test/BUILD.bazel
@@ -6,3 +6,10 @@ sh_test(
     args = ["$(location //bun:extensions.bzl)"],
     data = ["//bun:extensions.bzl"],
 )
+
+sh_test(
+    name = "npm_translate_lock_extension_shape_test",
+    srcs = ["npm_extension_shape_test.sh"],
+    args = ["$(location //npm:extensions.bzl)"],
+    data = ["//npm:extensions.bzl"],
+)
diff --git a/tests/install_extension_test/npm_extension_shape_test.sh b/tests/install_extension_test/npm_extension_shape_test.sh
new file mode 100755
index 0000000..3036148
--- /dev/null
+++ b/tests/install_extension_test/npm_extension_shape_test.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+extension_file="$1"
+
+grep -Eq 'npm_translate_lock[[:space:]]*=[[:space:]]*module_extension\(' "${extension_file}"
+grep -Eq 'tag_classes[[:space:]]*=[[:space:]]*\{"translate":[[:space:]]*_translate\}' "${extension_file}"
+grep -Eq '"name":[[:space:]]*attr\.string\(mandatory[[:space:]]*=[[:space:]]*True\)' "${extension_file}"
+grep -Eq '"package_json":[[:space:]]*attr\.label\(mandatory[[:space:]]*=[[:space:]]*True\)' "${extension_file}"
+grep -Eq '"lockfile":[[:space:]]*attr\.label\(mandatory[[:space:]]*=[[:space:]]*True\)' "${extension_file}"
diff --git a/tests/install_test/BUILD.bazel b/tests/install_test/BUILD.bazel
index 4ce9751..8292a7d 100644
--- a/tests/install_test/BUILD.bazel
+++ b/tests/install_test/BUILD.bazel
@@ -102,3 +102,53 @@ sh_test(
         "//conditions:default": ["@bun_linux_x64//:bun"],
     }),
 )
+
+sh_test(
+    name = "bun_install_workspaces_catalog_test",
+    srcs = ["workspaces_catalog.sh"],
+    args = select({
+        ":linux_x86_64": ["$(location @bun_linux_x64//:bun)"],
+        ":linux_aarch64": ["$(location @bun_linux_aarch64//:bun)"],
+        ":darwin_x86_64": ["$(location @bun_darwin_x64//:bun)"],
+        ":darwin_aarch64": ["$(location @bun_darwin_aarch64//:bun)"],
+        "//conditions:default": ["$(location @bun_linux_x64//:bun)"],
+    }),
+    data = select({
+        ":linux_x86_64": ["@bun_linux_x64//:bun"],
+        ":linux_aarch64": ["@bun_linux_aarch64//:bun"],
+        ":darwin_x86_64": ["@bun_darwin_x64//:bun"],
+        ":darwin_aarch64": ["@bun_darwin_aarch64//:bun"],
+        "//conditions:default": ["@bun_linux_x64//:bun"],
+    }),
+)
+
+sh_test(
+    name = "bun_install_workspace_parity_test",
+    srcs = ["workspace_parity.sh"],
+    env_inherit = ["PATH"],
+    args = select({
+        ":linux_x86_64": ["$(location @bun_linux_x64//:bun)"],
+        ":linux_aarch64": ["$(location @bun_linux_aarch64//:bun)"],
+        ":darwin_x86_64": ["$(location @bun_darwin_x64//:bun)"],
+        ":darwin_aarch64": ["$(location @bun_darwin_aarch64//:bun)"],
+        "//conditions:default": ["$(location @bun_linux_x64//:bun)"],
+    }),
+    data = select({
+        ":linux_x86_64": ["@bun_linux_x64//:bun"],
+        ":linux_aarch64": ["@bun_linux_aarch64//:bun"],
+        ":darwin_x86_64": ["@bun_darwin_x64//:bun"],
+        ":darwin_aarch64": ["@bun_darwin_aarch64//:bun"],
+        "//conditions:default": ["@bun_linux_x64//:bun"],
+    }) + [
+        "//:repo_runtime_files",
+        "//bun:repo_runtime_files",
+        "//internal:repo_runtime_files",
+    ],
+)
+
+sh_test(
+    name = "bun_install_install_flags_shape_test",
+    srcs = ["install_flags_shape.sh"],
+    args = ["$(location //internal:bun_install.bzl)"],
+    data = ["//internal:bun_install.bzl"],
+)
diff --git a/tests/install_test/clean_install.sh b/tests/install_test/clean_install.sh
index 93647f9..0eb37e1 100755
--- a/tests/install_test/clean_install.sh
+++ b/tests/install_test/clean_install.sh
@@ -5,7 +5,7 @@ bun_path="$1"
 workdir="$(mktemp -d)"
 trap 'rm -rf "${workdir}"' EXIT
 
-cat > "${workdir}/package.json" <<'JSON'
+cat >"${workdir}/package.json" <<'JSON'
 {
   "name": "clean-install-test",
   "version": "1.0.0"
diff --git a/tests/install_test/determinism.sh b/tests/install_test/determinism.sh
index fe382bd..904a353 100755
--- a/tests/install_test/determinism.sh
+++ b/tests/install_test/determinism.sh
@@ -4,10 +4,11 @@ set -euo pipefail
 rule_file="$1"
 
 grep -Eq 'install", "--frozen-lockfile", "--no-progress"' "${rule_file}"
-grep -Eq 'repository_ctx\.file\("package\.json", repository_ctx\.read\(package_json\)\)' "${rule_file}"
+grep -Eq 'repository_ctx\.file\("package\.json", _normalized_root_manifest\(repository_ctx, package_json\)\)' "${rule_file}"
 grep -Eq 'lockfile_name = bun_lockfile\.basename' "${rule_file}"
 grep -Eq 'if lockfile_name not in \["bun\.lock", "bun\.lockb"\]:' "${rule_file}"
 grep -Eq 'repository_ctx\.symlink\(bun_lockfile, lockfile_name\)' "${rule_file}"
 grep -Eq 'glob\(\["\*\*/node_modules/\*\*"\]' "${rule_file}"
 grep -Eq '_DEFAULT_INSTALL_INPUTS = \[' "${rule_file}"
 grep -Eq '"install_inputs": attr\.label_list\(allow_files = True\)' "${rule_file}"
+grep -Eq '_materialize_install_inputs\(repository_ctx, package_json\)' "${rule_file}"
diff --git a/tests/install_test/environment_shape.sh b/tests/install_test/environment_shape.sh
index 09b17f0..27d5fb1 100755
--- a/tests/install_test/environment_shape.sh
+++ b/tests/install_test/environment_shape.sh
@@ -7,3 +7,4 @@ grep -Eq 'install_args = \[str\(bun_bin\), "--bun", "install", "--frozen-lockfil
 grep -Eq 'if repository_ctx\.attr\.isolated_home:' "${rule_file}"
 grep -Eq 'environment[[:space:]]*=[[:space:]]*\{"HOME":[[:space:]]*str\(repository_ctx\.path\("\."\)\)\}' "${rule_file}"
 grep -Eq '"isolated_home": attr\.bool\(default = True\)' "${rule_file}"
+grep -Eq '"install_flags": attr\.string_list\(\)' "${rule_file}"
diff --git a/tests/install_test/install_flags_shape.sh b/tests/install_test/install_flags_shape.sh
new file mode 100755
index 0000000..8470b8b
--- /dev/null
+++ b/tests/install_test/install_flags_shape.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+rule_file="$1"
+
+grep -Fq 'repository_ctx.attr.production' "${rule_file}"
+grep -Fq '"--production"' "${rule_file}"
+grep -Fq 'for omit in repository_ctx.attr.omit' "${rule_file}"
+grep -Fq '"--omit"' "${rule_file}"
+grep -Fq 'repository_ctx.attr.linker' "${rule_file}"
+grep -Fq '"--linker"' "${rule_file}"
+grep -Fq 'repository_ctx.attr.backend' "${rule_file}"
+grep -Fq '"--backend"' "${rule_file}"
+grep -Fq 'repository_ctx.attr.ignore_scripts' "${rule_file}"
+grep -Fq '"--ignore-scripts"' "${rule_file}"
+grep -Fq 'repository_ctx.attr.install_flags' "${rule_file}"
diff --git a/tests/install_test/workspace_parity.sh b/tests/install_test/workspace_parity.sh
index 41b35f2..108fef4 100755
--- a/tests/install_test/workspace_parity.sh
+++ b/tests/install_test/workspace_parity.sh
@@ -3,8 +3,12 @@ set -euo pipefail
 
 bun_path="${1:-bun}"
 
-if ! command -v bazel >/dev/null 2>&1; then
-  echo "bazel is required on PATH" >&2
+if command -v bazel >/dev/null 2>&1; then
+  bazel_cmd=(bazel)
+elif command -v bazelisk >/dev/null 2>&1; then
+  bazel_cmd=(bazelisk)
+else
+  echo "bazel or bazelisk is required on PATH" >&2
   exit 1
 fi
 
@@ -199,12 +203,12 @@ chmod +x "${bazel_dir}/node_modules_smoke_test.sh"
 
 (
   cd "${bazel_dir}"
-  bazel build @node_modules//:node_modules >/dev/null
-  bazel test //:node_modules_smoke_test >/dev/null
-  bazel run //:web_build -- --emptyOutDir >/dev/null
+  "${bazel_cmd[@]}" build @node_modules//:node_modules >/dev/null
+  "${bazel_cmd[@]}" test //:node_modules_smoke_test >/dev/null
+  "${bazel_cmd[@]}" run //:web_build -- --emptyOutDir >/dev/null
 )
 
-output_base="$(cd "${bazel_dir}" && bazel info output_base)"
+output_base="$(cd "${bazel_dir}" && "${bazel_cmd[@]}" info output_base)"
 bazel_repo_dir="$(find "${output_base}/external" -maxdepth 1 -type d -name '*+node_modules' | head -n 1)"
 
 if [[ -z ${bazel_repo_dir} ]]; then
@@ -238,6 +242,8 @@ root = sys.argv[1]
 
 def include(rel):
     if rel == "node_modules" or rel.startswith("node_modules/"):
+        if rel == "node_modules/.rules_bun" or rel.startswith("node_modules/.rules_bun/"):
+            return False
         return True
     if rel.startswith("packages/") and "/node_modules" in rel:
         return True
@@ -283,6 +289,8 @@ root = sys.argv[1]
 
 def include(rel):
     if rel == "node_modules" or rel.startswith("node_modules/"):
+        if rel == "node_modules/.rules_bun" or rel.startswith("node_modules/.rules_bun/"):
+            return False
         return True
     if rel.startswith("packages/") and "/node_modules" in rel:
         return True
@@ -343,6 +351,8 @@ for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=Fals
     for name in dirnames + filenames:
         full = os.path.join(dirpath, name)
         rel = os.path.join(rel_dir, name) if rel_dir else name
+        if rel == ".rules_bun" or rel.startswith(".rules_bun/"):
+            continue
         st = os.lstat(full)
         mode = st.st_mode
         if stat.S_ISLNK(mode):
@@ -379,6 +389,8 @@ for dirpath, dirnames, filenames in os.walk(root, topdown=True, followlinks=Fals
     for name in dirnames + filenames:
         full = os.path.join(dirpath, name)
         rel = os.path.join(rel_dir, name) if rel_dir else name
+        if rel == ".rules_bun" or rel.startswith(".rules_bun/"):
+            continue
         st = os.lstat(full)
         mode = st.st_mode
         if stat.S_ISLNK(mode):
@@ -411,7 +423,7 @@ rm -rf "${plain_dist_dir}" "${bazel_dist_dir}"
 
 (
   cd "${bazel_dir}"
-  bazel run //:web_build -- --emptyOutDir --outDir "${bazel_dist_dir}" >/dev/null
+  "${bazel_cmd[@]}" run //:web_build -- --emptyOutDir --outDir "${bazel_dist_dir}" >/dev/null
 )
 
 if [[ ! -d ${plain_dist_dir} ]]; then
diff --git a/tests/install_test/workspaces_catalog.sh b/tests/install_test/workspaces_catalog.sh
new file mode 100755
index 0000000..e3fec05
--- /dev/null
+++ b/tests/install_test/workspaces_catalog.sh
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+bun_path="$1"
+workdir="$(mktemp -d)"
+trap 'rm -rf "${workdir}"' EXIT
+
+mkdir -p "${workdir}/packages/pkg-a" "${workdir}/packages/pkg-b" "${workdir}/packages/web"
+
+cat >"${workdir}/package.json" <<'JSON'
+{
+  "name": "workspace-catalog-root",
+  "private": true,
+  "workspaces": {
+    "packages": ["packages/*"],
+    "catalog": {
+      "is-number": "7.0.0",
+      "vite": "5.4.14"
+    },
+    "catalogs": {
+      "testing": {
+        "vitest": "3.2.4"
+      }
+    }
+  }
+}
+JSON
+
+cat >"${workdir}/packages/pkg-a/package.json" <<'JSON'
+{
+  "name": "@workspace/pkg-a",
+  "version": "1.0.0",
+  "main": "index.js",
+  "dependencies": {
+    "is-number": "catalog:"
+  },
+  "scripts": {
+    "check": "bun -e \"const version = require('is-number/package.json').version; if (version !== '7.0.0') { console.error(version); process.exit(1); }\""
+  }
+}
+JSON
+
+cat >"${workdir}/packages/pkg-a/index.js" <<'JS'
+module.exports = { value: 42 };
+JS
+
+cat >"${workdir}/packages/pkg-b/package.json" <<'JSON'
+{
+  "name": "@workspace/pkg-b",
+  "version": "1.0.0",
+  "dependencies": {
+    "@workspace/pkg-a": "workspace:*",
+    "is-number": "catalog:"
+  },
+  "scripts": {
+    "check": "bun -e \"const { value } = require('@workspace/pkg-a'); const version = require('is-number/package.json').version; if (value !== 42 || version !== '7.0.0') { console.error({ value, version }); process.exit(1); }\""
+  }
+}
+JSON
+
+cat >"${workdir}/packages/web/package.json" <<'JSON'
+{
+  "name": "@workspace/web",
+  "private": true,
+  "devDependencies": {
+    "vite": "catalog:",
+    "vitest": "catalog:testing"
+  },
+  "scripts": {
+    "check": "bun -e \"const viteVersion = require('vite/package.json').version; const vitestVersion = require('vitest/package.json').version; if (viteVersion !== '5.4.14' || vitestVersion !== '3.2.4') { console.error({ viteVersion, vitestVersion }); process.exit(1); }\""
+  }
+}
+JSON
+
+"${bun_path}" install --cwd "${workdir}" >/dev/null
+rm -rf "${workdir}/node_modules" "${workdir}/packages/"*/node_modules
+"${bun_path}" install --cwd "${workdir}" --frozen-lockfile >/dev/null
+
+"${bun_path}" run --cwd "${workdir}/packages/pkg-a" check >/dev/null
+"${bun_path}" run --cwd "${workdir}/packages/pkg-b" check >/dev/null
+"${bun_path}" run --cwd "${workdir}/packages/web" check >/dev/null
diff --git a/tests/integration_test/BUILD.bazel b/tests/integration_test/BUILD.bazel
index 672d7f0..902db4d 100644
--- a/tests/integration_test/BUILD.bazel
+++ b/tests/integration_test/BUILD.bazel
@@ -4,6 +4,7 @@ test_suite(
     name = "examples_test",
     tests = [
         ":examples_basic_run_e2e_test",
+        ":examples_basic_hot_restart_shape_test",
         ":examples_workspace_bundle_e2e_test",
         ":examples_workspace_catalog_shape_test",
         ":examples_vite_monorepo_catalog_shape_test",
@@ -31,6 +32,13 @@ sh_test(
     data = ["//examples/basic:web_dev"],
 )
 
+sh_test(
+    name = "examples_basic_hot_restart_shape_test",
+    srcs = ["examples_basic_hot_restart_shape_test.sh"],
+    args = ["$(location //examples/basic:web_dev_hot_restart)"],
+    data = ["//examples/basic:web_dev_hot_restart"],
+)
+
 sh_test(
     name = "examples_workspace_bundle_e2e_test",
     srcs = ["examples_workspace_bundle_e2e_test.sh"],
diff --git a/tests/integration_test/examples_basic_e2e_build_test.sh b/tests/integration_test/examples_basic_e2e_build_test.sh
index 5a4108b..3d36399 100755
--- a/tests/integration_test/examples_basic_e2e_build_test.sh
+++ b/tests/integration_test/examples_basic_e2e_build_test.sh
@@ -4,6 +4,6 @@ set -euo pipefail
 build_file="$1"
 readme_file="$2"
 
-[[ -f "${build_file}" ]]
-[[ -f "${readme_file}" ]]
+[[ -f ${build_file} ]]
+[[ -f ${readme_file} ]]
 grep -Eq '^package\(default_visibility = \["//visibility:public"\]\)$' "${build_file}"
diff --git a/tests/integration_test/examples_basic_hot_restart_shape_test.sh b/tests/integration_test/examples_basic_hot_restart_shape_test.sh
new file mode 100755
index 0000000..9656f1c
--- /dev/null
+++ b/tests/integration_test/examples_basic_hot_restart_shape_test.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+binary="$1"
+
+grep -Fq -- 'watch_mode="hot"' "${binary}"
+grep -Fq -- 'bun_args+=("--hot")' "${binary}"
+grep -Fq -- '--no-clear-screen' "${binary}"
+grep -Fq -- 'if [[ 1 -eq 0 ]]; then' "${binary}"
+grep -Fq -- 'readarray -t restart_paths' "${binary}"
+grep -Fq -- 'examples/basic/README.md' "${binary}"
diff --git a/tests/js_compat_test/BUILD.bazel b/tests/js_compat_test/BUILD.bazel
new file mode 100644
index 0000000..79ff97c
--- /dev/null
+++ b/tests/js_compat_test/BUILD.bazel
@@ -0,0 +1,68 @@
+load("//js:defs.bzl", "js_binary", "js_run_devserver", "js_test", "ts_library")
+load("@rules_shell//shell:sh_test.bzl", "sh_test")
+
+ts_library(
+    name = "helper_lib",
+    srcs = ["helper.ts"],
+    data = ["payload.txt"],
+)
+
+js_binary(
+    name = "compat_bin",
+    entry_point = "main.ts",
+    deps = [":helper_lib"],
+    args = ["compat-mode"],
+)
+
+sh_test(
+    name = "js_binary_compat_test",
+    srcs = ["run_binary.sh"],
+    args = ["$(location :compat_bin)"],
+    data = [":compat_bin"],
+)
+
+js_test(
+    name = "compat_suite",
+    entry_point = "app.test.ts",
+    deps = [":helper_lib"],
+)
+
+js_run_devserver(
+    name = "compat_devserver",
+    tool = ":compat_bin",
+    args = ["devserver-mode"],
+)
+
+sh_test(
+    name = "js_run_devserver_compat_test",
+    srcs = ["run_devserver.sh"],
+    args = ["$(location :compat_devserver)"],
+    data = [":compat_devserver"],
+)
+
+js_run_devserver(
+    name = "compat_devserver_with_package_json",
+    tool = ":compat_bin",
+    package_json = "app/package.json",
+    working_dir = "package",
+)
+
+js_run_devserver(
+    name = "compat_devserver_with_package_dir_hint",
+    tool = ":compat_bin",
+    package_dir_hint = "app",
+    working_dir = "package",
+)
+
+sh_test(
+    name = "js_run_devserver_workspace_shape_test",
+    srcs = ["verify_workspace_shape.sh"],
+    args = [
+        "$(location :compat_devserver_with_package_json)",
+        "$(location :compat_devserver_with_package_dir_hint)",
+    ],
+    data = [
+        ":compat_devserver_with_package_json",
+        ":compat_devserver_with_package_dir_hint",
+    ],
+)
diff --git a/tests/js_compat_test/app.test.ts b/tests/js_compat_test/app.test.ts
new file mode 100644
index 0000000..7a8de3c
--- /dev/null
+++ b/tests/js_compat_test/app.test.ts
@@ -0,0 +1,7 @@
+import { expect, test } from "bun:test";
+
+import { helperMessage } from "./helper.ts";
+
+test("js_test compatibility layer propagates deps and data", () => {
+  expect(helperMessage()).toBe("helper:payload-from-lib");
+});
diff --git a/tests/js_compat_test/app/package.json b/tests/js_compat_test/app/package.json
new file mode 100644
index 0000000..f46ec7c
--- /dev/null
+++ b/tests/js_compat_test/app/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "js-compat-app",
+  "private": true
+}
diff --git a/tests/js_compat_test/helper.ts b/tests/js_compat_test/helper.ts
new file mode 100644
index 0000000..12b839d
--- /dev/null
+++ b/tests/js_compat_test/helper.ts
@@ -0,0 +1,6 @@
+import { readFileSync } from "node:fs";
+
+export function helperMessage(): string {
+  const payload = readFileSync(new URL("./payload.txt", import.meta.url), "utf8").trim();
+  return `helper:${payload}`;
+}
diff --git a/tests/js_compat_test/main.ts b/tests/js_compat_test/main.ts
new file mode 100644
index 0000000..699ff5d
--- /dev/null
+++ b/tests/js_compat_test/main.ts
@@ -0,0 +1,3 @@
+import { helperMessage } from "./helper.ts";
+
+console.log(`${helperMessage()} ${Bun.argv.slice(2).join(" ")}`.trim());
diff --git a/tests/js_compat_test/payload.txt b/tests/js_compat_test/payload.txt
new file mode 100644
index 0000000..7991bf4
--- /dev/null
+++ b/tests/js_compat_test/payload.txt
@@ -0,0 +1 @@
+payload-from-lib
diff --git a/tests/js_compat_test/run_binary.sh b/tests/js_compat_test/run_binary.sh
new file mode 100755
index 0000000..824bad7
--- /dev/null
+++ b/tests/js_compat_test/run_binary.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+binary="$1"
+output="$("${binary}")"
+
+if [[ ${output} != "helper:payload-from-lib compat-mode" ]]; then
+  echo "unexpected output: ${output}" >&2
+  exit 1
+fi
diff --git a/tests/js_compat_test/run_devserver.sh b/tests/js_compat_test/run_devserver.sh
new file mode 100755
index 0000000..4e9bd4b
--- /dev/null
+++ b/tests/js_compat_test/run_devserver.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+binary="$1"
+output="$("${binary}")"
+
+if [[ ${output} != "helper:payload-from-lib compat-mode devserver-mode" ]]; then
+  echo "unexpected output: ${output}" >&2
+  exit 1
+fi
diff --git a/tests/js_compat_test/verify_workspace_shape.sh b/tests/js_compat_test/verify_workspace_shape.sh
new file mode 100755
index 0000000..b99865b
--- /dev/null
+++ b/tests/js_compat_test/verify_workspace_shape.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+package_json_launcher="$1"
+package_dir_hint_launcher="$2"
+
+grep -Fq -- 'package_json="${runfiles_dir}/_main/tests/js_compat_test/app/package.json"' "${package_json_launcher}"
+grep -Fq -- 'package_rel_dir_hint="."' "${package_json_launcher}"
+grep -Fq -- 'working_dir_mode="package"' "${package_json_launcher}"
+
+grep -Fq -- 'package_json=""' "${package_dir_hint_launcher}"
+grep -Fq -- 'package_rel_dir_hint="app"' "${package_dir_hint_launcher}"
+grep -Fq -- 'working_dir_mode="package"' "${package_dir_hint_launcher}"
diff --git a/tests/library_test/verify_bundle.sh b/tests/library_test/verify_bundle.sh
index 006fd97..d33d867 100755
--- a/tests/library_test/verify_bundle.sh
+++ b/tests/library_test/verify_bundle.sh
@@ -3,7 +3,7 @@ set -euo pipefail
 
 bundle="$1"
 
-if [[ ! -s "${bundle}" ]]; then
+if [[ ! -s ${bundle} ]]; then
   echo "Expected bundled output to exist and be non-empty: ${bundle}" >&2
   exit 1
 fi
diff --git a/tests/npm_compat_test/BUILD.bazel b/tests/npm_compat_test/BUILD.bazel
new file mode 100644
index 0000000..2ed0064
--- /dev/null
+++ b/tests/npm_compat_test/BUILD.bazel
@@ -0,0 +1,59 @@
+load("@rules_shell//shell:sh_test.bzl", "sh_test")
+
+config_setting(
+    name = "linux_x86_64",
+    constraint_values = [
+        "@platforms//os:linux",
+        "@platforms//cpu:x86_64",
+    ],
+)
+
+config_setting(
+    name = "linux_aarch64",
+    constraint_values = [
+        "@platforms//os:linux",
+        "@platforms//cpu:aarch64",
+    ],
+)
+
+config_setting(
+    name = "darwin_x86_64",
+    constraint_values = [
+        "@platforms//os:macos",
+        "@platforms//cpu:x86_64",
+    ],
+)
+
+config_setting(
+    name = "darwin_aarch64",
+    constraint_values = [
+        "@platforms//os:macos",
+        "@platforms//cpu:aarch64",
+    ],
+)
+
+sh_test(
+    name = "npm_translate_lock_workspace_test",
+    srcs = ["npm_translate_lock_workspace_test.sh"],
+    env_inherit = ["PATH"],
+    args = select({
+        ":linux_x86_64": ["$(location @bun_linux_x64//:bun)"],
+        ":linux_aarch64": ["$(location @bun_linux_aarch64//:bun)"],
+        ":darwin_x86_64": ["$(location @bun_darwin_x64//:bun)"],
+        ":darwin_aarch64": ["$(location @bun_darwin_aarch64//:bun)"],
+        "//conditions:default": ["$(location @bun_linux_x64//:bun)"],
+    }),
+    data = select({
+        ":linux_x86_64": ["@bun_linux_x64//:bun"],
+        ":linux_aarch64": ["@bun_linux_aarch64//:bun"],
+        ":darwin_x86_64": ["@bun_darwin_x64//:bun"],
+        ":darwin_aarch64": ["@bun_darwin_aarch64//:bun"],
+        "//conditions:default": ["@bun_linux_x64//:bun"],
+    }) + [
+        "//:repo_runtime_files",
+        "//bun:repo_runtime_files",
+        "//internal:repo_runtime_files",
+        "//js:repo_runtime_files",
+        "//npm:repo_runtime_files",
+    ],
+)
diff --git a/tests/npm_compat_test/npm_translate_lock_workspace_test.sh b/tests/npm_compat_test/npm_translate_lock_workspace_test.sh
new file mode 100755
index 0000000..41ad8ca
--- /dev/null
+++ b/tests/npm_compat_test/npm_translate_lock_workspace_test.sh
@@ -0,0 +1,118 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if command -v bazel >/dev/null 2>&1; then
+  bazel_bin="$(command -v bazel)"
+elif command -v bazelisk >/dev/null 2>&1; then
+  bazel_bin="$(command -v bazelisk)"
+else
+  echo "bazel or bazelisk is required on PATH" >&2
+  exit 1
+fi
+
+bun_path="${1:-bun}"
+
+script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
+rules_bun_root="$(cd "${script_dir}/../.." && pwd -P)"
+
+workdir="$(mktemp -d)"
+trap 'rm -rf "${workdir}"' EXIT
+
+fixture_dir="${workdir}/fixture"
+mkdir -p "${fixture_dir}"
+
+cat >"${fixture_dir}/package.json" <<'JSON'
+{
+  "name": "npm-compat-test",
+  "type": "module",
+  "dependencies": {
+    "is-number": "7.0.0"
+  }
+}
+JSON
+
+cat >"${fixture_dir}/main.js" <<'JS'
+import isNumber from "is-number";
+
+console.log(`compat:${isNumber(42)}`);
+JS
+
+"${bun_path}" install --cwd "${fixture_dir}" >/dev/null
+rm -rf "${fixture_dir}/node_modules"
+
+cat >"${fixture_dir}/MODULE.bazel" <"${fixture_dir}/BUILD.bazel" <<'EOF'
+load("@npm//:defs.bzl", "npm_link_all_packages")
+load("@rules_bun//js:defs.bzl", "js_binary")
+
+exports_files([
+    "bun.lock",
+    "main.js",
+    "package.json",
+])
+
+npm_link_all_packages()
+
+js_binary(
+    name = "app",
+    entry_point = "main.js",
+    node_modules = ":node_modules",
+)
+EOF
+
+output="$(
+  cd "${fixture_dir}" &&
+    "${bazel_bin}" run //:app
+)"
+
+if [[ ${output} != *"compat:true"* ]]; then
+  echo "unexpected output: ${output}" >&2
+  exit 1
+fi
+
+query_output="$(
+  cd "${fixture_dir}" &&
+    "${bazel_bin}" query //:npm__is_number
+)"
+if ! grep -Fxq "//:npm__is_number" <<<"${query_output}"; then
+  echo "expected npm_link_all_packages to create //:npm__is_number" >&2
+  exit 1
+fi
diff --git a/tests/script_test/BUILD.bazel b/tests/script_test/BUILD.bazel
index 7c6550b..e9e89e8 100644
--- a/tests/script_test/BUILD.bazel
+++ b/tests/script_test/BUILD.bazel
@@ -83,4 +83,136 @@ sh_test(
         ":vite_monorepo_app_a_dev_server",
         ":vite_monorepo_app_b_dev_server",
     ],
-)
\ No newline at end of file
+)
+
+bun_script(
+    name = "paraglide_monorepo_app_a_build",
+    script = "build:app-a",
+    package_json = "paraglide_monorepo/package.json",
+    node_modules = "@script_test_paraglide_monorepo_node_modules//:node_modules",
+    data = [
+        "paraglide_monorepo/scripts/build-app-a.mjs",
+        "paraglide_monorepo/scripts/build-app-b.mjs",
+        "paraglide_monorepo/packages/i18n/package.json",
+        "paraglide_monorepo/packages/i18n/project.inlang/settings.json",
+        "paraglide_monorepo/packages/i18n/messages/en.json",
+        "paraglide_monorepo/packages/i18n/messages/sv.json",
+        "paraglide_monorepo/packages/app-a/package.json",
+        "paraglide_monorepo/packages/app-a/index.html",
+        "paraglide_monorepo/packages/app-a/main.js",
+        "paraglide_monorepo/packages/app-a/vite.config.js",
+        "paraglide_monorepo/packages/app-b/package.json",
+        "paraglide_monorepo/packages/app-b/index.html",
+        "paraglide_monorepo/packages/app-b/main.js",
+        "paraglide_monorepo/packages/app-b/vite.config.js",
+    ],
+)
+
+bun_script(
+    name = "paraglide_monorepo_app_b_build",
+    script = "build:app-b",
+    package_json = "paraglide_monorepo/package.json",
+    node_modules = "@script_test_paraglide_monorepo_node_modules//:node_modules",
+    data = [
+        "paraglide_monorepo/scripts/build-app-a.mjs",
+        "paraglide_monorepo/scripts/build-app-b.mjs",
+        "paraglide_monorepo/packages/i18n/package.json",
+        "paraglide_monorepo/packages/i18n/project.inlang/settings.json",
+        "paraglide_monorepo/packages/i18n/messages/en.json",
+        "paraglide_monorepo/packages/i18n/messages/sv.json",
+        "paraglide_monorepo/packages/app-a/package.json",
+        "paraglide_monorepo/packages/app-a/index.html",
+        "paraglide_monorepo/packages/app-a/main.js",
+        "paraglide_monorepo/packages/app-a/vite.config.js",
+        "paraglide_monorepo/packages/app-b/package.json",
+        "paraglide_monorepo/packages/app-b/index.html",
+        "paraglide_monorepo/packages/app-b/main.js",
+        "paraglide_monorepo/packages/app-b/vite.config.js",
+    ],
+)
+
+sh_test(
+    name = "bun_script_paraglide_monorepo_build_test",
+    srcs = ["run_paraglide_monorepo_builds.sh"],
+    args = [
+        "$(location :paraglide_monorepo_app_a_build)",
+        "$(location :paraglide_monorepo_app_b_build)",
+    ],
+    data = [
+        ":paraglide_monorepo_app_a_build",
+        ":paraglide_monorepo_app_b_build",
+    ],
+)
+
+bun_script(
+    name = "workspace_filtered_script",
+    script = "say",
+    package_json = "workspace_run/package.json",
+    data = [
+        "workspace_run/packages/pkg-a/package.json",
+        "workspace_run/packages/pkg-a/say.ts",
+        "workspace_run/packages/pkg-b/package.json",
+        "workspace_run/packages/pkg-b/say.ts",
+    ],
+    filters = ["./packages/pkg-a"],
+    execution_mode = "sequential",
+    silent = True,
+)
+
+sh_test(
+    name = "bun_script_workspace_filter_test",
+    srcs = ["run_workspace_script.sh"],
+    args = ["$(location :workspace_filtered_script)"],
+    data = [":workspace_filtered_script"],
+)
+
+bun_script(
+    name = "workspace_parallel_script",
+    script = "say",
+    package_json = "workspace_run/package.json",
+    data = [
+        "workspace_run/packages/pkg-a/package.json",
+        "workspace_run/packages/pkg-a/say.ts",
+        "workspace_run/packages/pkg-b/package.json",
+        "workspace_run/packages/pkg-b/say.ts",
+    ],
+    workspaces = True,
+    execution_mode = "parallel",
+)
+
+sh_test(
+    name = "bun_script_workspace_parallel_test",
+    srcs = ["run_workspace_parallel.sh"],
+    args = ["$(location :workspace_parallel_script)"],
+    data = [":workspace_parallel_script"],
+)
+
+bun_script(
+    name = "workspace_flagged_script",
+    script = "say",
+    package_json = "workspace_run/package.json",
+    data = [
+        "workspace_run/packages/pkg-a/package.json",
+        "workspace_run/packages/pkg-a/say.ts",
+        "workspace_run/packages/pkg-b/package.json",
+        "workspace_run/packages/pkg-b/say.ts",
+    ],
+    workspaces = True,
+    execution_mode = "parallel",
+    no_exit_on_error = True,
+    shell = "system",
+)
+
+sh_test(
+    name = "bun_script_workspace_flag_shape_test",
+    srcs = ["verify_launcher_flags.sh"],
+    args = [
+        "$(location :workspace_flagged_script)",
+        "--workspaces",
+        "--parallel",
+        "--no-exit-on-error",
+        "--shell",
+        "system",
+    ],
+    data = [":workspace_flagged_script"],
+)
diff --git a/tests/script_test/paraglide_monorepo/bun.lock b/tests/script_test/paraglide_monorepo/bun.lock
new file mode 100644
index 0000000..8202199
--- /dev/null
+++ b/tests/script_test/paraglide_monorepo/bun.lock
@@ -0,0 +1,218 @@
+{
+  "lockfileVersion": 1,
+  "configVersion": 1,
+  "workspaces": {
+    "": {
+      "name": "paraglide-monorepo-test",
+    },
+    "packages/app-a": {
+      "name": "paraglide-monorepo-app-a",
+      "dependencies": {
+        "@workspace/i18n": "workspace:*",
+      },
+      "devDependencies": {
+        "vite": "catalog:",
+      },
+    },
+    "packages/app-b": {
+      "name": "paraglide-monorepo-app-b",
+      "dependencies": {
+        "@workspace/i18n": "workspace:*",
+      },
+      "devDependencies": {
+        "vite": "catalog:",
+      },
+    },
+    "packages/i18n": {
+      "name": "@workspace/i18n",
+      "devDependencies": {
+        "@inlang/paraglide-js": "catalog:",
+      },
+    },
+  },
+  "catalog": {
+    "@inlang/paraglide-js": "2.13.2",
+    "vite": "5.4.14",
+  },
+  "packages": {
+    "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
+
+    "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
+
+    "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
+
+    "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
+
+    "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
+
+    "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
+
+    "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
+
+    "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
+
+    "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
+
+    "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
+
+    "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
+
+    "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
+
+    "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
+
+    "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
+
+    "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
+
+    "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
+
+    "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
+
+    "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
+
+    "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
+
+    "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
+
+    "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
+
+    "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
+
+    "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
+
+    "@inlang/paraglide-js": ["@inlang/paraglide-js@2.13.2", "", { "dependencies": { "@inlang/recommend-sherlock": "^0.2.1", "@inlang/sdk": "^2.7.0", "commander": "11.1.0", "consola": "3.4.0", "json5": "2.2.3", "unplugin": "^2.1.2", "urlpattern-polyfill": "^10.0.0" }, "bin": { "paraglide-js": "bin/run.js" } }, "sha512-ecxw95pmMbasVj7M/B6pu5wqYHomYQBcu3QzDl1svwAkbnRqRmsdrH4IizzFwqeVWd+uluibMIy1VOGywin94A=="],
+
+    "@inlang/recommend-sherlock": ["@inlang/recommend-sherlock@0.2.1", "", { "dependencies": { "comment-json": "^4.2.3" } }, "sha512-ckv8HvHy/iTqaVAEKrr+gnl+p3XFNwe5D2+6w6wJk2ORV2XkcRkKOJ/XsTUJbPSiyi4PI+p+T3bqbmNx/rDUlg=="],
+
+    "@inlang/sdk": ["@inlang/sdk@2.7.0", "", { "dependencies": { "@lix-js/sdk": "0.4.7", "@sinclair/typebox": "^0.31.17", "kysely": "^0.27.4", "sqlite-wasm-kysely": "0.3.0", "uuid": "^13.0.0" } }, "sha512-yJNBD0o8i29TTJqWX5uDRHxnalDGcsUDctxepzFXsUfkzqGWfiFBxODdxvReqvM2CuKAAOo/kib/F1UcgdYFNQ=="],
+
+    "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+    "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
+
+    "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+    "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
+
+    "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
+
+    "@lix-js/sdk": ["@lix-js/sdk@0.4.7", "", { "dependencies": { "@lix-js/server-protocol-schema": "0.1.1", "dedent": "1.5.1", "human-id": "^4.1.1", "js-sha256": "^0.11.0", "kysely": "^0.27.4", "sqlite-wasm-kysely": "0.3.0", "uuid": "^10.0.0" } }, "sha512-pRbW+joG12L0ULfMiWYosIW0plmW4AsUdiPCp+Z8rAsElJ+wJ6in58zhD3UwUcd4BNcpldEGjg6PdA7e0RgsDQ=="],
+
+    "@lix-js/server-protocol-schema": ["@lix-js/server-protocol-schema@0.1.1", "", {}, "sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ=="],
+
+    "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
+
+    "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
+
+    "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
+
+    "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
+
+    "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
+
+    "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
+
+    "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
+
+    "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
+
+    "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
+
+    "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
+
+    "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
+
+    "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
+
+    "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
+
+    "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
+
+    "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
+
+    "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
+
+    "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
+
+    "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
+
+    "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
+
+    "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
+
+    "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
+
+    "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
+
+    "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
+
+    "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
+
+    "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
+
+    "@sinclair/typebox": ["@sinclair/typebox@0.31.28", "", {}, "sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ=="],
+
+    "@sqlite.org/sqlite-wasm": ["@sqlite.org/sqlite-wasm@3.48.0-build4", "", { "bin": { "sqlite-wasm": "bin/index.js" } }, "sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ=="],
+
+    "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
+
+    "@workspace/i18n": ["@workspace/i18n@workspace:packages/i18n"],
+
+    "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
+
+    "array-timsort": ["array-timsort@1.0.3", "", {}, "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ=="],
+
+    "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
+
+    "comment-json": ["comment-json@4.6.2", "", { "dependencies": { "array-timsort": "^1.0.3", "esprima": "^4.0.1" } }, "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w=="],
+
+    "consola": ["consola@3.4.0", "", {}, "sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA=="],
+
+    "dedent": ["dedent@1.5.1", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg=="],
+
+    "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
+
+    "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
+    "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+    "human-id": ["human-id@4.1.3", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="],
+
+    "js-sha256": ["js-sha256@0.11.1", "", {}, "sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg=="],
+
+    "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
+
+    "kysely": ["kysely@0.27.6", "", {}, "sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ=="],
+
+    "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
+    "paraglide-monorepo-app-a": ["paraglide-monorepo-app-a@workspace:packages/app-a"],
+
+    "paraglide-monorepo-app-b": ["paraglide-monorepo-app-b@workspace:packages/app-b"],
+
+    "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+    "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
+
+    "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
+
+    "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
+
+    "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
+    "sqlite-wasm-kysely": ["sqlite-wasm-kysely@0.3.0", "", { "dependencies": { "@sqlite.org/sqlite-wasm": "^3.48.0-build2" }, "peerDependencies": { "kysely": "*" } }, "sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg=="],
+
+    "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="],
+
+    "urlpattern-polyfill": ["urlpattern-polyfill@10.1.0", "", {}, "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw=="],
+
+    "uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="],
+
+    "vite": ["vite@5.4.14", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA=="],
+
+    "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
+
+    "@lix-js/sdk/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
+  }
+}
diff --git a/tests/script_test/paraglide_monorepo/package.json b/tests/script_test/paraglide_monorepo/package.json
new file mode 100644
index 0000000..72a8295
--- /dev/null
+++ b/tests/script_test/paraglide_monorepo/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "paraglide-monorepo-test",
+  "private": true,
+  "workspaces": {
+    "packages": [
+      "packages/*"
+    ],
+    "catalog": {
+      "@inlang/paraglide-js": "2.13.2",
+      "vite": "5.4.14"
+    }
+  },
+  "scripts": {
+    "build:app-a": "bun ./scripts/build-app-a.mjs",
+    "build:app-b": "bun ./scripts/build-app-b.mjs"
+  }
+}
diff --git a/tests/script_test/paraglide_monorepo/packages/app-a/index.html b/tests/script_test/paraglide_monorepo/packages/app-a/index.html
new file mode 100644
index 0000000..a5bf961
--- /dev/null
+++ b/tests/script_test/paraglide_monorepo/packages/app-a/index.html
@@ -0,0 +1,12 @@
+
+
+    
+        
+        
+        Paraglide monorepo app A
+    
+    
+        
+ + + diff --git a/tests/script_test/paraglide_monorepo/packages/app-a/main.js b/tests/script_test/paraglide_monorepo/packages/app-a/main.js new file mode 100644 index 0000000..01699f6 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-a/main.js @@ -0,0 +1,9 @@ +import * as messages from "@workspace/i18n/messages"; +import { setLocale } from "@workspace/i18n/runtime"; + +setLocale("sv"); + +const app = document.querySelector("#app"); +if (app) { + app.textContent = `${messages.hero()} :: ${messages.hello()}`; +} diff --git a/tests/script_test/paraglide_monorepo/packages/app-a/package.json b/tests/script_test/paraglide_monorepo/packages/app-a/package.json new file mode 100644 index 0000000..26f043d --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-a/package.json @@ -0,0 +1,14 @@ +{ + "name": "paraglide-monorepo-app-a", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + }, + "dependencies": { + "@workspace/i18n": "workspace:*" + }, + "devDependencies": { + "vite": "catalog:" + } +} diff --git a/tests/script_test/paraglide_monorepo/packages/app-a/vite.config.js b/tests/script_test/paraglide_monorepo/packages/app-a/vite.config.js new file mode 100644 index 0000000..0e665f3 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-a/vite.config.js @@ -0,0 +1,10 @@ +export default { + resolve: { + preserveSymlinks: true, + }, + optimizeDeps: { + esbuildOptions: { + preserveSymlinks: true, + }, + }, +}; diff --git a/tests/script_test/paraglide_monorepo/packages/app-b/index.html b/tests/script_test/paraglide_monorepo/packages/app-b/index.html new file mode 100644 index 0000000..1070527 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-b/index.html @@ -0,0 +1,12 @@ + + + + + + Paraglide monorepo app B + + +
+ + + diff --git a/tests/script_test/paraglide_monorepo/packages/app-b/main.js b/tests/script_test/paraglide_monorepo/packages/app-b/main.js new file mode 100644 index 0000000..09ab6e4 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-b/main.js @@ -0,0 +1,6 @@ +import * as messages from "@workspace/i18n/messages"; + +const app = document.querySelector("#app"); +if (app) { + app.textContent = `${messages.hero({ locale: "en" })} :: ${messages.hello({ locale: "en" })}`; +} diff --git a/tests/script_test/paraglide_monorepo/packages/app-b/package.json b/tests/script_test/paraglide_monorepo/packages/app-b/package.json new file mode 100644 index 0000000..0d62b6c --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-b/package.json @@ -0,0 +1,14 @@ +{ + "name": "paraglide-monorepo-app-b", + "private": true, + "type": "module", + "scripts": { + "build": "vite build" + }, + "dependencies": { + "@workspace/i18n": "workspace:*" + }, + "devDependencies": { + "vite": "catalog:" + } +} diff --git a/tests/script_test/paraglide_monorepo/packages/app-b/vite.config.js b/tests/script_test/paraglide_monorepo/packages/app-b/vite.config.js new file mode 100644 index 0000000..0e665f3 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/app-b/vite.config.js @@ -0,0 +1,10 @@ +export default { + resolve: { + preserveSymlinks: true, + }, + optimizeDeps: { + esbuildOptions: { + preserveSymlinks: true, + }, + }, +}; diff --git a/tests/script_test/paraglide_monorepo/packages/i18n/messages/en.json b/tests/script_test/paraglide_monorepo/packages/i18n/messages/en.json new file mode 100644 index 0000000..2318743 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/i18n/messages/en.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "hello": "Hello from the shared translations", + "hero": "One shared translation source" +} diff --git a/tests/script_test/paraglide_monorepo/packages/i18n/messages/sv.json b/tests/script_test/paraglide_monorepo/packages/i18n/messages/sv.json new file mode 100644 index 0000000..9242955 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/i18n/messages/sv.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "hello": "Hej fran de delade oversattningarna", + "hero": "En gemensam oversattningskalla" +} diff --git a/tests/script_test/paraglide_monorepo/packages/i18n/package.json b/tests/script_test/paraglide_monorepo/packages/i18n/package.json new file mode 100644 index 0000000..36ce75c --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/i18n/package.json @@ -0,0 +1,15 @@ +{ + "name": "@workspace/i18n", + "private": true, + "type": "module", + "exports": { + "./messages": "./src/paraglide/messages.js", + "./runtime": "./src/paraglide/runtime.js" + }, + "scripts": { + "build": "paraglide-js compile --project ./project.inlang --outdir ./src/paraglide" + }, + "devDependencies": { + "@inlang/paraglide-js": "catalog:" + } +} diff --git a/tests/script_test/paraglide_monorepo/packages/i18n/project.inlang/settings.json b/tests/script_test/paraglide_monorepo/packages/i18n/project.inlang/settings.json new file mode 100644 index 0000000..ac9cde7 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/packages/i18n/project.inlang/settings.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://inlang.com/schema/project-settings", + "baseLocale": "en", + "locales": [ + "en", + "sv" + ], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js", + "https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": "./messages/{locale}.json" + } +} diff --git a/tests/script_test/paraglide_monorepo/scripts/build-app-a.mjs b/tests/script_test/paraglide_monorepo/scripts/build-app-a.mjs new file mode 100644 index 0000000..aae97b9 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/scripts/build-app-a.mjs @@ -0,0 +1,14 @@ +import { spawnSync } from "node:child_process"; + +const bun = process.execPath; +const extraArgs = process.argv.slice(2); + +for (const args of [ + ["run", "--cwd", "./packages/i18n", "build"], + ["run", "--cwd", "./packages/app-a", "build", "--", ...extraArgs], +]) { + const result = spawnSync(bun, args, { stdio: "inherit" }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} diff --git a/tests/script_test/paraglide_monorepo/scripts/build-app-b.mjs b/tests/script_test/paraglide_monorepo/scripts/build-app-b.mjs new file mode 100644 index 0000000..34eac49 --- /dev/null +++ b/tests/script_test/paraglide_monorepo/scripts/build-app-b.mjs @@ -0,0 +1,14 @@ +import { spawnSync } from "node:child_process"; + +const bun = process.execPath; +const extraArgs = process.argv.slice(2); + +for (const args of [ + ["run", "--cwd", "./packages/i18n", "build"], + ["run", "--cwd", "./packages/app-b", "build", "--", ...extraArgs], +]) { + const result = spawnSync(bun, args, { stdio: "inherit" }); + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +} diff --git a/tests/script_test/run_paraglide_monorepo_builds.sh b/tests/script_test/run_paraglide_monorepo_builds.sh new file mode 100755 index 0000000..a304162 --- /dev/null +++ b/tests/script_test/run_paraglide_monorepo_builds.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -euo pipefail + +app_a_binary="$1" +app_b_binary="$2" +workdir="$(mktemp -d)" + +cleanup() { + rm -rf "${workdir}" +} +trap cleanup EXIT + +verify_build() { + local binary="$1" + local out_dir="$2" + local expected_title="$3" + local expected_text="$4" + + "${binary}" --outDir "${out_dir}" >/dev/null + + if [[ ! -f "${out_dir}/index.html" ]]; then + echo "missing build output index.html for ${binary}" >&2 + exit 1 + fi + + if ! grep -Fq "${expected_title}" "${out_dir}/index.html"; then + echo "missing expected title in ${out_dir}/index.html" >&2 + exit 1 + fi + + local asset + asset="$(find "${out_dir}/assets" -type f -name '*.js' | head -n 1)" + if [[ -z ${asset} ]]; then + echo "missing built JS asset for ${binary}" >&2 + exit 1 + fi + + if ! grep -Fq "${expected_text}" "${asset}"; then + echo "missing expected translated text in ${asset}" >&2 + exit 1 + fi +} + +verify_build \ + "${app_a_binary}" \ + "${workdir}/app-a-dist" \ + "Paraglide monorepo app A" \ + "En gemensam oversattningskalla" + +verify_build \ + "${app_b_binary}" \ + "${workdir}/app-b-dist" \ + "Paraglide monorepo app B" \ + "One shared translation source" diff --git a/tests/script_test/run_workspace_parallel.sh b/tests/script_test/run_workspace_parallel.sh new file mode 100755 index 0000000..b59dd92 --- /dev/null +++ b/tests/script_test/run_workspace_parallel.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_bin="$1" +output="$(${script_bin})" + +if [[ ${output} != *"pkg-a"* ]]; then + echo "Expected workspace parallel run output to include pkg-a: ${output}" >&2 + exit 1 +fi + +if [[ ${output} != *"pkg-b"* ]]; then + echo "Expected workspace parallel run output to include pkg-b: ${output}" >&2 + exit 1 +fi diff --git a/tests/script_test/run_workspace_script.sh b/tests/script_test/run_workspace_script.sh new file mode 100755 index 0000000..691bbff --- /dev/null +++ b/tests/script_test/run_workspace_script.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +script_bin="$1" +output="$(${script_bin})" + +if [[ ${output} != *"pkg-a"* ]]; then + echo "Expected workspace run output to include pkg-a: ${output}" >&2 + exit 1 +fi + +if [[ ${output} == *"pkg-b"* ]]; then + echo "Workspace filter unexpectedly included pkg-b: ${output}" >&2 + exit 1 +fi diff --git a/tests/script_test/verify_launcher_flags.sh b/tests/script_test/verify_launcher_flags.sh new file mode 100755 index 0000000..515cd41 --- /dev/null +++ b/tests/script_test/verify_launcher_flags.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +binary="$1" +shift + +for expected in "$@"; do + if ! grep -Fq -- "${expected}" "${binary}"; then + echo "Expected ${binary} to contain ${expected}" >&2 + exit 1 + fi +done diff --git a/tests/script_test/workspace_run/package.json b/tests/script_test/workspace_run/package.json new file mode 100644 index 0000000..2909d1d --- /dev/null +++ b/tests/script_test/workspace_run/package.json @@ -0,0 +1,7 @@ +{ + "name": "workspace-run-root", + "private": true, + "workspaces": [ + "packages/*" + ] +} diff --git a/tests/script_test/workspace_run/packages/pkg-a/package.json b/tests/script_test/workspace_run/packages/pkg-a/package.json new file mode 100644 index 0000000..92faa4b --- /dev/null +++ b/tests/script_test/workspace_run/packages/pkg-a/package.json @@ -0,0 +1,6 @@ +{ + "name": "pkg-a", + "scripts": { + "say": "bun ./say.ts" + } +} diff --git a/tests/script_test/workspace_run/packages/pkg-a/say.ts b/tests/script_test/workspace_run/packages/pkg-a/say.ts new file mode 100644 index 0000000..9f02f95 --- /dev/null +++ b/tests/script_test/workspace_run/packages/pkg-a/say.ts @@ -0,0 +1 @@ +console.log("pkg-a"); diff --git a/tests/script_test/workspace_run/packages/pkg-b/package.json b/tests/script_test/workspace_run/packages/pkg-b/package.json new file mode 100644 index 0000000..88dfd34 --- /dev/null +++ b/tests/script_test/workspace_run/packages/pkg-b/package.json @@ -0,0 +1,6 @@ +{ + "name": "pkg-b", + "scripts": { + "say": "bun ./say.ts" + } +} diff --git a/tests/script_test/workspace_run/packages/pkg-b/say.ts b/tests/script_test/workspace_run/packages/pkg-b/say.ts new file mode 100644 index 0000000..aee7da0 --- /dev/null +++ b/tests/script_test/workspace_run/packages/pkg-b/say.ts @@ -0,0 +1 @@ +console.log("pkg-b"); diff --git a/tests/toolchain_test/toolchain_version.sh b/tests/toolchain_test/toolchain_version.sh index 3024b9e..73e1387 100755 --- a/tests/toolchain_test/toolchain_version.sh +++ b/tests/toolchain_test/toolchain_version.sh @@ -4,7 +4,7 @@ set -euo pipefail bun_path="$1" version="$(${bun_path} --version)" -if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then +if [[ ! ${version} =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then echo "Unexpected bun version output: ${version}" >&2 exit 1 fi