feature/add-build-compile-rules #4
2
MODULE.bazel.lock
generated
2
MODULE.bazel.lock
generated
@@ -192,7 +192,7 @@
|
|||||||
"moduleExtensions": {
|
"moduleExtensions": {
|
||||||
"//bun:extensions.bzl%bun": {
|
"//bun:extensions.bzl%bun": {
|
||||||
"general": {
|
"general": {
|
||||||
"bzlTransitiveDigest": "eSFVebwDN61an1dp3505njvMKN961HH+iY2tK6fEBQQ=",
|
"bzlTransitiveDigest": "mWoMIEcKvXURFuMv68yk2TPrpNykLSLygedENme3WrQ=",
|
||||||
"usagesDigest": "/0BcCMA6AOzLhQaRK6DquxrCfpPHJUjSUaFz4zmQrsM=",
|
"usagesDigest": "/0BcCMA6AOzLhQaRK6DquxrCfpPHJUjSUaFz4zmQrsM=",
|
||||||
"recordedInputs": [
|
"recordedInputs": [
|
||||||
"REPO_MAPPING:,bazel_tools bazel_tools"
|
"REPO_MAPPING:,bazel_tools bazel_tools"
|
||||||
|
|||||||
33
README.md
33
README.md
@@ -33,6 +33,9 @@ The public entrypoint for rule authors and users is `@rules_bun//bun:defs.bzl`.
|
|||||||
- `bun_dev`
|
- `bun_dev`
|
||||||
- `bun_script`
|
- `bun_script`
|
||||||
- `bun_test`
|
- `bun_test`
|
||||||
|
- `js_binary`
|
||||||
|
- `js_test`
|
||||||
|
- `js_run_devserver`
|
||||||
- `js_library`
|
- `js_library`
|
||||||
- `ts_library`
|
- `ts_library`
|
||||||
|
|
||||||
@@ -105,6 +108,36 @@ bun_install_ext.install(
|
|||||||
use_repo(bun_install_ext, "bun_deps")
|
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 `@<repo>//: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
|
## Legacy WORKSPACE usage
|
||||||
|
|
||||||
For non-Bzlmod consumers, the repository exposes a legacy setup macro in
|
For non-Bzlmod consumers, the repository exposes a legacy setup macro in
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ bzl_library(
|
|||||||
"//internal:bun_dev_bzl",
|
"//internal:bun_dev_bzl",
|
||||||
"//internal:bun_script_bzl",
|
"//internal:bun_script_bzl",
|
||||||
"//internal:bun_test_bzl",
|
"//internal:bun_test_bzl",
|
||||||
|
"//internal:js_compat_bzl",
|
||||||
"//internal:js_library_bzl",
|
"//internal:js_library_bzl",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ load("//internal:bun_bundle.bzl", _bun_bundle = "bun_bundle")
|
|||||||
load("//internal:bun_dev.bzl", _bun_dev = "bun_dev")
|
load("//internal:bun_dev.bzl", _bun_dev = "bun_dev")
|
||||||
load("//internal:bun_script.bzl", _bun_script = "bun_script")
|
load("//internal:bun_script.bzl", _bun_script = "bun_script")
|
||||||
load("//internal:bun_test.bzl", _bun_test = "bun_test")
|
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("//internal:js_library.bzl", _js_library = "js_library", _ts_library = "ts_library")
|
||||||
load(":toolchain.bzl", _BunToolchainInfo = "BunToolchainInfo", _bun_toolchain = "bun_toolchain")
|
load(":toolchain.bzl", _BunToolchainInfo = "BunToolchainInfo", _bun_toolchain = "bun_toolchain")
|
||||||
|
|
||||||
@@ -14,8 +15,12 @@ bun_bundle = _bun_bundle
|
|||||||
bun_dev = _bun_dev
|
bun_dev = _bun_dev
|
||||||
bun_script = _bun_script
|
bun_script = _bun_script
|
||||||
bun_test = _bun_test
|
bun_test = _bun_test
|
||||||
|
js_binary = _js_binary
|
||||||
|
js_test = _js_test
|
||||||
|
js_run_devserver = _js_run_devserver
|
||||||
js_library = _js_library
|
js_library = _js_library
|
||||||
ts_library = _ts_library
|
ts_library = _ts_library
|
||||||
|
JsInfo = _JsInfo
|
||||||
BunToolchainInfo = _BunToolchainInfo
|
BunToolchainInfo = _BunToolchainInfo
|
||||||
bun_toolchain = _bun_toolchain
|
bun_toolchain = _bun_toolchain
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ def _bun_install_impl(ctx):
|
|||||||
bun_lockfile = install.bun_lockfile,
|
bun_lockfile = install.bun_lockfile,
|
||||||
install_inputs = install.install_inputs,
|
install_inputs = install.install_inputs,
|
||||||
isolated_home = install.isolated_home,
|
isolated_home = install.isolated_home,
|
||||||
|
visible_repo_name = install.name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ stardoc(
|
|||||||
"bun_dev",
|
"bun_dev",
|
||||||
"bun_script",
|
"bun_script",
|
||||||
"bun_test",
|
"bun_test",
|
||||||
|
"js_binary",
|
||||||
|
"js_run_devserver",
|
||||||
|
"js_test",
|
||||||
"js_library",
|
"js_library",
|
||||||
"ts_library",
|
"ts_library",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Unlike the build rules in [rules.md](rules.md), `bun_install` is not loaded from
|
|||||||
- runs `bun install --frozen-lockfile`
|
- runs `bun install --frozen-lockfile`
|
||||||
- uses your checked-in `package.json` and `bun.lock` or `bun.lockb`
|
- uses your checked-in `package.json` and `bun.lock` or `bun.lockb`
|
||||||
- creates an external Bazel repository exposing `:node_modules`
|
- 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
|
- keeps dependency installation under Bun rather than npm
|
||||||
|
|
||||||
The generated repository can then be passed to rules such as `bun_script`,
|
The generated repository can then be passed to rules such as `bun_script`,
|
||||||
|
|||||||
@@ -2,6 +2,46 @@
|
|||||||
|
|
||||||
This file documents the public rules exported from `@rules_bun//bun:defs.bzl`.
|
This file documents the public rules exported from `@rules_bun//bun:defs.bzl`.
|
||||||
|
|
||||||
|
## js_binary
|
||||||
|
|
||||||
|
Runs a JS/TS entry point with Bun behind a `rules_js`-style name.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
- `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` or `npm_translate_lock`, made available in runfiles.
|
||||||
|
- `data` (label_list, optional): additional runtime files.
|
||||||
|
- `deps` (label_list, optional): library dependencies required by the program.
|
||||||
|
- `args` (string_list, optional): default arguments appended before command-line arguments passed to the binary.
|
||||||
|
- `working_dir` (string, default: `"workspace"`, values: `"workspace" | "entry_point"`): runtime working directory.
|
||||||
|
|
||||||
|
## js_test
|
||||||
|
|
||||||
|
Runs Bun tests behind a `rules_js`-style name.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
- `srcs` (label_list, required): test source files passed to `bun test`.
|
||||||
|
- `node_modules` (label, optional): package files from a `node_modules` tree, typically produced by `bun_install` or `npm_translate_lock`, made available in runfiles.
|
||||||
|
- `deps` (label_list, optional): library dependencies required by tests.
|
||||||
|
- `data` (label_list, optional): additional runtime files needed by tests.
|
||||||
|
- `args` (string_list, optional): default arguments appended after the test source list.
|
||||||
|
|
||||||
|
## js_run_devserver
|
||||||
|
|
||||||
|
Runs an executable target from a staged JS workspace.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
|
||||||
|
- `tool` (label, required): executable target to launch as the dev server.
|
||||||
|
- `args` (string_list, optional): default arguments appended before command-line arguments passed to the dev server.
|
||||||
|
- `package_json` (label, optional): package manifest used to resolve the package working directory.
|
||||||
|
- `package_dir_hint` (string, default: `"."`): package-relative directory hint when `package_json` is omitted.
|
||||||
|
- `node_modules` (label, optional): package files from a `node_modules` tree, typically produced by `bun_install` or `npm_translate_lock`, made available in runfiles.
|
||||||
|
- `deps` (label_list, optional): library dependencies required by the dev server.
|
||||||
|
- `data` (label_list, optional): additional runtime files.
|
||||||
|
- `working_dir` (string, default: `"workspace"`, values: `"workspace" | "package"`): runtime working directory.
|
||||||
|
|
||||||
## bun_binary
|
## bun_binary
|
||||||
|
|
||||||
Runs a JS/TS entry point with Bun as an executable target (`bazel run`).
|
Runs a JS/TS entry point with Bun as an executable target (`bazel run`).
|
||||||
@@ -11,6 +51,8 @@ Attributes:
|
|||||||
- `entry_point` (label, required): path to the main JS/TS file to execute.
|
- `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.
|
- `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.
|
- `data` (label_list, optional): additional runtime files.
|
||||||
|
- `deps` (label_list, optional): library dependencies required by the program.
|
||||||
|
- `args` (string_list, optional): default arguments appended before command-line arguments passed to the binary.
|
||||||
- `working_dir` (string, default: `"workspace"`, values: `"workspace" | "entry_point"`): runtime working directory.
|
- `working_dir` (string, default: `"workspace"`, values: `"workspace" | "entry_point"`): runtime working directory.
|
||||||
|
|
||||||
## bun_dev
|
## bun_dev
|
||||||
@@ -68,6 +110,7 @@ Attributes:
|
|||||||
- `node_modules` (label, optional): package files from a `node_modules` tree, typically produced by `bun_install`, made available in runfiles.
|
- `node_modules` (label, optional): package files from a `node_modules` tree, typically produced by `bun_install`, made available in runfiles.
|
||||||
- `deps` (label_list, optional): library dependencies required by tests.
|
- `deps` (label_list, optional): library dependencies required by tests.
|
||||||
- `data` (label_list, optional): additional runtime files needed by tests.
|
- `data` (label_list, optional): additional runtime files needed by tests.
|
||||||
|
- `args` (string_list, optional): default arguments appended after the test source list.
|
||||||
|
|
||||||
## js_library
|
## js_library
|
||||||
|
|
||||||
@@ -76,6 +119,8 @@ Aggregates JavaScript sources and transitive Bun source dependencies.
|
|||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
- `srcs` (label_list, optional): `.js`, `.jsx`, `.mjs`, `.cjs` files.
|
- `srcs` (label_list, optional): `.js`, `.jsx`, `.mjs`, `.cjs` files.
|
||||||
|
- `types` (label_list, optional): `.d.ts` files propagated to dependents.
|
||||||
|
- `data` (label_list, optional): runtime files propagated to dependents.
|
||||||
- `deps` (label_list, optional): dependent source libraries.
|
- `deps` (label_list, optional): dependent source libraries.
|
||||||
|
|
||||||
## ts_library
|
## ts_library
|
||||||
@@ -85,4 +130,6 @@ Aggregates TypeScript sources and transitive Bun source dependencies.
|
|||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
- `srcs` (label_list, optional): `.ts`, `.tsx` files.
|
- `srcs` (label_list, optional): `.ts`, `.tsx` files.
|
||||||
|
- `types` (label_list, optional): `.d.ts` files propagated to dependents.
|
||||||
|
- `data` (label_list, optional): runtime files propagated to dependents.
|
||||||
- `deps` (label_list, optional): dependent source libraries.
|
- `deps` (label_list, optional): dependent source libraries.
|
||||||
|
|||||||
80
flake.lock
generated
80
flake.lock
generated
@@ -1,28 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"devshell-lib": {
|
|
||||||
"inputs": {
|
|
||||||
"git-hooks": "git-hooks",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1772815059,
|
|
||||||
"narHash": "sha256-9Mn8t/a7b43omtmKRsF0HmFpCkNpTsvYEq0y85KLL5s=",
|
|
||||||
"ref": "v2.0.1",
|
|
||||||
"rev": "80cc529de7060e079d89a69d8daaf0347b53d8f9",
|
|
||||||
"revCount": 43,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.dgren.dev/eric/nix-flake-lib"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "v2.0.1",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.dgren.dev/eric/nix-flake-lib"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -43,7 +20,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat",
|
"flake-compat": "flake-compat",
|
||||||
"gitignore": "gitignore",
|
"gitignore": "gitignore",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772024342,
|
"lastModified": 1772024342,
|
||||||
@@ -62,7 +39,7 @@
|
|||||||
"gitignore": {
|
"gitignore": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"devshell-lib",
|
"repo-lib",
|
||||||
"git-hooks",
|
"git-hooks",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
@@ -82,6 +59,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1772542754,
|
||||||
|
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770073757,
|
"lastModified": 1770073757,
|
||||||
"narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=",
|
"narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=",
|
||||||
@@ -97,7 +90,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_3": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770107345,
|
"lastModified": 1770107345,
|
||||||
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
||||||
@@ -113,31 +106,38 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"repo-lib": {
|
||||||
|
"inputs": {
|
||||||
|
"git-hooks": "git-hooks",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772542754,
|
"lastModified": 1772866275,
|
||||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
"narHash": "sha256-lsJrFIbq6OO5wUC648VnvOmJm3qgJrlEugbdjeZsP34=",
|
||||||
"owner": "nixos",
|
"ref": "refs/tags/v3.0.0",
|
||||||
"repo": "nixpkgs",
|
"rev": "96d2d190466dddcb9e652c38b70152f09b9fcb05",
|
||||||
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
|
"revCount": 50,
|
||||||
"type": "github"
|
"type": "git",
|
||||||
|
"url": "https://git.dgren.dev/eric/nix-flake-lib"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"ref": "refs/tags/v3.0.0",
|
||||||
"ref": "nixos-unstable",
|
"type": "git",
|
||||||
"repo": "nixpkgs",
|
"url": "https://git.dgren.dev/eric/nix-flake-lib"
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"devshell-lib": "devshell-lib",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs": "nixpkgs_3"
|
"repo-lib": "repo-lib"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"treefmt-nix": {
|
"treefmt-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770228511,
|
"lastModified": 1770228511,
|
||||||
|
|||||||
200
flake.nix
200
flake.nix
@@ -3,143 +3,50 @@
|
|||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
devshell-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=v2.0.1";
|
repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.0.0";
|
||||||
devshell-lib.inputs.nixpkgs.follows = "nixpkgs";
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
devshell-lib,
|
repo-lib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
supportedSystems = [
|
bazelVersion = "9.0.0";
|
||||||
"x86_64-linux"
|
|
||||||
"aarch64-linux"
|
|
||||||
"x86_64-darwin"
|
|
||||||
"aarch64-darwin"
|
|
||||||
];
|
|
||||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
|
||||||
in
|
in
|
||||||
{
|
repo-lib.lib.mkRepo {
|
||||||
devShells = forAllSystems (
|
inherit self nixpkgs;
|
||||||
system:
|
src = ./.;
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
bazel9 = pkgs.writeShellScriptBin "bazel" ''
|
|
||||||
export USE_BAZEL_VERSION="''${USE_BAZEL_VERSION:-9.0.0}"
|
|
||||||
exec ${pkgs.bazelisk}/bin/bazelisk "$@"
|
|
||||||
'';
|
|
||||||
env = devshell-lib.lib.mkDevShell {
|
|
||||||
inherit system;
|
|
||||||
|
|
||||||
extraPackages = with pkgs; [
|
config = {
|
||||||
go
|
shell.extraShellText = ''
|
||||||
gopls
|
export USE_BAZEL_VERSION="''${USE_BAZEL_VERSION:-${bazelVersion}}"
|
||||||
gotools
|
export BUN_INSTALL="''${BUN_INSTALL:-$HOME/.bun}"
|
||||||
bun
|
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||||
bazel9
|
'';
|
||||||
bazel-buildtools
|
|
||||||
self.packages.${system}.release
|
|
||||||
];
|
|
||||||
|
|
||||||
features = {
|
formatting = {
|
||||||
oxfmt = false;
|
programs.shfmt.enable = true;
|
||||||
};
|
settings.shfmt.options = [
|
||||||
|
"-i"
|
||||||
|
"2"
|
||||||
|
"-s"
|
||||||
|
"-w"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
formatters = {
|
release = {
|
||||||
shfmt.enable = true;
|
steps = [
|
||||||
};
|
|
||||||
|
|
||||||
formatterSettings = {
|
|
||||||
shfmt.options = [
|
|
||||||
"-i"
|
|
||||||
"2"
|
|
||||||
"-s"
|
|
||||||
"-w"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
additionalHooks = {
|
|
||||||
tests = {
|
|
||||||
enable = true;
|
|
||||||
entry = ''
|
|
||||||
bazel test //tests/...
|
|
||||||
'';
|
|
||||||
pass_filenames = false;
|
|
||||||
stages = [ "pre-push" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
tools = [
|
|
||||||
{
|
|
||||||
name = "Bun";
|
|
||||||
bin = "${pkgs.bun}/bin/bun";
|
|
||||||
versionCmd = "--version";
|
|
||||||
color = "YELLOW";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "Go";
|
|
||||||
bin = "${pkgs.go}/bin/go";
|
|
||||||
versionCmd = "version";
|
|
||||||
color = "CYAN";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "Bazel";
|
|
||||||
bin = "${bazel9}/bin/bazel";
|
|
||||||
versionCmd = "--version";
|
|
||||||
color = "GREEN";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
extraShellHook = ''
|
|
||||||
export USE_BAZEL_VERSION="''${USE_BAZEL_VERSION:-9.0.0}"
|
|
||||||
export BUN_INSTALL="''${BUN_INSTALL:-$HOME/.bun}"
|
|
||||||
export PATH="$BUN_INSTALL/bin:$PATH"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = env.shell;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
checks = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
env = devshell-lib.lib.mkDevShell { inherit system; };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (env) pre-commit-check;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
formatter = forAllSystems (system: (devshell-lib.lib.mkDevShell { inherit system; }).formatter);
|
|
||||||
|
|
||||||
# Optional: release command (`release`)
|
|
||||||
#
|
|
||||||
# The release script always updates VERSION first, then:
|
|
||||||
# 1) runs release steps in order (file writes and scripts)
|
|
||||||
# 2) runs postVersion hook
|
|
||||||
# 3) formats, stages, commits, tags, and pushes
|
|
||||||
#
|
|
||||||
# Runtime env vars available in release.run/postVersion:
|
|
||||||
# BASE_VERSION, CHANNEL, PRERELEASE_NUM, FULL_VERSION, FULL_TAG
|
|
||||||
#
|
|
||||||
packages = forAllSystems (system: {
|
|
||||||
release = devshell-lib.lib.mkRelease {
|
|
||||||
inherit system;
|
|
||||||
|
|
||||||
release = [
|
|
||||||
{
|
{
|
||||||
run = ''
|
run.script = ''
|
||||||
sed -E -i 's#^([[:space:]]*version[[:space:]]*=[[:space:]]*")[^"]*(",)$#\1'"$FULL_VERSION"'\2#' "$ROOT_DIR/MODULE.bazel"
|
sed -E -i 's#^([[:space:]]*version[[:space:]]*=[[:space:]]*")[^"]*(",)$#\1'"$FULL_VERSION"'\2#' "$ROOT_DIR/MODULE.bazel"
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
run = ''
|
run.script = ''
|
||||||
README="$ROOT_DIR/README.md"
|
README="$ROOT_DIR/README.md"
|
||||||
TMP="$README.tmp"
|
TMP="$README.tmp"
|
||||||
|
|
||||||
@@ -174,8 +81,59 @@
|
|||||||
echo "Released $FULL_TAG"
|
echo "Released $FULL_TAG"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
});
|
};
|
||||||
|
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
bazel9 = pkgs.writeShellScriptBin "bazel" ''
|
||||||
|
export USE_BAZEL_VERSION="''${USE_BAZEL_VERSION:-${bazelVersion}}"
|
||||||
|
exec ${pkgs.bazelisk}/bin/bazelisk "$@"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
tools = [
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Bun";
|
||||||
|
package = pkgs.bun;
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
banner.color = "YELLOW";
|
||||||
|
})
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Go";
|
||||||
|
package = pkgs.go;
|
||||||
|
version.args = [ "version" ];
|
||||||
|
banner.color = "CYAN";
|
||||||
|
})
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Bazel";
|
||||||
|
package = bazel9;
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
banner.color = "GREEN";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
shell.packages = [
|
||||||
|
pkgs.gopls
|
||||||
|
pkgs.gotools
|
||||||
|
pkgs.bazel-buildtools
|
||||||
|
self.packages.${system}.release
|
||||||
|
];
|
||||||
|
|
||||||
|
checks.tests = {
|
||||||
|
command = "bazel test //tests/...";
|
||||||
|
stage = "pre-push";
|
||||||
|
passFilenames = false;
|
||||||
|
runtimeInputs = [
|
||||||
|
bazel9
|
||||||
|
pkgs.bun
|
||||||
|
pkgs.go
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ exports_files([
|
|||||||
"bun_install.bzl",
|
"bun_install.bzl",
|
||||||
"bun_script.bzl",
|
"bun_script.bzl",
|
||||||
"bun_test.bzl",
|
"bun_test.bzl",
|
||||||
|
"js_compat.bzl",
|
||||||
"js_library.bzl",
|
"js_library.bzl",
|
||||||
|
"js_run_devserver.bzl",
|
||||||
|
"workspace.bzl",
|
||||||
])
|
])
|
||||||
|
|
||||||
bzl_library(
|
bzl_library(
|
||||||
@@ -44,7 +47,32 @@ bzl_library(
|
|||||||
deps = [":js_library_bzl"],
|
deps = [":js_library_bzl"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "js_compat_bzl",
|
||||||
|
srcs = ["js_compat.bzl"],
|
||||||
|
deps = [
|
||||||
|
":bun_binary_bzl",
|
||||||
|
":bun_test_bzl",
|
||||||
|
":js_library_bzl",
|
||||||
|
":js_run_devserver_bzl",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
bzl_library(
|
bzl_library(
|
||||||
name = "js_library_bzl",
|
name = "js_library_bzl",
|
||||||
srcs = ["js_library.bzl"],
|
srcs = ["js_library.bzl"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "js_run_devserver_bzl",
|
||||||
|
srcs = ["js_run_devserver.bzl"],
|
||||||
|
deps = [
|
||||||
|
":js_library_bzl",
|
||||||
|
":workspace_bzl",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "workspace_bzl",
|
||||||
|
srcs = ["workspace.bzl"],
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,67 +1,55 @@
|
|||||||
"""Rule for running JS/TS scripts with Bun."""
|
"""Rule for running JS/TS scripts with Bun."""
|
||||||
|
|
||||||
|
load("//internal:js_library.bzl", "collect_js_runfiles")
|
||||||
|
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||||
|
|
||||||
|
def _shell_quote(value):
|
||||||
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||||
|
|
||||||
def _bun_binary_impl(ctx):
|
def _bun_binary_impl(ctx):
|
||||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||||
bun_bin = toolchain.bun.bun_bin
|
bun_bin = toolchain.bun.bun_bin
|
||||||
entry_point = ctx.file.entry_point
|
entry_point = ctx.file.entry_point
|
||||||
|
dep_runfiles = [collect_js_runfiles(dep) for dep in ctx.attr.deps]
|
||||||
|
workspace_info = create_bun_workspace_info(
|
||||||
|
ctx,
|
||||||
|
extra_files = ctx.files.data + [bun_bin],
|
||||||
|
primary_file = entry_point,
|
||||||
|
)
|
||||||
|
|
||||||
|
command = """
|
||||||
|
trap cleanup_runtime_workspace EXIT
|
||||||
|
cd "${runtime_exec_dir}"
|
||||||
|
exec "${bun_bin}" --bun run "${primary_source}" "$@"
|
||||||
|
"""
|
||||||
|
if ctx.attr.args:
|
||||||
|
command = """
|
||||||
|
trap cleanup_runtime_workspace EXIT
|
||||||
|
cd "${runtime_exec_dir}"
|
||||||
|
exec "${bun_bin}" --bun run "${primary_source}" __DEFAULT_ARGS__ "$@"
|
||||||
|
""".replace("__DEFAULT_ARGS__", " ".join([_shell_quote(arg) for arg in ctx.attr.args]))
|
||||||
|
|
||||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||||
ctx.actions.write(
|
ctx.actions.write(
|
||||||
output = launcher,
|
output = launcher,
|
||||||
is_executable = True,
|
is_executable = True,
|
||||||
content = """#!/usr/bin/env bash
|
content = render_workspace_setup(
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
runfiles_dir="${{RUNFILES_DIR:-$0.runfiles}}"
|
|
||||||
workspace_root="${{runfiles_dir}}/_main"
|
|
||||||
bun_bin="${{runfiles_dir}}/_main/{bun_short_path}"
|
|
||||||
entry_point="${{runfiles_dir}}/_main/{entry_short_path}"
|
|
||||||
|
|
||||||
resolve_entrypoint_workdir() {{
|
|
||||||
local dir
|
|
||||||
dir="$(dirname "${{entry_point}}")"
|
|
||||||
while [[ "${{dir}}" == "${{workspace_root}}"* ]]; do
|
|
||||||
if [[ -f "${{dir}}/.env" || -f "${{dir}}/package.json" ]]; then
|
|
||||||
echo "${{dir}}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ "${{dir}}" == "${{workspace_root}}" ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
dir="$(dirname "${{dir}}")"
|
|
||||||
done
|
|
||||||
echo "$(dirname "${{entry_point}}")"
|
|
||||||
}}
|
|
||||||
|
|
||||||
working_dir="{working_dir}"
|
|
||||||
if [[ "${{working_dir}}" == "entry_point" ]]; then
|
|
||||||
cd "$(resolve_entrypoint_workdir)"
|
|
||||||
else
|
|
||||||
cd "${{workspace_root}}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "${{bun_bin}}" --bun run "${{entry_point}}" "$@"
|
|
||||||
""".format(
|
|
||||||
bun_short_path = bun_bin.short_path,
|
bun_short_path = bun_bin.short_path,
|
||||||
entry_short_path = entry_point.short_path,
|
primary_source_short_path = entry_point.short_path,
|
||||||
working_dir = ctx.attr.working_dir,
|
working_dir_mode = ctx.attr.working_dir,
|
||||||
),
|
) + command,
|
||||||
)
|
|
||||||
|
|
||||||
transitive_files = []
|
|
||||||
if ctx.attr.node_modules:
|
|
||||||
transitive_files.append(ctx.attr.node_modules[DefaultInfo].files)
|
|
||||||
|
|
||||||
runfiles = ctx.runfiles(
|
|
||||||
files = [bun_bin, entry_point] + ctx.files.data,
|
|
||||||
transitive_files = depset(transitive = transitive_files),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
workspace_info,
|
||||||
DefaultInfo(
|
DefaultInfo(
|
||||||
executable = launcher,
|
executable = launcher,
|
||||||
runfiles = runfiles,
|
runfiles = workspace_runfiles(
|
||||||
|
ctx,
|
||||||
|
workspace_info,
|
||||||
|
direct_files = [launcher],
|
||||||
|
transitive_files = dep_runfiles,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -85,6 +73,9 @@ Use this rule for non-test scripts and CLIs that should run via `bazel run`.
|
|||||||
allow_files = True,
|
allow_files = True,
|
||||||
doc = "Additional runtime files required by the program.",
|
doc = "Additional runtime files required by the program.",
|
||||||
),
|
),
|
||||||
|
"deps": attr.label_list(
|
||||||
|
doc = "Library dependencies required by the program.",
|
||||||
|
),
|
||||||
"working_dir": attr.string(
|
"working_dir": attr.string(
|
||||||
default = "workspace",
|
default = "workspace",
|
||||||
values = ["workspace", "entry_point"],
|
values = ["workspace", "entry_point"],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Rule for bundling JS/TS sources with Bun."""
|
"""Rule for bundling JS/TS sources with Bun."""
|
||||||
|
|
||||||
load("//internal:js_library.bzl", "BunSourcesInfo")
|
load("//internal:js_library.bzl", "collect_js_sources")
|
||||||
|
|
||||||
|
|
||||||
def _output_name(target_name, entry):
|
def _output_name(target_name, entry):
|
||||||
@@ -16,10 +16,7 @@ def _bun_bundle_impl(ctx):
|
|||||||
if ctx.attr.node_modules:
|
if ctx.attr.node_modules:
|
||||||
transitive_inputs.append(ctx.attr.node_modules[DefaultInfo].files)
|
transitive_inputs.append(ctx.attr.node_modules[DefaultInfo].files)
|
||||||
for dep in ctx.attr.deps:
|
for dep in ctx.attr.deps:
|
||||||
if BunSourcesInfo in dep:
|
transitive_inputs.append(collect_js_sources(dep))
|
||||||
transitive_inputs.append(dep[BunSourcesInfo].transitive_sources)
|
|
||||||
else:
|
|
||||||
transitive_inputs.append(dep[DefaultInfo].files)
|
|
||||||
|
|
||||||
outputs = []
|
outputs = []
|
||||||
for entry in ctx.files.entry_points:
|
for entry in ctx.files.entry_points:
|
||||||
|
|||||||
@@ -1,101 +1,76 @@
|
|||||||
"""Rule for running JS/TS scripts with Bun in watch mode for development."""
|
"""Rule for running JS/TS scripts with Bun in watch mode for development."""
|
||||||
|
|
||||||
|
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||||
|
|
||||||
def _bun_dev_impl(ctx):
|
def _bun_dev_impl(ctx):
|
||||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||||
bun_bin = toolchain.bun.bun_bin
|
bun_bin = toolchain.bun.bun_bin
|
||||||
entry_point = ctx.file.entry_point
|
entry_point = ctx.file.entry_point
|
||||||
|
workspace_info = create_bun_workspace_info(
|
||||||
|
ctx,
|
||||||
|
extra_files = ctx.files.data + ctx.files.restart_on + [bun_bin],
|
||||||
|
primary_file = entry_point,
|
||||||
|
)
|
||||||
|
|
||||||
restart_watch_paths = "\n".join([path.short_path for path in ctx.files.restart_on])
|
restart_watch_paths = "\n".join([path.short_path for path in ctx.files.restart_on])
|
||||||
|
command = """
|
||||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
watch_mode="__WATCH_MODE__"
|
||||||
ctx.actions.write(
|
if [[ "${watch_mode}" == "hot" ]]; then
|
||||||
output = launcher,
|
|
||||||
is_executable = True,
|
|
||||||
content = """#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
runfiles_dir="${{RUNFILES_DIR:-$0.runfiles}}"
|
|
||||||
workspace_root="${{runfiles_dir}}/_main"
|
|
||||||
bun_bin="${{runfiles_dir}}/_main/{bun_short_path}"
|
|
||||||
entry_point="${{runfiles_dir}}/_main/{entry_short_path}"
|
|
||||||
|
|
||||||
resolve_entrypoint_workdir() {{
|
|
||||||
local dir
|
|
||||||
dir="$(dirname "${{entry_point}}")"
|
|
||||||
while [[ "${{dir}}" == "${{workspace_root}}"* ]]; do
|
|
||||||
if [[ -f "${{dir}}/.env" || -f "${{dir}}/package.json" ]]; then
|
|
||||||
echo "${{dir}}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
if [[ "${{dir}}" == "${{workspace_root}}" ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
dir="$(dirname "${{dir}}")"
|
|
||||||
done
|
|
||||||
echo "$(dirname "${{entry_point}}")"
|
|
||||||
}}
|
|
||||||
|
|
||||||
working_dir="{working_dir}"
|
|
||||||
if [[ "${{working_dir}}" == "entry_point" ]]; then
|
|
||||||
cd "$(resolve_entrypoint_workdir)"
|
|
||||||
else
|
|
||||||
cd "${{workspace_root}}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
watch_mode="{watch_mode}"
|
|
||||||
if [[ "${{watch_mode}}" == "hot" ]]; then
|
|
||||||
dev_flag="--hot"
|
dev_flag="--hot"
|
||||||
else
|
else
|
||||||
dev_flag="--watch"
|
dev_flag="--watch"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
run_dev() {{
|
if [[ __RESTART_COUNT__ -eq 0 ]]; then
|
||||||
exec "${{bun_bin}}" --bun "${{dev_flag}}" run "${{entry_point}}" "$@"
|
trap cleanup_runtime_workspace EXIT
|
||||||
}}
|
cd "${runtime_exec_dir}"
|
||||||
|
exec "${bun_bin}" --bun "${dev_flag}" run "${primary_source}" "$@"
|
||||||
if [[ {restart_count} -eq 0 ]]; then
|
|
||||||
run_dev "$@"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
readarray -t restart_paths <<'EOF_RESTART_PATHS'
|
readarray -t restart_paths <<'EOF_RESTART_PATHS'
|
||||||
{restart_watch_paths}
|
__RESTART_PATHS__
|
||||||
EOF_RESTART_PATHS
|
EOF_RESTART_PATHS
|
||||||
|
|
||||||
file_mtime() {{
|
file_mtime() {
|
||||||
local p="$1"
|
local path="$1"
|
||||||
if stat -f '%m' "${{p}}" >/dev/null 2>&1; then
|
if stat -f '%m' "${path}" >/dev/null 2>&1; then
|
||||||
stat -f '%m' "${{p}}"
|
stat -f '%m' "${path}"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
stat -c '%Y' "${{p}}"
|
stat -c '%Y' "${path}"
|
||||||
}}
|
}
|
||||||
|
|
||||||
declare -A mtimes
|
declare -A mtimes
|
||||||
for rel in "${{restart_paths[@]}}"; do
|
for rel in "${restart_paths[@]}"; do
|
||||||
path="${{runfiles_dir}}/_main/${{rel}}"
|
path="${runfiles_dir}/_main/${rel}"
|
||||||
if [[ -e "${{path}}" ]]; then
|
if [[ -e "${path}" ]]; then
|
||||||
mtimes["${{rel}}"]="$(file_mtime "${{path}}")"
|
mtimes["${rel}"]="$(file_mtime "${path}")"
|
||||||
else
|
else
|
||||||
mtimes["${{rel}}"]="missing"
|
mtimes["${rel}"]="missing"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
child_pid=""
|
child_pid=""
|
||||||
restart_child() {{
|
restart_child() {
|
||||||
if [[ -n "${{child_pid}}" ]] && kill -0 "${{child_pid}}" 2>/dev/null; then
|
if [[ -n "${child_pid}" ]] && kill -0 "${child_pid}" 2>/dev/null; then
|
||||||
kill "${{child_pid}}"
|
kill "${child_pid}"
|
||||||
wait "${{child_pid}}" || true
|
wait "${child_pid}" || true
|
||||||
fi
|
fi
|
||||||
"${{bun_bin}}" --bun "${{dev_flag}}" run "${{entry_point}}" "$@" &
|
|
||||||
child_pid=$!
|
|
||||||
}}
|
|
||||||
|
|
||||||
cleanup() {{
|
(
|
||||||
if [[ -n "${{child_pid}}" ]] && kill -0 "${{child_pid}}" 2>/dev/null; then
|
cd "${runtime_exec_dir}"
|
||||||
kill "${{child_pid}}"
|
exec "${bun_bin}" --bun "${dev_flag}" run "${primary_source}" "$@"
|
||||||
wait "${{child_pid}}" || true
|
) &
|
||||||
|
child_pid=$!
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [[ -n "${child_pid}" ]] && kill -0 "${child_pid}" 2>/dev/null; then
|
||||||
|
kill "${child_pid}"
|
||||||
|
wait "${child_pid}" || true
|
||||||
fi
|
fi
|
||||||
}}
|
cleanup_runtime_workspace
|
||||||
|
}
|
||||||
|
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
@@ -104,45 +79,46 @@ restart_child "$@"
|
|||||||
while true; do
|
while true; do
|
||||||
sleep 1
|
sleep 1
|
||||||
changed=0
|
changed=0
|
||||||
for rel in "${{restart_paths[@]}}"; do
|
for rel in "${restart_paths[@]}"; do
|
||||||
path="${{runfiles_dir}}/_main/${{rel}}"
|
path="${runfiles_dir}/_main/${rel}"
|
||||||
if [[ -e "${{path}}" ]]; then
|
if [[ -e "${path}" ]]; then
|
||||||
current="$(file_mtime "${{path}}")"
|
current="$(file_mtime "${path}")"
|
||||||
else
|
else
|
||||||
current="missing"
|
current="missing"
|
||||||
fi
|
fi
|
||||||
if [[ "${{current}}" != "${{mtimes[${{rel}}]}}" ]]; then
|
if [[ "${current}" != "${mtimes[${rel}]}" ]]; then
|
||||||
mtimes["${{rel}}"]="${{current}}"
|
mtimes["${rel}"]="${current}"
|
||||||
changed=1
|
changed=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [[ "${{changed}}" -eq 1 ]]; then
|
if [[ "${changed}" -eq 1 ]]; then
|
||||||
restart_child "$@"
|
restart_child "$@"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
""".format(
|
""".replace("__WATCH_MODE__", ctx.attr.watch_mode).replace(
|
||||||
bun_short_path = bun_bin.short_path,
|
"__RESTART_COUNT__",
|
||||||
entry_short_path = entry_point.short_path,
|
str(len(ctx.files.restart_on)),
|
||||||
watch_mode = ctx.attr.watch_mode,
|
).replace(
|
||||||
working_dir = ctx.attr.working_dir,
|
"__RESTART_PATHS__",
|
||||||
restart_count = len(ctx.files.restart_on),
|
restart_watch_paths,
|
||||||
restart_watch_paths = restart_watch_paths,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
transitive_files = []
|
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||||
if ctx.attr.node_modules:
|
ctx.actions.write(
|
||||||
transitive_files.append(ctx.attr.node_modules[DefaultInfo].files)
|
output = launcher,
|
||||||
|
is_executable = True,
|
||||||
runfiles = ctx.runfiles(
|
content = render_workspace_setup(
|
||||||
files = [bun_bin, entry_point] + ctx.files.data + ctx.files.restart_on,
|
bun_short_path = bun_bin.short_path,
|
||||||
transitive_files = depset(transitive = transitive_files),
|
primary_source_short_path = entry_point.short_path,
|
||||||
|
working_dir_mode = ctx.attr.working_dir,
|
||||||
|
) + command,
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
workspace_info,
|
||||||
DefaultInfo(
|
DefaultInfo(
|
||||||
executable = launcher,
|
executable = launcher,
|
||||||
runfiles = runfiles,
|
runfiles = workspace_runfiles(ctx, workspace_info, direct_files = [launcher]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ _DEFAULT_INSTALL_INPUTS = [
|
|||||||
"bunfig.toml",
|
"bunfig.toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_MANIFEST_DEP_FIELDS = [
|
||||||
|
"dependencies",
|
||||||
|
"devDependencies",
|
||||||
|
"optionalDependencies",
|
||||||
|
"peerDependencies",
|
||||||
|
]
|
||||||
|
|
||||||
def _normalize_path(path):
|
def _normalize_path(path):
|
||||||
normalized = path.replace("\\", "/")
|
normalized = path.replace("\\", "/")
|
||||||
if normalized.endswith("/") and normalized != "/":
|
if normalized.endswith("/") and normalized != "/":
|
||||||
@@ -115,6 +122,30 @@ def _validate_catalog_shape(field, value):
|
|||||||
def _copy_json_value(value):
|
def _copy_json_value(value):
|
||||||
return json.decode(json.encode(value))
|
return json.decode(json.encode(value))
|
||||||
|
|
||||||
|
def _package_target_name(package_name):
|
||||||
|
sanitized = package_name
|
||||||
|
sanitized = sanitized.replace("@", "at_")
|
||||||
|
sanitized = sanitized.replace("/", "_")
|
||||||
|
sanitized = sanitized.replace("-", "_")
|
||||||
|
sanitized = sanitized.replace(".", "_")
|
||||||
|
sanitized = sanitized.replace("__", "_").replace("__", "_").replace("__", "_")
|
||||||
|
sanitized = sanitized.strip("_")
|
||||||
|
if not sanitized:
|
||||||
|
sanitized = "package"
|
||||||
|
return "npm__" + sanitized
|
||||||
|
|
||||||
|
def _manifest_dependency_names(manifest):
|
||||||
|
names = {}
|
||||||
|
for field in _MANIFEST_DEP_FIELDS:
|
||||||
|
dependencies = manifest.get(field)
|
||||||
|
if dependencies == None:
|
||||||
|
continue
|
||||||
|
if type(dependencies) != type({}):
|
||||||
|
fail("bun_install: `{}` must be an object when present".format(field))
|
||||||
|
for name in dependencies.keys():
|
||||||
|
names[name] = True
|
||||||
|
return names
|
||||||
|
|
||||||
def _normalized_root_manifest(repository_ctx, package_json):
|
def _normalized_root_manifest(repository_ctx, package_json):
|
||||||
manifest = json.decode(repository_ctx.read(package_json))
|
manifest = json.decode(repository_ctx.read(package_json))
|
||||||
workspaces = manifest.get("workspaces")
|
workspaces = manifest.get("workspaces")
|
||||||
@@ -147,6 +178,7 @@ def _materialize_workspace_packages(repository_ctx, package_json):
|
|||||||
package_root = package_json.dirname
|
package_root = package_json.dirname
|
||||||
package_root_str = str(package_root)
|
package_root_str = str(package_root)
|
||||||
written = {}
|
written = {}
|
||||||
|
workspace_packages = {}
|
||||||
|
|
||||||
for pattern in _workspace_patterns(repository_ctx, package_json):
|
for pattern in _workspace_patterns(repository_ctx, package_json):
|
||||||
segments = pattern.split("/")
|
segments = pattern.split("/")
|
||||||
@@ -168,6 +200,15 @@ def _materialize_workspace_packages(repository_ctx, package_json):
|
|||||||
repository_ctx.read(workspace_package_json),
|
repository_ctx.read(workspace_package_json),
|
||||||
)
|
)
|
||||||
written[relative_dir] = True
|
written[relative_dir] = True
|
||||||
|
manifest = json.decode(repository_ctx.read(workspace_package_json))
|
||||||
|
package_name = manifest.get("name")
|
||||||
|
workspace_packages[relative_dir] = package_name if type(package_name) == type("") else ""
|
||||||
|
|
||||||
|
package_dirs = sorted(workspace_packages.keys())
|
||||||
|
return struct(
|
||||||
|
package_dirs = package_dirs,
|
||||||
|
package_names = [workspace_packages[package_dir] for package_dir in package_dirs if workspace_packages[package_dir]],
|
||||||
|
)
|
||||||
|
|
||||||
def _materialize_install_inputs(repository_ctx, package_json):
|
def _materialize_install_inputs(repository_ctx, package_json):
|
||||||
package_root = package_json.dirname
|
package_root = package_json.dirname
|
||||||
@@ -218,6 +259,68 @@ def _select_bun_binary(repository_ctx):
|
|||||||
|
|
||||||
fail("Unsupported host platform: os={}, arch={}".format(repository_ctx.os.name, repository_ctx.os.arch))
|
fail("Unsupported host platform: os={}, arch={}".format(repository_ctx.os.name, repository_ctx.os.arch))
|
||||||
|
|
||||||
|
def _render_package_targets_file(package_names):
|
||||||
|
lines = ["NPM_PACKAGE_TARGETS = {"]
|
||||||
|
for package_name in package_names:
|
||||||
|
lines.append(' "{}": "{}",'.format(package_name, _package_target_name(package_name)))
|
||||||
|
lines.extend([
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _render_repo_defs_bzl(repo_name):
|
||||||
|
return """load(":packages.bzl", "NPM_PACKAGE_TARGETS")
|
||||||
|
|
||||||
|
def package_target_name(package_name):
|
||||||
|
return NPM_PACKAGE_TARGETS.get(package_name)
|
||||||
|
|
||||||
|
def npm_link_all_packages(name = "node_modules", imported_links = []):
|
||||||
|
if not native.existing_rule(name):
|
||||||
|
native.alias(
|
||||||
|
name = name,
|
||||||
|
actual = "@{repo_name}//:node_modules",
|
||||||
|
)
|
||||||
|
|
||||||
|
requested = {{}}
|
||||||
|
for package_name in imported_links:
|
||||||
|
requested[package_name] = True
|
||||||
|
|
||||||
|
for package_name, target_name in NPM_PACKAGE_TARGETS.items():
|
||||||
|
if imported_links and package_name not in requested:
|
||||||
|
continue
|
||||||
|
if native.existing_rule(target_name):
|
||||||
|
continue
|
||||||
|
native.alias(
|
||||||
|
name = target_name,
|
||||||
|
actual = "@{repo_name}//:%s" % target_name,
|
||||||
|
)
|
||||||
|
""".format(repo_name = repo_name)
|
||||||
|
|
||||||
|
def _render_repo_build(package_names):
|
||||||
|
lines = [
|
||||||
|
'exports_files(["defs.bzl", "packages.bzl"])',
|
||||||
|
"",
|
||||||
|
"filegroup(",
|
||||||
|
' name = "node_modules",',
|
||||||
|
' srcs = glob(["**/node_modules/**"], allow_empty = False),',
|
||||||
|
' visibility = ["//visibility:public"],',
|
||||||
|
")",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
for package_name in package_names:
|
||||||
|
lines.extend([
|
||||||
|
"filegroup(",
|
||||||
|
' name = "{}",'.format(_package_target_name(package_name)),
|
||||||
|
' srcs = glob(["node_modules/{}/**"], allow_empty = True),'.format(package_name),
|
||||||
|
' visibility = ["//visibility:public"],',
|
||||||
|
")",
|
||||||
|
"",
|
||||||
|
])
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
def _bun_install_repository_impl(repository_ctx):
|
def _bun_install_repository_impl(repository_ctx):
|
||||||
package_json = repository_ctx.path(repository_ctx.attr.package_json)
|
package_json = repository_ctx.path(repository_ctx.attr.package_json)
|
||||||
bun_lockfile = repository_ctx.path(repository_ctx.attr.bun_lockfile)
|
bun_lockfile = repository_ctx.path(repository_ctx.attr.bun_lockfile)
|
||||||
@@ -230,6 +333,7 @@ def _bun_install_repository_impl(repository_ctx):
|
|||||||
|
|
||||||
bun_bin = _select_bun_binary(repository_ctx)
|
bun_bin = _select_bun_binary(repository_ctx)
|
||||||
lockfile_name = bun_lockfile.basename
|
lockfile_name = bun_lockfile.basename
|
||||||
|
root_manifest = json.decode(repository_ctx.read(package_json))
|
||||||
|
|
||||||
if lockfile_name not in ["bun.lock", "bun.lockb"]:
|
if lockfile_name not in ["bun.lock", "bun.lockb"]:
|
||||||
lockfile_name = "bun.lock"
|
lockfile_name = "bun.lock"
|
||||||
@@ -237,7 +341,7 @@ def _bun_install_repository_impl(repository_ctx):
|
|||||||
repository_ctx.file("package.json", _normalized_root_manifest(repository_ctx, package_json))
|
repository_ctx.file("package.json", _normalized_root_manifest(repository_ctx, package_json))
|
||||||
repository_ctx.symlink(bun_lockfile, lockfile_name)
|
repository_ctx.symlink(bun_lockfile, lockfile_name)
|
||||||
_materialize_install_inputs(repository_ctx, package_json)
|
_materialize_install_inputs(repository_ctx, package_json)
|
||||||
_materialize_workspace_packages(repository_ctx, package_json)
|
workspace_packages = _materialize_workspace_packages(repository_ctx, package_json)
|
||||||
|
|
||||||
install_args = [str(bun_bin), "--bun", "install", "--frozen-lockfile", "--no-progress"]
|
install_args = [str(bun_bin), "--bun", "install", "--frozen-lockfile", "--no-progress"]
|
||||||
if repository_ctx.attr.isolated_home:
|
if repository_ctx.attr.isolated_home:
|
||||||
@@ -263,15 +367,26 @@ stderr:
|
|||||||
""".format(result.stdout, result.stderr))
|
""".format(result.stdout, result.stderr))
|
||||||
|
|
||||||
repository_ctx.file(
|
repository_ctx.file(
|
||||||
"BUILD.bazel",
|
"node_modules/.rules_bun/install.json",
|
||||||
"""filegroup(
|
json.encode({
|
||||||
name = "node_modules",
|
"bun_lockfile": lockfile_name,
|
||||||
srcs = glob(["**/node_modules/**"], allow_empty = False),
|
"package_json": "package.json",
|
||||||
visibility = ["//visibility:public"],
|
"workspace_package_dirs": workspace_packages.package_dirs,
|
||||||
)
|
}) + "\n",
|
||||||
""",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
package_names = {}
|
||||||
|
for package_name in _manifest_dependency_names(root_manifest).keys():
|
||||||
|
package_names[package_name] = True
|
||||||
|
for package_name in workspace_packages.package_names:
|
||||||
|
package_names[package_name] = True
|
||||||
|
|
||||||
|
sorted_package_names = sorted(package_names.keys())
|
||||||
|
visible_repo_name = repository_ctx.attr.visible_repo_name or repository_ctx.name
|
||||||
|
repository_ctx.file("packages.bzl", _render_package_targets_file(sorted_package_names))
|
||||||
|
repository_ctx.file("defs.bzl", _render_repo_defs_bzl(visible_repo_name))
|
||||||
|
repository_ctx.file("BUILD.bazel", _render_repo_build(sorted_package_names))
|
||||||
|
|
||||||
bun_install_repository = repository_rule(
|
bun_install_repository = repository_rule(
|
||||||
implementation = _bun_install_repository_impl,
|
implementation = _bun_install_repository_impl,
|
||||||
attrs = {
|
attrs = {
|
||||||
@@ -279,6 +394,7 @@ bun_install_repository = repository_rule(
|
|||||||
"bun_lockfile": attr.label(mandatory = True, allow_single_file = True),
|
"bun_lockfile": attr.label(mandatory = True, allow_single_file = True),
|
||||||
"install_inputs": attr.label_list(allow_files = True),
|
"install_inputs": attr.label_list(allow_files = True),
|
||||||
"isolated_home": attr.bool(default = True),
|
"isolated_home": attr.bool(default = True),
|
||||||
|
"visible_repo_name": attr.string(),
|
||||||
"bun_linux_x64": attr.label(default = "@bun_linux_x64//:bun-linux-x64/bun", allow_single_file = True),
|
"bun_linux_x64": attr.label(default = "@bun_linux_x64//:bun-linux-x64/bun", allow_single_file = True),
|
||||||
"bun_linux_aarch64": attr.label(default = "@bun_linux_aarch64//:bun-linux-aarch64/bun", allow_single_file = True),
|
"bun_linux_aarch64": attr.label(default = "@bun_linux_aarch64//:bun-linux-aarch64/bun", allow_single_file = True),
|
||||||
"bun_darwin_x64": attr.label(default = "@bun_darwin_x64//:bun-darwin-x64/bun", allow_single_file = True),
|
"bun_darwin_x64": attr.label(default = "@bun_darwin_x64//:bun-darwin-x64/bun", allow_single_file = True),
|
||||||
@@ -313,4 +429,5 @@ def bun_install(name, package_json, bun_lockfile, install_inputs = [], isolated_
|
|||||||
bun_lockfile = bun_lockfile,
|
bun_lockfile = bun_lockfile,
|
||||||
install_inputs = install_inputs,
|
install_inputs = install_inputs,
|
||||||
isolated_home = isolated_home,
|
isolated_home = isolated_home,
|
||||||
|
visible_repo_name = name,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
"""Rule for running package.json scripts with Bun."""
|
"""Rule for running package.json scripts with Bun."""
|
||||||
|
|
||||||
|
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||||
|
|
||||||
def _shell_quote(value):
|
def _shell_quote(value):
|
||||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||||
@@ -9,302 +10,37 @@ def _bun_script_impl(ctx):
|
|||||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||||
bun_bin = toolchain.bun.bun_bin
|
bun_bin = toolchain.bun.bun_bin
|
||||||
package_json = ctx.file.package_json
|
package_json = ctx.file.package_json
|
||||||
|
workspace_info = create_bun_workspace_info(
|
||||||
|
ctx,
|
||||||
|
extra_files = ctx.files.data + [bun_bin],
|
||||||
|
package_dir_hint = package_json.dirname or ".",
|
||||||
|
package_json = package_json,
|
||||||
|
primary_file = package_json,
|
||||||
|
)
|
||||||
|
command = """
|
||||||
|
trap cleanup_runtime_workspace EXIT
|
||||||
|
cd "${runtime_exec_dir}"
|
||||||
|
exec "${bun_bin}" --bun run __SCRIPT__ "$@"
|
||||||
|
""".replace("__SCRIPT__", _shell_quote(ctx.attr.script))
|
||||||
|
|
||||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||||
ctx.actions.write(
|
ctx.actions.write(
|
||||||
output = launcher,
|
output = launcher,
|
||||||
is_executable = True,
|
is_executable = True,
|
||||||
content = """#!/usr/bin/env bash
|
content = render_workspace_setup(
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
runfiles_dir="${{RUNFILES_DIR:-$0.runfiles}}"
|
|
||||||
workspace_root="${{runfiles_dir}}/_main"
|
|
||||||
workspace_root="$(cd "${{workspace_root}}" && pwd -P)"
|
|
||||||
bun_bin="${{runfiles_dir}}/_main/{bun_short_path}"
|
|
||||||
package_json="${{runfiles_dir}}/_main/{package_json_short_path}"
|
|
||||||
package_dir="$(cd "$(dirname "${{package_json}}")" && pwd -P)"
|
|
||||||
package_rel_dir="{package_rel_dir}"
|
|
||||||
|
|
||||||
select_primary_node_modules() {{
|
|
||||||
local selected=""
|
|
||||||
local fallback=""
|
|
||||||
while IFS= read -r node_modules_dir; do
|
|
||||||
if [[ -z "${{fallback}}" ]]; then
|
|
||||||
fallback="${{node_modules_dir}}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -d "${{node_modules_dir}}/.bun" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${{node_modules_dir}}" != *"/runfiles/_main/"* ]]; then
|
|
||||||
selected="${{node_modules_dir}}"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${{selected}}" ]]; then
|
|
||||||
selected="${{node_modules_dir}}"
|
|
||||||
fi
|
|
||||||
done < <(find -L "${{runfiles_dir}}" -type d -name node_modules 2>/dev/null | sort)
|
|
||||||
|
|
||||||
if [[ -n "${{selected}}" ]]; then
|
|
||||||
echo "${{selected}}"
|
|
||||||
else
|
|
||||||
echo "${{fallback}}"
|
|
||||||
fi
|
|
||||||
}}
|
|
||||||
|
|
||||||
primary_node_modules="$(select_primary_node_modules)"
|
|
||||||
|
|
||||||
runtime_workspace="$(mktemp -d)"
|
|
||||||
cleanup_runtime_workspace() {{
|
|
||||||
rm -rf "${{runtime_workspace}}"
|
|
||||||
}}
|
|
||||||
trap cleanup_runtime_workspace EXIT
|
|
||||||
|
|
||||||
runtime_package_dir="${{runtime_workspace}}/${{package_rel_dir}}"
|
|
||||||
mkdir -p "${{runtime_package_dir}}"
|
|
||||||
cp -RL "${{package_dir}}/." "${{runtime_package_dir}}/"
|
|
||||||
|
|
||||||
workspace_package_map="${{runtime_workspace}}/workspace-packages.tsv"
|
|
||||||
python3 - "${{runtime_package_dir}}" >"${{workspace_package_map}}" <<'PY'
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
root = sys.argv[1]
|
|
||||||
|
|
||||||
for dirpath, dirnames, filenames in os.walk(root):
|
|
||||||
dirnames[:] = [name for name in dirnames if name != "node_modules"]
|
|
||||||
if "package.json" not in filenames:
|
|
||||||
continue
|
|
||||||
|
|
||||||
manifest_path = os.path.join(dirpath, "package.json")
|
|
||||||
try:
|
|
||||||
with open(manifest_path, "r", encoding="utf-8") as manifest_file:
|
|
||||||
package_name = json.load(manifest_file).get("name")
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(package_name, str):
|
|
||||||
print(f"{{package_name}}\t{{dirpath}}")
|
|
||||||
PY
|
|
||||||
|
|
||||||
install_repo_root=""
|
|
||||||
if [[ -n "${{primary_node_modules}}" ]]; then
|
|
||||||
install_repo_root="$(dirname "${{primary_node_modules}}")"
|
|
||||||
ln -s "${{primary_node_modules}}" "${{runtime_workspace}}/node_modules"
|
|
||||||
fi
|
|
||||||
|
|
||||||
workspace_package_dir_for_source() {{
|
|
||||||
local source="$1"
|
|
||||||
local manifest_path="${{source}}/package.json"
|
|
||||||
local package_name=""
|
|
||||||
local workspace_dir=""
|
|
||||||
|
|
||||||
if [[ ! -f "${{manifest_path}}" ]]; then
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
package_name="$(python3 - "${{manifest_path}}" <<'PY'
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(sys.argv[1], "r", encoding="utf-8") as manifest_file:
|
|
||||||
package_name = json.load(manifest_file).get("name", "")
|
|
||||||
except Exception:
|
|
||||||
package_name = ""
|
|
||||||
|
|
||||||
if isinstance(package_name, str):
|
|
||||||
print(package_name)
|
|
||||||
PY
|
|
||||||
)"
|
|
||||||
|
|
||||||
workspace_dir="$(awk -F '\t' -v name="$package_name" '$1 == name {{ print $2; exit }}' "${{workspace_package_map}}")"
|
|
||||||
if [[ -n "${{package_name}}" && -n "${{workspace_dir}}" ]]; then
|
|
||||||
echo "${{workspace_dir}}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}}
|
|
||||||
|
|
||||||
link_node_modules_entry() {{
|
|
||||||
local source="$1"
|
|
||||||
local destination="$2"
|
|
||||||
local workspace_target=""
|
|
||||||
|
|
||||||
rm -rf "${{destination}}"
|
|
||||||
workspace_target="$(workspace_package_dir_for_source "${{source}}" || true)"
|
|
||||||
if [[ -n "${{workspace_target}}" ]]; then
|
|
||||||
ln -s "${{workspace_target}}" "${{destination}}"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -L "${{source}}" ]]; then
|
|
||||||
ln -s "$(readlink "${{source}}")" "${{destination}}"
|
|
||||||
else
|
|
||||||
ln -s "${{source}}" "${{destination}}"
|
|
||||||
fi
|
|
||||||
}}
|
|
||||||
|
|
||||||
mirror_node_modules_dir() {{
|
|
||||||
local source_dir="$1"
|
|
||||||
local destination_dir="$2"
|
|
||||||
local entry=""
|
|
||||||
local scoped_entry=""
|
|
||||||
|
|
||||||
rm -rf "${{destination_dir}}"
|
|
||||||
mkdir -p "${{destination_dir}}"
|
|
||||||
|
|
||||||
shopt -s dotglob nullglob
|
|
||||||
for entry in "${{source_dir}}"/* "${{source_dir}}"/.[!.]* "${{source_dir}}"/..?*; do
|
|
||||||
local entry_name="$(basename "${{entry}}")"
|
|
||||||
if [[ "${{entry_name}}" == "." || "${{entry_name}}" == ".." ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "${{entry}}" && ! -L "${{entry}}" && "${{entry_name}}" == @* ]]; then
|
|
||||||
mkdir -p "${{destination_dir}}/${{entry_name}}"
|
|
||||||
for scoped_entry in "${{entry}}"/* "${{entry}}"/.[!.]* "${{entry}}"/..?*; do
|
|
||||||
local scoped_name="$(basename "${{scoped_entry}}")"
|
|
||||||
if [[ "${{scoped_name}}" == "." || "${{scoped_name}}" == ".." ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
link_node_modules_entry "${{scoped_entry}}" "${{destination_dir}}/${{entry_name}}/${{scoped_name}}"
|
|
||||||
done
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
link_node_modules_entry "${{entry}}" "${{destination_dir}}/${{entry_name}}"
|
|
||||||
done
|
|
||||||
shopt -u dotglob nullglob
|
|
||||||
}}
|
|
||||||
|
|
||||||
find_node_modules() {{
|
|
||||||
local dir="$1"
|
|
||||||
local root="$2"
|
|
||||||
|
|
||||||
while [[ "$dir" == "$root"* ]]; do
|
|
||||||
if [[ -d "$dir/node_modules" ]]; then
|
|
||||||
echo "$dir/node_modules"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$dir" == "$root" ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
dir="$(dirname "$dir")"
|
|
||||||
done
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}}
|
|
||||||
|
|
||||||
find_install_repo_node_modules() {{
|
|
||||||
local repo_root="$1"
|
|
||||||
local rel_dir="$2"
|
|
||||||
local candidate="${{rel_dir}}"
|
|
||||||
|
|
||||||
while [[ -n "${{candidate}}" ]]; do
|
|
||||||
if [[ -d "${{repo_root}}/${{candidate}}/node_modules" ]]; then
|
|
||||||
echo "${{repo_root}}/${{candidate}}/node_modules"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${{candidate}}" != */* ]]; then
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
|
|
||||||
candidate="${{candidate#*/}}"
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -d "${{repo_root}}/node_modules" ]]; then
|
|
||||||
echo "${{repo_root}}/node_modules"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}}
|
|
||||||
|
|
||||||
mirror_install_repo_workspace_node_modules() {{
|
|
||||||
local repo_root="$1"
|
|
||||||
local destination_root="$2"
|
|
||||||
|
|
||||||
while IFS= read -r install_node_modules; do
|
|
||||||
local rel_path="${{install_node_modules#${{repo_root}}/}}"
|
|
||||||
local destination="${{destination_root}}/${{rel_path}}"
|
|
||||||
|
|
||||||
mkdir -p "$(dirname "${{destination}}")"
|
|
||||||
mirror_node_modules_dir "${{install_node_modules}}" "${{destination}}"
|
|
||||||
done < <(find "${{repo_root}}" \
|
|
||||||
-path "${{repo_root}}/node_modules" -prune -o \
|
|
||||||
-type d -name node_modules -print 2>/dev/null | sort)
|
|
||||||
}}
|
|
||||||
|
|
||||||
resolved_install_node_modules=""
|
|
||||||
if [[ -n "${{install_repo_root}}" ]]; then
|
|
||||||
resolved_install_node_modules="$(find_install_repo_node_modules "${{install_repo_root}}" "${{package_rel_dir}}" || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${{resolved_install_node_modules}}" ]]; then
|
|
||||||
mirror_node_modules_dir "${{resolved_install_node_modules}}" "${{runtime_package_dir}}/node_modules"
|
|
||||||
else
|
|
||||||
resolved_node_modules="$(find_node_modules "${{runtime_package_dir}}" "${{runtime_workspace}}" || true)"
|
|
||||||
if [[ -n "${{resolved_node_modules}}" && "${{resolved_node_modules}}" != "${{runtime_package_dir}}/node_modules" ]]; then
|
|
||||||
mirror_node_modules_dir "${{resolved_node_modules}}" "${{runtime_package_dir}}/node_modules"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${{install_repo_root}}" ]]; then
|
|
||||||
mirror_install_repo_workspace_node_modules "${{install_repo_root}}" "${{runtime_package_dir}}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
path_entries=()
|
|
||||||
if [[ -d "${{runtime_package_dir}}/node_modules/.bin" ]]; then
|
|
||||||
path_entries+=("${{runtime_package_dir}}/node_modules/.bin")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "${{runtime_workspace}}/node_modules/.bin" && "${{runtime_workspace}}/node_modules/.bin" != "${{runtime_package_dir}}/node_modules/.bin" ]]; then
|
|
||||||
path_entries+=("${{runtime_workspace}}/node_modules/.bin")
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${{#path_entries[@]}} -gt 0 ]]; then
|
|
||||||
export PATH="$(IFS=:; echo "${{path_entries[*]}}"):${{PATH}}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
working_dir="{working_dir}"
|
|
||||||
if [[ "${{working_dir}}" == "package" ]]; then
|
|
||||||
cd "${{runtime_package_dir}}"
|
|
||||||
else
|
|
||||||
cd "${{runtime_workspace}}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
exec "${{bun_bin}}" --bun run {script} "$@"
|
|
||||||
""".format(
|
|
||||||
bun_short_path = bun_bin.short_path,
|
bun_short_path = bun_bin.short_path,
|
||||||
|
package_dir_hint = package_json.dirname or ".",
|
||||||
package_json_short_path = package_json.short_path,
|
package_json_short_path = package_json.short_path,
|
||||||
package_rel_dir = package_json.dirname,
|
primary_source_short_path = package_json.short_path,
|
||||||
working_dir = ctx.attr.working_dir,
|
working_dir_mode = ctx.attr.working_dir,
|
||||||
script = _shell_quote(ctx.attr.script),
|
) + command,
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
transitive_files = []
|
|
||||||
if ctx.attr.node_modules:
|
|
||||||
transitive_files.append(ctx.attr.node_modules[DefaultInfo].files)
|
|
||||||
|
|
||||||
runfiles = ctx.runfiles(
|
|
||||||
files = [bun_bin, package_json] + ctx.files.data,
|
|
||||||
transitive_files = depset(transitive = transitive_files),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
workspace_info,
|
||||||
DefaultInfo(
|
DefaultInfo(
|
||||||
executable = launcher,
|
executable = launcher,
|
||||||
runfiles = runfiles,
|
runfiles = workspace_runfiles(ctx, workspace_info, direct_files = [launcher]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Rule for running test suites with Bun."""
|
"""Rule for running test suites with Bun."""
|
||||||
|
|
||||||
load("//internal:js_library.bzl", "BunSourcesInfo")
|
load("//internal:js_library.bzl", "collect_js_runfiles")
|
||||||
|
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||||
|
|
||||||
|
|
||||||
def _shell_quote(value):
|
def _shell_quote(value):
|
||||||
@@ -10,53 +11,58 @@ def _shell_quote(value):
|
|||||||
def _bun_test_impl(ctx):
|
def _bun_test_impl(ctx):
|
||||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||||
bun_bin = toolchain.bun.bun_bin
|
bun_bin = toolchain.bun.bun_bin
|
||||||
|
primary_file = ctx.files.srcs[0]
|
||||||
|
dep_runfiles = [collect_js_runfiles(dep) for dep in ctx.attr.deps]
|
||||||
|
workspace_info = create_bun_workspace_info(
|
||||||
|
ctx,
|
||||||
|
extra_files = ctx.files.srcs + ctx.files.data + [bun_bin],
|
||||||
|
primary_file = primary_file,
|
||||||
|
)
|
||||||
|
|
||||||
src_args = " ".join([_shell_quote(src.short_path) for src in ctx.files.srcs])
|
src_args = " ".join([_shell_quote(src.short_path) for src in ctx.files.srcs])
|
||||||
|
command = """
|
||||||
|
trap cleanup_runtime_workspace EXIT
|
||||||
|
cd "${runtime_workspace}"
|
||||||
|
test_args=(__SRC_ARGS__)
|
||||||
|
|
||||||
|
if [[ -n "${TESTBRIDGE_TEST_ONLY:-}" && -n "${COVERAGE_DIR:-}" ]]; then
|
||||||
|
exec "${bun_bin}" --bun test "${test_args[@]}" --test-name-pattern "${TESTBRIDGE_TEST_ONLY}" --coverage "$@"
|
||||||
|
fi
|
||||||
|
if [[ -n "${TESTBRIDGE_TEST_ONLY:-}" ]]; then
|
||||||
|
exec "${bun_bin}" --bun test "${test_args[@]}" --test-name-pattern "${TESTBRIDGE_TEST_ONLY}" "$@"
|
||||||
|
fi
|
||||||
|
if [[ -n "${COVERAGE_DIR:-}" ]]; then
|
||||||
|
exec "${bun_bin}" --bun test "${test_args[@]}" --coverage "$@"
|
||||||
|
fi
|
||||||
|
exec "${bun_bin}" --bun test "${test_args[@]}" "$@"
|
||||||
|
""".replace("__SRC_ARGS__", src_args)
|
||||||
|
if ctx.attr.args:
|
||||||
|
default_args = "\n".join(['test_args+=({})'.format(_shell_quote(arg)) for arg in ctx.attr.args])
|
||||||
|
command = command.replace(
|
||||||
|
'test_args=(__SRC_ARGS__)',
|
||||||
|
'test_args=(__SRC_ARGS__)\n' + default_args,
|
||||||
|
)
|
||||||
|
|
||||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||||
ctx.actions.write(
|
ctx.actions.write(
|
||||||
output = launcher,
|
output = launcher,
|
||||||
is_executable = True,
|
is_executable = True,
|
||||||
content = """#!/usr/bin/env bash
|
content = render_workspace_setup(
|
||||||
set -euo pipefail
|
bun_short_path = bun_bin.short_path,
|
||||||
|
primary_source_short_path = primary_file.short_path,
|
||||||
runfiles_dir="${{RUNFILES_DIR:-$0.runfiles}}"
|
working_dir_mode = "workspace",
|
||||||
bun_bin="${{runfiles_dir}}/_main/{bun_short_path}"
|
) + command,
|
||||||
cd "${{runfiles_dir}}/_main"
|
|
||||||
|
|
||||||
if [[ -n "${{TESTBRIDGE_TEST_ONLY:-}}" && -n "${{COVERAGE_DIR:-}}" ]]; then
|
|
||||||
exec "${{bun_bin}}" --bun test {src_args} --test-name-pattern "${{TESTBRIDGE_TEST_ONLY}}" --coverage "$@"
|
|
||||||
fi
|
|
||||||
if [[ -n "${{TESTBRIDGE_TEST_ONLY:-}}" ]]; then
|
|
||||||
exec "${{bun_bin}}" --bun test {src_args} --test-name-pattern "${{TESTBRIDGE_TEST_ONLY}}" "$@"
|
|
||||||
fi
|
|
||||||
if [[ -n "${{COVERAGE_DIR:-}}" ]]; then
|
|
||||||
exec "${{bun_bin}}" --bun test {src_args} --coverage "$@"
|
|
||||||
fi
|
|
||||||
exec "${{bun_bin}}" --bun test {src_args} "$@"
|
|
||||||
""".format(
|
|
||||||
bun_short_path = bun_bin.short_path,
|
|
||||||
src_args = src_args,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
transitive_files = []
|
|
||||||
if ctx.attr.node_modules:
|
|
||||||
transitive_files.append(ctx.attr.node_modules[DefaultInfo].files)
|
|
||||||
for dep in ctx.attr.deps:
|
|
||||||
if BunSourcesInfo in dep:
|
|
||||||
transitive_files.append(dep[BunSourcesInfo].transitive_sources)
|
|
||||||
else:
|
|
||||||
transitive_files.append(dep[DefaultInfo].files)
|
|
||||||
|
|
||||||
runfiles = ctx.runfiles(
|
|
||||||
files = [bun_bin] + ctx.files.srcs + ctx.files.data,
|
|
||||||
transitive_files = depset(transitive = transitive_files),
|
|
||||||
)
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
workspace_info,
|
||||||
DefaultInfo(
|
DefaultInfo(
|
||||||
executable = launcher,
|
executable = launcher,
|
||||||
runfiles = runfiles,
|
runfiles = workspace_runfiles(
|
||||||
|
ctx,
|
||||||
|
workspace_info,
|
||||||
|
direct_files = [launcher],
|
||||||
|
transitive_files = dep_runfiles,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
29
internal/js_compat.bzl
Normal file
29
internal/js_compat.bzl
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"""rules_js-style compatibility exports backed by Bun."""
|
||||||
|
|
||||||
|
load("//internal:bun_binary.bzl", _bun_binary = "bun_binary")
|
||||||
|
load("//internal:bun_test.bzl", _bun_test = "bun_test")
|
||||||
|
load("//internal:js_library.bzl", _JsInfo = "JsInfo", _js_library = "js_library", _ts_library = "ts_library")
|
||||||
|
load("//internal:js_run_devserver.bzl", _js_run_devserver = "js_run_devserver")
|
||||||
|
|
||||||
|
JsInfo = _JsInfo
|
||||||
|
js_library = _js_library
|
||||||
|
ts_library = _ts_library
|
||||||
|
js_run_devserver = _js_run_devserver
|
||||||
|
|
||||||
|
def js_binary(name, **kwargs):
|
||||||
|
_bun_binary(name = name, **kwargs)
|
||||||
|
|
||||||
|
def js_test(name, entry_point = None, srcs = None, **kwargs):
|
||||||
|
if entry_point != None:
|
||||||
|
if srcs != None:
|
||||||
|
fail("js_test accepts either `entry_point` or `srcs`, but not both")
|
||||||
|
srcs = [entry_point]
|
||||||
|
|
||||||
|
if srcs == None:
|
||||||
|
fail("js_test requires `entry_point` or `srcs`")
|
||||||
|
|
||||||
|
_bun_test(
|
||||||
|
name = name,
|
||||||
|
srcs = srcs,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
@@ -1,23 +1,74 @@
|
|||||||
"""Lightweight JS/TS source grouping rules."""
|
"""Lightweight JS/TS source grouping rules."""
|
||||||
|
|
||||||
|
JsInfo = provider(
|
||||||
|
doc = "Provides transitive JavaScript/TypeScript metadata for Bun and JS compatibility rules.",
|
||||||
|
fields = {
|
||||||
|
"sources": "Direct source files owned by this target.",
|
||||||
|
"transitive_sources": "Transitive source files from this target and its deps.",
|
||||||
|
"types": "Direct type files owned by this target.",
|
||||||
|
"transitive_types": "Transitive type files from this target and its deps.",
|
||||||
|
"data_files": "Direct runtime data files owned by this target.",
|
||||||
|
"transitive_runfiles": "Transitive runtime files from this target and its deps.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
BunSourcesInfo = provider(
|
BunSourcesInfo = provider(
|
||||||
"Provides transitive sources for Bun libraries.",
|
"Provides transitive sources for Bun libraries.",
|
||||||
fields = ["transitive_sources"],
|
fields = ["transitive_sources"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def collect_js_sources(dep):
|
||||||
|
if JsInfo in dep:
|
||||||
|
return dep[JsInfo].transitive_sources
|
||||||
|
if BunSourcesInfo in dep:
|
||||||
|
return dep[BunSourcesInfo].transitive_sources
|
||||||
|
return dep[DefaultInfo].files
|
||||||
|
|
||||||
|
def collect_js_runfiles(dep):
|
||||||
|
if JsInfo in dep:
|
||||||
|
return dep[JsInfo].transitive_runfiles
|
||||||
|
if BunSourcesInfo in dep:
|
||||||
|
return dep[BunSourcesInfo].transitive_sources
|
||||||
|
return dep[DefaultInfo].files
|
||||||
|
|
||||||
def _bun_library_impl(ctx):
|
def _bun_library_impl(ctx):
|
||||||
transitive_sources = [
|
transitive_sources = [collect_js_sources(dep) for dep in ctx.attr.deps]
|
||||||
dep[BunSourcesInfo].transitive_sources
|
transitive_types = [
|
||||||
|
dep[JsInfo].transitive_types
|
||||||
for dep in ctx.attr.deps
|
for dep in ctx.attr.deps
|
||||||
if BunSourcesInfo in dep
|
if JsInfo in dep
|
||||||
]
|
]
|
||||||
|
transitive_runfiles = [collect_js_runfiles(dep) for dep in ctx.attr.deps]
|
||||||
|
|
||||||
all_sources = depset(
|
all_sources = depset(
|
||||||
direct = ctx.files.srcs,
|
direct = ctx.files.srcs,
|
||||||
transitive = transitive_sources,
|
transitive = transitive_sources,
|
||||||
)
|
)
|
||||||
|
all_types = depset(
|
||||||
|
direct = ctx.files.types,
|
||||||
|
transitive = transitive_types,
|
||||||
|
)
|
||||||
|
all_runfiles = depset(
|
||||||
|
direct = ctx.files.srcs + ctx.files.types + ctx.files.data,
|
||||||
|
transitive = transitive_runfiles,
|
||||||
|
)
|
||||||
|
default_files = depset(
|
||||||
|
direct = ctx.files.srcs + ctx.files.types + ctx.files.data,
|
||||||
|
transitive = transitive_sources + transitive_types + transitive_runfiles,
|
||||||
|
)
|
||||||
|
|
||||||
|
js_info = JsInfo(
|
||||||
|
sources = depset(ctx.files.srcs),
|
||||||
|
transitive_sources = all_sources,
|
||||||
|
types = depset(ctx.files.types),
|
||||||
|
transitive_types = all_types,
|
||||||
|
data_files = depset(ctx.files.data),
|
||||||
|
transitive_runfiles = all_runfiles,
|
||||||
|
)
|
||||||
return [
|
return [
|
||||||
|
js_info,
|
||||||
BunSourcesInfo(transitive_sources = all_sources),
|
BunSourcesInfo(transitive_sources = all_sources),
|
||||||
DefaultInfo(files = all_sources),
|
DefaultInfo(files = default_files),
|
||||||
]
|
]
|
||||||
|
|
||||||
js_library = rule(
|
js_library = rule(
|
||||||
@@ -28,6 +79,14 @@ js_library = rule(
|
|||||||
allow_files = [".js", ".jsx", ".mjs", ".cjs"],
|
allow_files = [".js", ".jsx", ".mjs", ".cjs"],
|
||||||
doc = "JavaScript source files in this library.",
|
doc = "JavaScript source files in this library.",
|
||||||
),
|
),
|
||||||
|
"types": attr.label_list(
|
||||||
|
allow_files = [".d.ts"],
|
||||||
|
doc = "Optional declaration files associated with this library.",
|
||||||
|
),
|
||||||
|
"data": attr.label_list(
|
||||||
|
allow_files = True,
|
||||||
|
doc = "Optional runtime files propagated to dependents.",
|
||||||
|
),
|
||||||
"deps": attr.label_list(
|
"deps": attr.label_list(
|
||||||
doc = "Other Bun source libraries to include transitively.",
|
doc = "Other Bun source libraries to include transitively.",
|
||||||
),
|
),
|
||||||
@@ -42,6 +101,14 @@ ts_library = rule(
|
|||||||
allow_files = [".ts", ".tsx"],
|
allow_files = [".ts", ".tsx"],
|
||||||
doc = "TypeScript source files in this library.",
|
doc = "TypeScript source files in this library.",
|
||||||
),
|
),
|
||||||
|
"types": attr.label_list(
|
||||||
|
allow_files = [".d.ts"],
|
||||||
|
doc = "Optional declaration files associated with this library.",
|
||||||
|
),
|
||||||
|
"data": attr.label_list(
|
||||||
|
allow_files = True,
|
||||||
|
doc = "Optional runtime files propagated to dependents.",
|
||||||
|
),
|
||||||
"deps": attr.label_list(
|
"deps": attr.label_list(
|
||||||
doc = "Other Bun source libraries to include transitively.",
|
doc = "Other Bun source libraries to include transitively.",
|
||||||
),
|
),
|
||||||
|
|||||||
100
internal/js_run_devserver.bzl
Normal file
100
internal/js_run_devserver.bzl
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""Compatibility rule for running an executable target as a dev server."""
|
||||||
|
|
||||||
|
load("//internal:js_library.bzl", "collect_js_runfiles")
|
||||||
|
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||||
|
|
||||||
|
def _shell_quote(value):
|
||||||
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||||
|
|
||||||
|
def _js_run_devserver_impl(ctx):
|
||||||
|
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||||
|
bun_bin = toolchain.bun.bun_bin
|
||||||
|
package_json = ctx.file.package_json
|
||||||
|
dep_runfiles = [collect_js_runfiles(dep) for dep in ctx.attr.deps]
|
||||||
|
tool_default_info = ctx.attr.tool[DefaultInfo]
|
||||||
|
|
||||||
|
workspace_info = create_bun_workspace_info(
|
||||||
|
ctx,
|
||||||
|
primary_file = package_json or tool_default_info.files_to_run.executable,
|
||||||
|
package_json = package_json,
|
||||||
|
package_dir_hint = ctx.attr.package_dir_hint,
|
||||||
|
extra_files = ctx.files.data + [bun_bin, tool_default_info.files_to_run.executable],
|
||||||
|
)
|
||||||
|
|
||||||
|
tool_workspace = ctx.attr.tool.label.workspace_name or "_main"
|
||||||
|
tool_path = "{}/{}".format(tool_workspace, tool_default_info.files_to_run.executable.short_path)
|
||||||
|
default_args = " ".join([_shell_quote(arg) for arg in ctx.attr.args])
|
||||||
|
|
||||||
|
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||||
|
ctx.actions.write(
|
||||||
|
output = launcher,
|
||||||
|
is_executable = True,
|
||||||
|
content = render_workspace_setup(
|
||||||
|
bun_short_path = bun_bin.short_path,
|
||||||
|
primary_source_short_path = package_json.short_path if package_json else tool_default_info.files_to_run.executable.short_path,
|
||||||
|
package_json_short_path = package_json.short_path if package_json else "",
|
||||||
|
package_dir_hint = ctx.attr.package_dir_hint,
|
||||||
|
working_dir_mode = ctx.attr.working_dir,
|
||||||
|
) + """
|
||||||
|
trap cleanup_runtime_workspace EXIT
|
||||||
|
cd "${runtime_exec_dir}"
|
||||||
|
tool="${runfiles_dir}/__TOOL_SHORT_PATH__"
|
||||||
|
exec "${tool}" __DEFAULT_ARGS__ "$@"
|
||||||
|
""".replace("__TOOL_SHORT_PATH__", tool_path).replace("__DEFAULT_ARGS__", default_args),
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
workspace_info,
|
||||||
|
DefaultInfo(
|
||||||
|
executable = launcher,
|
||||||
|
runfiles = workspace_runfiles(
|
||||||
|
ctx,
|
||||||
|
workspace_info,
|
||||||
|
direct_files = [launcher, tool_default_info.files_to_run.executable],
|
||||||
|
transitive_files = dep_runfiles,
|
||||||
|
).merge(tool_default_info.default_runfiles),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
js_run_devserver = rule(
|
||||||
|
implementation = _js_run_devserver_impl,
|
||||||
|
doc = """Runs an executable target from a staged JS workspace.
|
||||||
|
|
||||||
|
This is a Bun-backed compatibility adapter for `rules_js`-style devserver
|
||||||
|
targets. It stages the same runtime workspace as the Bun rules, then executes
|
||||||
|
the provided tool with any default arguments.
|
||||||
|
""",
|
||||||
|
attrs = {
|
||||||
|
"tool": attr.label(
|
||||||
|
mandatory = True,
|
||||||
|
executable = True,
|
||||||
|
cfg = "target",
|
||||||
|
doc = "Executable target to launch as the dev server.",
|
||||||
|
),
|
||||||
|
"package_json": attr.label(
|
||||||
|
allow_single_file = True,
|
||||||
|
doc = "Optional package.json used to resolve the package working directory.",
|
||||||
|
),
|
||||||
|
"package_dir_hint": attr.string(
|
||||||
|
default = ".",
|
||||||
|
doc = "Optional package-relative directory hint when package_json is not supplied.",
|
||||||
|
),
|
||||||
|
"node_modules": attr.label(
|
||||||
|
doc = "Optional label providing package files from a node_modules tree, typically produced by bun_install or npm_translate_lock, in runfiles.",
|
||||||
|
),
|
||||||
|
"deps": attr.label_list(
|
||||||
|
doc = "Library dependencies required by the dev server.",
|
||||||
|
),
|
||||||
|
"data": attr.label_list(
|
||||||
|
allow_files = True,
|
||||||
|
doc = "Additional runtime files required by the dev server.",
|
||||||
|
),
|
||||||
|
"working_dir": attr.string(
|
||||||
|
default = "workspace",
|
||||||
|
values = ["workspace", "package"],
|
||||||
|
doc = "Working directory at runtime: Bazel runfiles workspace root or the resolved package directory.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
executable = True,
|
||||||
|
toolchains = ["//bun:toolchain_type"],
|
||||||
|
)
|
||||||
649
internal/workspace.bzl
Normal file
649
internal/workspace.bzl
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
"""Shared Bun workspace metadata and launcher helpers."""
|
||||||
|
|
||||||
|
BunWorkspaceInfo = provider(
|
||||||
|
doc = "Workspace/runtime metadata shared by Bun rules and adapters.",
|
||||||
|
fields = {
|
||||||
|
"install_metadata_file": "Optional install metadata file from bun_install.",
|
||||||
|
"metadata_file": "Rule-local metadata file describing the staged workspace inputs.",
|
||||||
|
"node_modules_files": "Depset of node_modules files from bun_install.",
|
||||||
|
"package_dir_hint": "Package-relative directory when known at analysis time.",
|
||||||
|
"package_json": "Package manifest file when explicitly provided.",
|
||||||
|
"primary_file": "Primary source file used to resolve the runtime package context.",
|
||||||
|
"runtime_files": "Depset of runtime files required to stage the workspace.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
_WORKSPACE_SETUP_TEMPLATE = """#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
runfiles_dir="${RUNFILES_DIR:-$0.runfiles}"
|
||||||
|
workspace_root="${runfiles_dir}/_main"
|
||||||
|
workspace_root="$(cd "${workspace_root}" && pwd -P)"
|
||||||
|
bun_bin="${runfiles_dir}/_main/__BUN_SHORT_PATH__"
|
||||||
|
primary_source=""
|
||||||
|
if [[ -n "__PRIMARY_SOURCE_SHORT_PATH__" ]]; then
|
||||||
|
primary_source="${runfiles_dir}/_main/__PRIMARY_SOURCE_SHORT_PATH__"
|
||||||
|
fi
|
||||||
|
package_json=""
|
||||||
|
if [[ -n "__PACKAGE_JSON_SHORT_PATH__" ]]; then
|
||||||
|
package_json="${runfiles_dir}/_main/__PACKAGE_JSON_SHORT_PATH__"
|
||||||
|
fi
|
||||||
|
package_rel_dir_hint="__PACKAGE_DIR_HINT__"
|
||||||
|
working_dir_mode="__WORKING_DIR_MODE__"
|
||||||
|
|
||||||
|
normalize_rel_dir() {
|
||||||
|
local value="$1"
|
||||||
|
if [[ -z "${value}" || "${value}" == "." ]]; then
|
||||||
|
echo "."
|
||||||
|
else
|
||||||
|
echo "${value#./}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dirname_rel_dir() {
|
||||||
|
local value
|
||||||
|
value="$(normalize_rel_dir "$1")"
|
||||||
|
if [[ "${value}" == "." || "${value}" != */* ]]; then
|
||||||
|
echo "."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "${value%/*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
first_path_component() {
|
||||||
|
local value
|
||||||
|
value="$(normalize_rel_dir "$1")"
|
||||||
|
if [[ "${value}" == "." ]]; then
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "${value%%/*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
rel_dir_from_abs_path() {
|
||||||
|
local absolute_path="$1"
|
||||||
|
if [[ "${absolute_path}" == "${workspace_root}" ]]; then
|
||||||
|
echo "."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "${absolute_path#"${workspace_root}/"}"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_package_rel_dir_for_path() {
|
||||||
|
local path="$1"
|
||||||
|
local dir="$1"
|
||||||
|
if [[ -f "${dir}" ]]; then
|
||||||
|
dir="$(dirname "${dir}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [[ "${dir}" == "${workspace_root}"* ]]; do
|
||||||
|
if [[ -f "${dir}/package.json" ]]; then
|
||||||
|
rel_dir_from_abs_path "${dir}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "${dir}" == "${workspace_root}" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
dir="$(dirname "${dir}")"
|
||||||
|
done
|
||||||
|
|
||||||
|
rel_dir_from_abs_path "$(dirname "${path}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
find_working_rel_dir_for_path() {
|
||||||
|
local path="$1"
|
||||||
|
local dir="$1"
|
||||||
|
if [[ -f "${dir}" ]]; then
|
||||||
|
dir="$(dirname "${dir}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
while [[ "${dir}" == "${workspace_root}"* ]]; do
|
||||||
|
if [[ -f "${dir}/.env" || -f "${dir}/package.json" ]]; then
|
||||||
|
rel_dir_from_abs_path "${dir}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ "${dir}" == "${workspace_root}" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
dir="$(dirname "${dir}")"
|
||||||
|
done
|
||||||
|
|
||||||
|
rel_dir_from_abs_path "$(dirname "${path}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
select_primary_node_modules() {
|
||||||
|
local selected=""
|
||||||
|
local fallback=""
|
||||||
|
while IFS= read -r node_modules_dir; do
|
||||||
|
if [[ -z "${fallback}" ]]; then
|
||||||
|
fallback="${node_modules_dir}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d "${node_modules_dir}/.bun" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${node_modules_dir}" != *"/runfiles/_main/"* ]]; then
|
||||||
|
selected="${node_modules_dir}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${selected}" ]]; then
|
||||||
|
selected="${node_modules_dir}"
|
||||||
|
fi
|
||||||
|
done < <(find -L "${runfiles_dir}" -type d -name node_modules 2>/dev/null | sort)
|
||||||
|
|
||||||
|
if [[ -n "${selected}" ]]; then
|
||||||
|
echo "${selected}"
|
||||||
|
else
|
||||||
|
echo "${fallback}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
link_top_level_entries() {
|
||||||
|
local source_root="$1"
|
||||||
|
local destination_root="$2"
|
||||||
|
local skipped_entry="$3"
|
||||||
|
local entry=""
|
||||||
|
local entry_name=""
|
||||||
|
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
for entry in "${source_root}"/* "${source_root}"/.[!.]* "${source_root}"/..?*; do
|
||||||
|
entry_name="$(basename "${entry}")"
|
||||||
|
if [[ "${entry_name}" == "." || "${entry_name}" == ".." ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ -n "${skipped_entry}" && "${entry_name}" == "${skipped_entry}" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
ln -s "${entry}" "${destination_root}/${entry_name}"
|
||||||
|
done
|
||||||
|
shopt -u dotglob nullglob
|
||||||
|
}
|
||||||
|
|
||||||
|
materialize_package_path() {
|
||||||
|
local source_root="$1"
|
||||||
|
local destination_root="$2"
|
||||||
|
local package_rel_dir
|
||||||
|
package_rel_dir="$(normalize_rel_dir "$3")"
|
||||||
|
|
||||||
|
if [[ "${package_rel_dir}" == "." ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local source_cursor="${source_root}"
|
||||||
|
local destination_cursor="${destination_root}"
|
||||||
|
local parts=()
|
||||||
|
local current="${package_rel_dir}"
|
||||||
|
|
||||||
|
while [[ -n "${current}" ]]; do
|
||||||
|
if [[ "${current}" == */* ]]; then
|
||||||
|
parts+=("${current%%/*}")
|
||||||
|
current="${current#*/}"
|
||||||
|
else
|
||||||
|
parts+=("${current}")
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
local index=0
|
||||||
|
while [[ ${index} -lt $((${#parts[@]} - 1)) ]]; do
|
||||||
|
local part="${parts[${index}]}"
|
||||||
|
local next_part="${parts[$((index + 1))]}"
|
||||||
|
source_cursor="${source_cursor}/${part}"
|
||||||
|
destination_cursor="${destination_cursor}/${part}"
|
||||||
|
mkdir -p "${destination_cursor}"
|
||||||
|
|
||||||
|
local sibling=""
|
||||||
|
local sibling_name=""
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
for sibling in "${source_cursor}"/* "${source_cursor}"/.[!.]* "${source_cursor}"/..?*; do
|
||||||
|
sibling_name="$(basename "${sibling}")"
|
||||||
|
if [[ "${sibling_name}" == "." || "${sibling_name}" == ".." || "${sibling_name}" == "${next_part}" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [[ ! -e "${destination_cursor}/${sibling_name}" ]]; then
|
||||||
|
ln -s "${sibling}" "${destination_cursor}/${sibling_name}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
shopt -u dotglob nullglob
|
||||||
|
index=$((index + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p "${destination_root}/${package_rel_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
materialize_directory_entries() {
|
||||||
|
local source_root="$1"
|
||||||
|
local destination_root="$2"
|
||||||
|
local entry=""
|
||||||
|
local entry_name=""
|
||||||
|
|
||||||
|
mkdir -p "${destination_root}"
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
for entry in "${source_root}"/* "${source_root}"/.[!.]* "${source_root}"/..?*; do
|
||||||
|
entry_name="$(basename "${entry}")"
|
||||||
|
if [[ "${entry_name}" == "." || "${entry_name}" == ".." ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
rm -rf "${destination_root}/${entry_name}"
|
||||||
|
ln -s "${entry}" "${destination_root}/${entry_name}"
|
||||||
|
done
|
||||||
|
shopt -u dotglob nullglob
|
||||||
|
}
|
||||||
|
|
||||||
|
stage_workspace_view() {
|
||||||
|
local source_root="$1"
|
||||||
|
local destination_root="$2"
|
||||||
|
local package_rel_dir
|
||||||
|
package_rel_dir="$(normalize_rel_dir "$3")"
|
||||||
|
local skipped_entry
|
||||||
|
skipped_entry="$(first_path_component "${package_rel_dir}")"
|
||||||
|
|
||||||
|
link_top_level_entries "${source_root}" "${destination_root}" "${skipped_entry}"
|
||||||
|
|
||||||
|
if [[ "${package_rel_dir}" == "." ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
materialize_package_path "${source_root}" "${destination_root}" "${package_rel_dir}"
|
||||||
|
materialize_directory_entries "${source_root}/${package_rel_dir}" "${destination_root}/${package_rel_dir}"
|
||||||
|
}
|
||||||
|
|
||||||
|
build_workspace_package_map() {
|
||||||
|
local root="$1"
|
||||||
|
local out="$2"
|
||||||
|
|
||||||
|
python3 - "${root}" >"${out}" <<'PY'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
root = os.path.abspath(sys.argv[1])
|
||||||
|
|
||||||
|
for dirpath, dirnames, filenames in os.walk(root):
|
||||||
|
dirnames[:] = [name for name in dirnames if name != "node_modules"]
|
||||||
|
if "package.json" not in filenames:
|
||||||
|
continue
|
||||||
|
|
||||||
|
manifest_path = os.path.join(dirpath, "package.json")
|
||||||
|
try:
|
||||||
|
with open(manifest_path, "r", encoding="utf-8") as manifest_file:
|
||||||
|
package_name = json.load(manifest_file).get("name")
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not isinstance(package_name, str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
rel_dir = os.path.relpath(dirpath, root)
|
||||||
|
if rel_dir == ".":
|
||||||
|
rel_dir = "."
|
||||||
|
print(f"{package_name}\t{rel_dir}")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace_package_rel_dir_for_source() {
|
||||||
|
local source="$1"
|
||||||
|
local manifest_path="${source}/package.json"
|
||||||
|
local package_name=""
|
||||||
|
|
||||||
|
if [[ ! -f "${manifest_path}" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
package_name="$(python3 - "${manifest_path}" <<'PY'
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(sys.argv[1], "r", encoding="utf-8") as manifest_file:
|
||||||
|
package_name = json.load(manifest_file).get("name", "")
|
||||||
|
except Exception:
|
||||||
|
package_name = ""
|
||||||
|
|
||||||
|
if isinstance(package_name, str):
|
||||||
|
print(package_name)
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "${package_name}" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk -F '\t' -v name="${package_name}" '$1 == name { print $2; exit }' "${workspace_package_map}"
|
||||||
|
}
|
||||||
|
|
||||||
|
link_node_modules_entry() {
|
||||||
|
local source="$1"
|
||||||
|
local destination="$2"
|
||||||
|
local workspace_rel_dir=""
|
||||||
|
|
||||||
|
rm -rf "${destination}"
|
||||||
|
workspace_rel_dir="$(workspace_package_rel_dir_for_source "${source}" || true)"
|
||||||
|
if [[ -n "${workspace_rel_dir}" ]]; then
|
||||||
|
ln -s "${runtime_workspace}/${workspace_rel_dir}" "${destination}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -L "${source}" ]]; then
|
||||||
|
ln -s "$(readlink "${source}")" "${destination}"
|
||||||
|
else
|
||||||
|
ln -s "${source}" "${destination}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror_node_modules_dir() {
|
||||||
|
local source_dir="$1"
|
||||||
|
local destination_dir="$2"
|
||||||
|
local entry=""
|
||||||
|
local entry_name=""
|
||||||
|
local scoped_entry=""
|
||||||
|
local scoped_name=""
|
||||||
|
|
||||||
|
rm -rf "${destination_dir}"
|
||||||
|
mkdir -p "${destination_dir}"
|
||||||
|
|
||||||
|
shopt -s dotglob nullglob
|
||||||
|
for entry in "${source_dir}"/* "${source_dir}"/.[!.]* "${source_dir}"/..?*; do
|
||||||
|
entry_name="$(basename "${entry}")"
|
||||||
|
if [[ "${entry_name}" == "." || "${entry_name}" == ".." || "${entry_name}" == ".rules_bun" ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "${entry}" && ! -L "${entry}" && "${entry_name}" == @* ]]; then
|
||||||
|
mkdir -p "${destination_dir}/${entry_name}"
|
||||||
|
for scoped_entry in "${entry}"/* "${entry}"/.[!.]* "${entry}"/..?*; do
|
||||||
|
scoped_name="$(basename "${scoped_entry}")"
|
||||||
|
if [[ "${scoped_name}" == "." || "${scoped_name}" == ".." ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
link_node_modules_entry "${scoped_entry}" "${destination_dir}/${entry_name}/${scoped_name}"
|
||||||
|
done
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
link_node_modules_entry "${entry}" "${destination_dir}/${entry_name}"
|
||||||
|
done
|
||||||
|
shopt -u dotglob nullglob
|
||||||
|
}
|
||||||
|
|
||||||
|
find_install_repo_node_modules() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local package_rel_dir
|
||||||
|
package_rel_dir="$(normalize_rel_dir "$2")"
|
||||||
|
|
||||||
|
if [[ "${package_rel_dir}" != "." ]]; then
|
||||||
|
local candidate="${package_rel_dir}"
|
||||||
|
while true; do
|
||||||
|
if [[ -d "${repo_root}/${candidate}/node_modules" ]]; then
|
||||||
|
echo "${repo_root}/${candidate}/node_modules"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${candidate}" != */* ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
candidate="${candidate%/*}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -d "${repo_root}/node_modules" ]]; then
|
||||||
|
echo "${repo_root}/node_modules"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror_install_repo_workspace_node_modules() {
|
||||||
|
local repo_root="$1"
|
||||||
|
local destination_root="$2"
|
||||||
|
|
||||||
|
while IFS= read -r install_node_modules; do
|
||||||
|
local rel_path="${install_node_modules#${repo_root}/}"
|
||||||
|
local destination="${destination_root}/${rel_path}"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${destination}")"
|
||||||
|
mirror_node_modules_dir "${install_node_modules}" "${destination}"
|
||||||
|
done < <(find "${repo_root}" \
|
||||||
|
-path "${repo_root}/node_modules" -prune -o \
|
||||||
|
-type d -name node_modules -print 2>/dev/null | sort)
|
||||||
|
}
|
||||||
|
|
||||||
|
build_runtime_path() {
|
||||||
|
local workspace_dir="$1"
|
||||||
|
local package_dir="$2"
|
||||||
|
local entries=()
|
||||||
|
|
||||||
|
if [[ -d "${package_dir}/node_modules/.bin" ]]; then
|
||||||
|
entries+=("${package_dir}/node_modules/.bin")
|
||||||
|
fi
|
||||||
|
if [[ -d "${workspace_dir}/node_modules/.bin" && "${workspace_dir}/node_modules/.bin" != "${package_dir}/node_modules/.bin" ]]; then
|
||||||
|
entries+=("${workspace_dir}/node_modules/.bin")
|
||||||
|
fi
|
||||||
|
if [[ -n "${PATH:-}" ]]; then
|
||||||
|
entries+=("${PATH}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ${#entries[@]} -eq 0 ]]; then
|
||||||
|
echo ""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local path_value=""
|
||||||
|
local entry=""
|
||||||
|
for entry in "${entries[@]}"; do
|
||||||
|
if [[ -z "${path_value}" ]]; then
|
||||||
|
path_value="${entry}"
|
||||||
|
else
|
||||||
|
path_value="${path_value}:${entry}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "${path_value}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_package_rel_dir() {
|
||||||
|
if [[ -n "${package_rel_dir_hint}" && "${package_rel_dir_hint}" != "." ]]; then
|
||||||
|
normalize_rel_dir "${package_rel_dir_hint}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${package_json}" ]]; then
|
||||||
|
find_package_rel_dir_for_path "${package_json}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [[ -n "${primary_source}" ]]; then
|
||||||
|
find_package_rel_dir_for_path "${primary_source}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "."
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_execution_rel_dir() {
|
||||||
|
local package_rel_dir="$1"
|
||||||
|
case "${working_dir_mode}" in
|
||||||
|
workspace)
|
||||||
|
echo "."
|
||||||
|
;;
|
||||||
|
package)
|
||||||
|
echo "${package_rel_dir}"
|
||||||
|
;;
|
||||||
|
entry_point)
|
||||||
|
if [[ -n "${primary_source}" ]]; then
|
||||||
|
find_working_rel_dir_for_path "${primary_source}"
|
||||||
|
else
|
||||||
|
echo "${package_rel_dir}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "${package_rel_dir}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
package_rel_dir="$(resolve_package_rel_dir)"
|
||||||
|
execution_rel_dir="$(resolve_execution_rel_dir "${package_rel_dir}")"
|
||||||
|
|
||||||
|
runtime_workspace="$(mktemp -d)"
|
||||||
|
cleanup_runtime_workspace() {
|
||||||
|
rm -rf "${runtime_workspace}"
|
||||||
|
}
|
||||||
|
|
||||||
|
stage_workspace_view "${workspace_root}" "${runtime_workspace}" "${package_rel_dir}"
|
||||||
|
runtime_package_dir="${runtime_workspace}"
|
||||||
|
if [[ "${package_rel_dir}" != "." ]]; then
|
||||||
|
runtime_package_dir="${runtime_workspace}/${package_rel_dir}"
|
||||||
|
fi
|
||||||
|
runtime_exec_dir="${runtime_workspace}"
|
||||||
|
if [[ "${execution_rel_dir}" != "." ]]; then
|
||||||
|
runtime_exec_dir="${runtime_workspace}/${execution_rel_dir}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
workspace_package_map="${runtime_workspace}/.rules_bun_workspace_packages.tsv"
|
||||||
|
build_workspace_package_map "${runtime_workspace}" "${workspace_package_map}"
|
||||||
|
|
||||||
|
primary_node_modules="$(select_primary_node_modules)"
|
||||||
|
install_repo_root=""
|
||||||
|
if [[ -n "${primary_node_modules}" ]]; then
|
||||||
|
install_repo_root="$(dirname "${primary_node_modules}")"
|
||||||
|
mirror_node_modules_dir "${primary_node_modules}" "${runtime_workspace}/node_modules"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${install_repo_root}" ]]; then
|
||||||
|
resolved_install_node_modules="$(find_install_repo_node_modules "${install_repo_root}" "${package_rel_dir}" || true)"
|
||||||
|
if [[ -n "${resolved_install_node_modules}" && "${resolved_install_node_modules}" != "${install_repo_root}/node_modules" ]]; then
|
||||||
|
mirror_node_modules_dir "${resolved_install_node_modules}" "${runtime_package_dir}/node_modules"
|
||||||
|
fi
|
||||||
|
mirror_install_repo_workspace_node_modules "${install_repo_root}" "${runtime_workspace}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "${runtime_package_dir}/node_modules" && -e "${runtime_workspace}/node_modules" && "${runtime_package_dir}" != "${runtime_workspace}" ]]; then
|
||||||
|
ln -s "${runtime_workspace}/node_modules" "${runtime_package_dir}/node_modules"
|
||||||
|
fi
|
||||||
|
|
||||||
|
runtime_path="$(build_runtime_path "${runtime_workspace}" "${runtime_package_dir}")"
|
||||||
|
if [[ -n "${runtime_path}" ]]; then
|
||||||
|
export PATH="${runtime_path}"
|
||||||
|
fi
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _shell_quote(value):
|
||||||
|
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||||
|
|
||||||
|
def _dirname(path):
|
||||||
|
if not path or path == ".":
|
||||||
|
return "."
|
||||||
|
|
||||||
|
index = path.rfind("/")
|
||||||
|
if index < 0:
|
||||||
|
return "."
|
||||||
|
if index == 0:
|
||||||
|
return "/"
|
||||||
|
return path[:index]
|
||||||
|
|
||||||
|
def find_install_metadata_file(files):
|
||||||
|
for file in files:
|
||||||
|
if file.short_path.endswith("node_modules/.rules_bun/install.json"):
|
||||||
|
return file
|
||||||
|
return None
|
||||||
|
|
||||||
|
def resolve_node_modules_roots(files, workspace_dir = ""):
|
||||||
|
install_metadata_file = find_install_metadata_file(files)
|
||||||
|
shared_node_modules_root = None
|
||||||
|
workspace_node_modules_root = None
|
||||||
|
|
||||||
|
if install_metadata_file:
|
||||||
|
shared_node_modules_root = _dirname(_dirname(install_metadata_file.path))
|
||||||
|
|
||||||
|
workspace_marker = ""
|
||||||
|
if workspace_dir:
|
||||||
|
workspace_marker = "/%s/node_modules/" % workspace_dir.strip("/")
|
||||||
|
|
||||||
|
shortest_path = None
|
||||||
|
for src in files:
|
||||||
|
if workspace_marker and workspace_marker in src.path and workspace_node_modules_root == None:
|
||||||
|
workspace_node_modules_root = src.path[:src.path.find(workspace_marker) + len(workspace_marker) - 1]
|
||||||
|
if shortest_path == None or len(src.path) < len(shortest_path):
|
||||||
|
shortest_path = src.path
|
||||||
|
|
||||||
|
if shared_node_modules_root == None and shortest_path:
|
||||||
|
marker = "/node_modules/"
|
||||||
|
marker_index = shortest_path.find(marker)
|
||||||
|
if marker_index >= 0:
|
||||||
|
shared_node_modules_root = shortest_path[:marker_index + len("/node_modules")]
|
||||||
|
|
||||||
|
return struct(
|
||||||
|
install_metadata_file = install_metadata_file,
|
||||||
|
node_modules_root = workspace_node_modules_root or shared_node_modules_root,
|
||||||
|
shared_node_modules_root = shared_node_modules_root,
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_bun_workspace_info(ctx, primary_file = None, package_json = None, package_dir_hint = ".", extra_files = None):
|
||||||
|
direct_runtime_files = []
|
||||||
|
if primary_file:
|
||||||
|
direct_runtime_files.append(primary_file)
|
||||||
|
if package_json and package_json != primary_file:
|
||||||
|
direct_runtime_files.append(package_json)
|
||||||
|
direct_runtime_files.extend(extra_files or [])
|
||||||
|
|
||||||
|
node_modules_files = depset()
|
||||||
|
install_metadata_file = None
|
||||||
|
if getattr(ctx.attr, "node_modules", None):
|
||||||
|
node_modules_files = ctx.attr.node_modules[DefaultInfo].files
|
||||||
|
install_metadata_file = find_install_metadata_file(node_modules_files.to_list())
|
||||||
|
|
||||||
|
metadata_file = ctx.actions.declare_file(ctx.label.name + ".bun_workspace.json")
|
||||||
|
ctx.actions.write(
|
||||||
|
output = metadata_file,
|
||||||
|
content = json.encode({
|
||||||
|
"install_metadata": install_metadata_file.short_path if install_metadata_file else "",
|
||||||
|
"package_dir_hint": package_dir_hint or ".",
|
||||||
|
"package_json": package_json.short_path if package_json else "",
|
||||||
|
"primary_file": primary_file.short_path if primary_file else "",
|
||||||
|
}) + "\n",
|
||||||
|
)
|
||||||
|
direct_runtime_files.append(metadata_file)
|
||||||
|
|
||||||
|
runtime_files = depset(
|
||||||
|
direct = direct_runtime_files,
|
||||||
|
transitive = [node_modules_files],
|
||||||
|
)
|
||||||
|
|
||||||
|
return BunWorkspaceInfo(
|
||||||
|
install_metadata_file = install_metadata_file,
|
||||||
|
metadata_file = metadata_file,
|
||||||
|
node_modules_files = node_modules_files,
|
||||||
|
package_dir_hint = package_dir_hint or ".",
|
||||||
|
package_json = package_json,
|
||||||
|
primary_file = primary_file,
|
||||||
|
runtime_files = runtime_files,
|
||||||
|
)
|
||||||
|
|
||||||
|
def workspace_runfiles(ctx, workspace_info, direct_files = None, transitive_files = None):
|
||||||
|
return ctx.runfiles(
|
||||||
|
files = direct_files or [],
|
||||||
|
transitive_files = depset(
|
||||||
|
transitive = [workspace_info.runtime_files] + (transitive_files or []),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def render_workspace_setup(
|
||||||
|
bun_short_path,
|
||||||
|
working_dir_mode,
|
||||||
|
primary_source_short_path = "",
|
||||||
|
package_json_short_path = "",
|
||||||
|
package_dir_hint = "."):
|
||||||
|
return _WORKSPACE_SETUP_TEMPLATE.replace("__BUN_SHORT_PATH__", bun_short_path).replace(
|
||||||
|
"__PRIMARY_SOURCE_SHORT_PATH__",
|
||||||
|
primary_source_short_path,
|
||||||
|
).replace(
|
||||||
|
"__PACKAGE_JSON_SHORT_PATH__",
|
||||||
|
package_json_short_path,
|
||||||
|
).replace(
|
||||||
|
"__PACKAGE_DIR_HINT__",
|
||||||
|
package_dir_hint or ".",
|
||||||
|
).replace(
|
||||||
|
"__WORKING_DIR_MODE__",
|
||||||
|
working_dir_mode,
|
||||||
|
)
|
||||||
|
|
||||||
12
js/BUILD.bazel
Normal file
12
js/BUILD.bazel
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
exports_files(["defs.bzl"])
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "defs_bzl",
|
||||||
|
srcs = ["defs.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//internal:js_compat_bzl"],
|
||||||
|
)
|
||||||
12
js/defs.bzl
Normal file
12
js/defs.bzl
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
"""rules_js-style public API backed by Bun."""
|
||||||
|
|
||||||
|
load("//internal:js_compat.bzl", _JsInfo = "JsInfo", _js_binary = "js_binary", _js_library = "js_library", _js_run_devserver = "js_run_devserver", _js_test = "js_test", _ts_library = "ts_library")
|
||||||
|
|
||||||
|
visibility("public")
|
||||||
|
|
||||||
|
JsInfo = _JsInfo
|
||||||
|
js_binary = _js_binary
|
||||||
|
js_test = _js_test
|
||||||
|
js_run_devserver = _js_run_devserver
|
||||||
|
js_library = _js_library
|
||||||
|
ts_library = _ts_library
|
||||||
22
npm/BUILD.bazel
Normal file
22
npm/BUILD.bazel
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
|
||||||
|
|
||||||
|
package(default_visibility = ["//visibility:public"])
|
||||||
|
|
||||||
|
exports_files([
|
||||||
|
"extensions.bzl",
|
||||||
|
"repositories.bzl",
|
||||||
|
])
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "extensions_bzl",
|
||||||
|
srcs = ["extensions.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//internal:bun_install_bzl"],
|
||||||
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "repositories_bzl",
|
||||||
|
srcs = ["repositories.bzl"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//internal:bun_install_bzl"],
|
||||||
|
)
|
||||||
28
npm/extensions.bzl
Normal file
28
npm/extensions.bzl
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
load("//internal:bun_install.bzl", "bun_install_repository")
|
||||||
|
|
||||||
|
_translate = tag_class(
|
||||||
|
attrs = {
|
||||||
|
"name": attr.string(mandatory = True),
|
||||||
|
"package_json": attr.label(mandatory = True),
|
||||||
|
"lockfile": attr.label(mandatory = True),
|
||||||
|
"install_inputs": attr.label_list(allow_files = True),
|
||||||
|
"isolated_home": attr.bool(default = True),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _npm_translate_lock_impl(ctx):
|
||||||
|
for mod in ctx.modules:
|
||||||
|
for install in mod.tags.translate:
|
||||||
|
bun_install_repository(
|
||||||
|
name = install.name,
|
||||||
|
package_json = install.package_json,
|
||||||
|
bun_lockfile = install.lockfile,
|
||||||
|
install_inputs = install.install_inputs,
|
||||||
|
isolated_home = install.isolated_home,
|
||||||
|
visible_repo_name = install.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
npm_translate_lock = module_extension(
|
||||||
|
implementation = _npm_translate_lock_impl,
|
||||||
|
tag_classes = {"translate": _translate},
|
||||||
|
)
|
||||||
11
npm/repositories.bzl
Normal file
11
npm/repositories.bzl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
load("//internal:bun_install.bzl", "bun_install_repository")
|
||||||
|
|
||||||
|
def npm_translate_lock(name, package_json, lockfile, install_inputs = [], isolated_home = True):
|
||||||
|
bun_install_repository(
|
||||||
|
name = name,
|
||||||
|
package_json = package_json,
|
||||||
|
bun_lockfile = lockfile,
|
||||||
|
install_inputs = install_inputs,
|
||||||
|
isolated_home = isolated_home,
|
||||||
|
visible_repo_name = name,
|
||||||
|
)
|
||||||
@@ -6,3 +6,10 @@ sh_test(
|
|||||||
args = ["$(location //bun:extensions.bzl)"],
|
args = ["$(location //bun:extensions.bzl)"],
|
||||||
data = ["//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"],
|
||||||
|
)
|
||||||
|
|||||||
10
tests/install_extension_test/npm_extension_shape_test.sh
Executable file
10
tests/install_extension_test/npm_extension_shape_test.sh
Executable file
@@ -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}"
|
||||||
41
tests/js_compat_test/BUILD.bazel
Normal file
41
tests/js_compat_test/BUILD.bazel
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
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"],
|
||||||
|
)
|
||||||
7
tests/js_compat_test/app.test.ts
Normal file
7
tests/js_compat_test/app.test.ts
Normal file
@@ -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");
|
||||||
|
});
|
||||||
6
tests/js_compat_test/helper.ts
Normal file
6
tests/js_compat_test/helper.ts
Normal file
@@ -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}`;
|
||||||
|
}
|
||||||
3
tests/js_compat_test/main.ts
Normal file
3
tests/js_compat_test/main.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { helperMessage } from "./helper.ts";
|
||||||
|
|
||||||
|
console.log(`${helperMessage()} ${Bun.argv.slice(2).join(" ")}`.trim());
|
||||||
1
tests/js_compat_test/payload.txt
Normal file
1
tests/js_compat_test/payload.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
payload-from-lib
|
||||||
10
tests/js_compat_test/run_binary.sh
Executable file
10
tests/js_compat_test/run_binary.sh
Executable file
@@ -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
|
||||||
10
tests/js_compat_test/run_devserver.sh
Executable file
10
tests/js_compat_test/run_devserver.sh
Executable file
@@ -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
|
||||||
119
tests/npm_compat_test/npm_translate_lock_workspace_test.sh
Executable file
119
tests/npm_compat_test/npm_translate_lock_workspace_test.sh
Executable file
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
nix_cmd="${NIX:-/nix/var/nix/profiles/default/bin/nix}"
|
||||||
|
if [[ ! -x ${nix_cmd} ]]; then
|
||||||
|
nix_cmd="$(command -v nix || true)"
|
||||||
|
fi
|
||||||
|
if [[ -z ${nix_cmd} || ! -x ${nix_cmd} ]]; then
|
||||||
|
echo "nix is required to launch bazel from the repo dev shell" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
(
|
||||||
|
cd "${rules_bun_root}" &&
|
||||||
|
"${nix_cmd}" develop -c bash -lc 'bun install --cwd "$1" >/dev/null' bash "${fixture_dir}"
|
||||||
|
)
|
||||||
|
rm -rf "${fixture_dir}/node_modules"
|
||||||
|
|
||||||
|
cat >"${fixture_dir}/MODULE.bazel" <<EOF
|
||||||
|
module(
|
||||||
|
name = "npm_compat_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
bazel_dep(name = "rules_bun", version = "0.2.2")
|
||||||
|
|
||||||
|
local_path_override(
|
||||||
|
module_name = "rules_bun",
|
||||||
|
path = "${rules_bun_root}",
|
||||||
|
)
|
||||||
|
|
||||||
|
bun_ext = use_extension("@rules_bun//bun:extensions.bzl", "bun")
|
||||||
|
use_repo(
|
||||||
|
bun_ext,
|
||||||
|
"bun_darwin_aarch64",
|
||||||
|
"bun_darwin_x64",
|
||||||
|
"bun_linux_aarch64",
|
||||||
|
"bun_linux_x64",
|
||||||
|
"bun_windows_x64",
|
||||||
|
)
|
||||||
|
|
||||||
|
npm_ext = use_extension("@rules_bun//npm:extensions.bzl", "npm_translate_lock")
|
||||||
|
npm_ext.translate(
|
||||||
|
name = "npm",
|
||||||
|
package_json = "//:package.json",
|
||||||
|
lockfile = "//:bun.lock",
|
||||||
|
)
|
||||||
|
use_repo(npm_ext, "npm")
|
||||||
|
|
||||||
|
register_toolchains(
|
||||||
|
"@rules_bun//bun:darwin_aarch64_toolchain",
|
||||||
|
"@rules_bun//bun:darwin_x64_toolchain",
|
||||||
|
"@rules_bun//bun:linux_aarch64_toolchain",
|
||||||
|
"@rules_bun//bun:linux_x64_toolchain",
|
||||||
|
"@rules_bun//bun:windows_x64_toolchain",
|
||||||
|
)
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >"${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 "${rules_bun_root}" &&
|
||||||
|
"${nix_cmd}" develop -c bash -lc 'cd "$1" && bazel run //:app' bash "${fixture_dir}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ ${output} != *"compat:true"* ]]; then
|
||||||
|
echo "unexpected output: ${output}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
query_output="$(
|
||||||
|
cd "${rules_bun_root}" &&
|
||||||
|
"${nix_cmd}" develop -c bash -lc 'cd "$1" && bazel query //:npm__is_number' bash "${fixture_dir}"
|
||||||
|
)"
|
||||||
|
if ! grep -Fxq "//:npm__is_number" <<<"${query_output}"; then
|
||||||
|
echo "expected npm_link_all_packages to create //:npm__is_number" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user