fix: bun_install adds node_modules to each directory
This commit is contained in:
@@ -28,6 +28,11 @@ bzl_library(
|
||||
srcs = ["bun_dev.bzl"],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "bun_install_bzl",
|
||||
srcs = ["bun_install.bzl"],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "bun_script_bzl",
|
||||
srcs = ["bun_script.bzl"],
|
||||
|
||||
@@ -79,7 +79,7 @@ Use this rule for non-test scripts and CLIs that should run via `bazel run`.
|
||||
doc = "Path to the main JS/TS file to execute.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing Bun/npm package files in runfiles.",
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
|
||||
@@ -72,7 +72,7 @@ Each entry point produces one output JavaScript artifact.
|
||||
doc = "Entry files to bundle.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing Bun/npm package files for resolution.",
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, for package resolution.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Source/library dependencies that provide transitive inputs.",
|
||||
|
||||
@@ -169,7 +169,7 @@ watch/HMR plus optional full restarts on selected file changes.
|
||||
doc = "Files that trigger a full Bun process restart when they change.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing Bun/npm package files in runfiles.",
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
|
||||
@@ -1,5 +1,102 @@
|
||||
"""Repository-rule based bun_install implementation."""
|
||||
|
||||
def _segment_matches(name, pattern):
|
||||
if pattern == "*":
|
||||
return True
|
||||
|
||||
if "*" not in pattern:
|
||||
return name == pattern
|
||||
|
||||
parts = pattern.split("*")
|
||||
if len(parts) == 1:
|
||||
return name == pattern
|
||||
|
||||
pos = 0
|
||||
anchored_start = not pattern.startswith("*")
|
||||
anchored_end = not pattern.endswith("*")
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
if not part:
|
||||
continue
|
||||
|
||||
match_index = name.find(part, pos)
|
||||
if match_index < 0:
|
||||
return False
|
||||
|
||||
if i == 0 and anchored_start and match_index != 0:
|
||||
return False
|
||||
|
||||
pos = match_index + len(part)
|
||||
|
||||
if anchored_end and parts[-1] and not name.endswith(parts[-1]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _walk_workspace_dirs(root, segments):
|
||||
matches = [root]
|
||||
|
||||
for segment in segments:
|
||||
if segment == "**":
|
||||
fail("bun_install: `**` is not supported in workspace patterns; use explicit segments or `*`")
|
||||
|
||||
next_matches = []
|
||||
for parent in matches:
|
||||
for child in parent.readdir():
|
||||
if child.is_dir and _segment_matches(child.basename, segment):
|
||||
next_matches.append(child)
|
||||
|
||||
matches = next_matches
|
||||
|
||||
return matches
|
||||
|
||||
def _workspace_patterns(repository_ctx, package_json):
|
||||
manifest = json.decode(repository_ctx.read(package_json))
|
||||
workspaces = manifest.get("workspaces", [])
|
||||
|
||||
if type(workspaces) == type({}):
|
||||
workspaces = workspaces.get("packages", [])
|
||||
|
||||
if type(workspaces) != type([]):
|
||||
fail("bun_install: `workspaces` must be a list or an object with a `packages` list")
|
||||
|
||||
patterns = []
|
||||
for pattern in workspaces:
|
||||
if type(pattern) != type(""):
|
||||
fail("bun_install: workspace pattern must be a string, got {}".format(type(pattern)))
|
||||
|
||||
normalized = "/".join([segment for segment in pattern.split("/") if segment and segment != "."])
|
||||
if normalized:
|
||||
patterns.append(normalized)
|
||||
|
||||
return patterns
|
||||
|
||||
def _materialize_workspace_packages(repository_ctx, package_json):
|
||||
package_root = package_json.dirname
|
||||
package_root_str = str(package_root)
|
||||
written = {}
|
||||
|
||||
for pattern in _workspace_patterns(repository_ctx, package_json):
|
||||
segments = pattern.split("/")
|
||||
for workspace_dir in _walk_workspace_dirs(package_root, segments):
|
||||
workspace_package_json = repository_ctx.path(str(workspace_dir) + "/package.json")
|
||||
if not workspace_package_json.exists:
|
||||
continue
|
||||
|
||||
workspace_dir_str = str(workspace_dir)
|
||||
if workspace_dir_str == package_root_str:
|
||||
continue
|
||||
|
||||
relative_dir = workspace_dir_str[len(package_root_str) + 1:]
|
||||
if relative_dir in written:
|
||||
continue
|
||||
|
||||
repository_ctx.file(
|
||||
relative_dir + "/package.json",
|
||||
repository_ctx.read(workspace_package_json),
|
||||
)
|
||||
written[relative_dir] = True
|
||||
|
||||
def _select_bun_binary(repository_ctx):
|
||||
os_name = repository_ctx.os.name.lower()
|
||||
arch = repository_ctx.os.arch.lower()
|
||||
@@ -35,8 +132,9 @@ def _bun_install_repository_impl(repository_ctx):
|
||||
if lockfile_name not in ["bun.lock", "bun.lockb"]:
|
||||
lockfile_name = "bun.lock"
|
||||
|
||||
repository_ctx.symlink(package_json, "package.json")
|
||||
repository_ctx.file("package.json", repository_ctx.read(package_json))
|
||||
repository_ctx.symlink(bun_lockfile, lockfile_name)
|
||||
_materialize_workspace_packages(repository_ctx, package_json)
|
||||
|
||||
result = repository_ctx.execute(
|
||||
[str(bun_bin), "--bun", "install", "--frozen-lockfile", "--no-progress"],
|
||||
|
||||
@@ -86,7 +86,7 @@ declared in `package.json` and expect to run from the package directory with
|
||||
doc = "Label of the `package.json` file containing the named script.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing Bun/npm package files in runfiles. Executables from `node_modules/.bin` are added to `PATH`, which is useful for scripts such as `vite`.",
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles. Executables from `node_modules/.bin` are added to `PATH`, which is useful for scripts such as `vite`.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
|
||||
@@ -74,7 +74,7 @@ Supports Bazel test filtering (`--test_filter`) and coverage integration.
|
||||
doc = "Test source files passed to `bun test`.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing Bun/npm package files in runfiles.",
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by test sources.",
|
||||
|
||||
Reference in New Issue
Block a user