"""Rule for running package.json scripts with Bun.""" load("//internal:bun_command.bzl", "append_shell_flag", "append_shell_flag_files", "append_shell_flag_value", "append_shell_flag_values", "append_shell_install_mode", "append_shell_raw_flags", "render_shell_array", "shell_quote") load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles") def _bun_script_impl(ctx): toolchain = ctx.toolchains["//bun:toolchain_type"] bun_bin = toolchain.bun.bun_bin package_json = ctx.file.package_json workspace_info = create_bun_workspace_info( ctx, extra_files = ctx.files.data + ctx.files.preload + ctx.files.env_files + [bun_bin], package_dir_hint = package_json.dirname or ".", package_json = package_json, primary_file = package_json, ) launcher_lines = [render_shell_array("bun_args", ["--bun", "run"])] append_shell_install_mode(launcher_lines, "bun_args", ctx.attr.install_mode) append_shell_flag_files(launcher_lines, "bun_args", "--preload", ctx.files.preload) append_shell_flag_files(launcher_lines, "bun_args", "--env-file", ctx.files.env_files) append_shell_flag(launcher_lines, "bun_args", "--no-env-file", ctx.attr.no_env_file) append_shell_flag(launcher_lines, "bun_args", "--smol", ctx.attr.smol) append_shell_flag_values(launcher_lines, "bun_args", "--conditions", ctx.attr.conditions) append_shell_flag(launcher_lines, "bun_args", "--workspaces", ctx.attr.workspaces) append_shell_flag_values(launcher_lines, "bun_args", "--filter", ctx.attr.filters) if ctx.attr.execution_mode == "parallel": append_shell_flag(launcher_lines, "bun_args", "--parallel", True) elif ctx.attr.execution_mode == "sequential": append_shell_flag(launcher_lines, "bun_args", "--sequential", True) append_shell_flag(launcher_lines, "bun_args", "--no-exit-on-error", ctx.attr.no_exit_on_error) append_shell_flag_value(launcher_lines, "bun_args", "--shell", ctx.attr.shell) append_shell_flag(launcher_lines, "bun_args", "--silent", ctx.attr.silent) append_shell_raw_flags(launcher_lines, "bun_args", ctx.attr.run_flags) launcher_lines.append('bun_args+=(%s)' % shell_quote(ctx.attr.script)) for arg in ctx.attr.args: launcher_lines.append("bun_args+=(%s)" % shell_quote(arg)) command = """ trap cleanup_runtime_workspace EXIT cd "${runtime_exec_dir}" __BUN_ARGS__ exec "${bun_bin}" "${bun_args[@]}" "$@" """.replace("__BUN_ARGS__", "\n".join(launcher_lines)) 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, package_dir_hint = package_json.dirname or ".", package_json_short_path = package_json.short_path, primary_source_short_path = package_json.short_path, working_dir_mode = ctx.attr.working_dir, ) + command, ) return [ workspace_info, DefaultInfo( executable = launcher, runfiles = workspace_runfiles(ctx, workspace_info, direct_files = [launcher]), ), ] bun_script = rule( implementation = _bun_script_impl, doc = """Runs a named `package.json` script with Bun as an executable target. Use this rule to expose existing package scripts such as `dev`, `build`, or `check` via `bazel run` without adding wrapper shell scripts. This is a good fit for Vite-style workflows, where scripts like `vite dev` or `vite build` are declared in `package.json` and expect to run from the package directory with `node_modules/.bin` available on `PATH`. """, attrs = { "script": attr.string( mandatory = True, doc = "Name of the `package.json` script to execute via `bun run