Inital commit
This commit is contained in:
5
wails_bun/BUILD.bazel
Normal file
5
wails_bun/BUILD.bazel
Normal file
@@ -0,0 +1,5 @@
|
||||
exports_files([
|
||||
"defs.bzl",
|
||||
"README.md",
|
||||
])
|
||||
|
||||
23
wails_bun/README.md
Normal file
23
wails_bun/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# rules_wails Bun integration
|
||||
|
||||
Optional `rules_bun` integration for Wails projects that build their frontend
|
||||
with Bun and Vite-style package scripts.
|
||||
|
||||
Public API:
|
||||
|
||||
- `wails_bun_frontend_dist`
|
||||
- `wails_bun_frontend_dev`
|
||||
- `wails_bun_dev_session`
|
||||
- `wails_bun_app`
|
||||
|
||||
Important contract:
|
||||
|
||||
- `ts_library` is not enough for frontend package staging.
|
||||
- Package-source targets passed to `wails_bun_frontend_dist` should include both the package sources and `package.json`.
|
||||
- Consumers should provide package-source targets that include both application sources and `package.json`.
|
||||
|
||||
Load with:
|
||||
|
||||
```starlark
|
||||
load("@rules_wails//wails_bun:defs.bzl", "wails_bun_dev_session", "wails_bun_frontend_dev", "wails_bun_frontend_dist")
|
||||
```
|
||||
13
wails_bun/defs.bzl
Normal file
13
wails_bun/defs.bzl
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Public API for Bun-backed Wails helpers."""
|
||||
|
||||
load(":private/dev_session.bzl", _wails_bun_dev_session = "wails_bun_dev_session")
|
||||
load(":private/frontend_dist.bzl", _wails_bun_frontend_dev = "wails_bun_frontend_dev", _wails_bun_frontend_dist = "wails_bun_frontend_dist")
|
||||
load(":private/macros.bzl", _wails_bun_app = "wails_bun_app")
|
||||
|
||||
visibility("public")
|
||||
|
||||
wails_bun_frontend_dist = _wails_bun_frontend_dist
|
||||
wails_bun_frontend_dev = _wails_bun_frontend_dev
|
||||
wails_bun_dev_session = _wails_bun_dev_session
|
||||
wails_bun_app = _wails_bun_app
|
||||
|
||||
198
wails_bun/private/dev_session.bzl
Normal file
198
wails_bun/private/dev_session.bzl
Normal file
@@ -0,0 +1,198 @@
|
||||
"""Dev-session rules for Bun-backed Wails apps."""
|
||||
|
||||
load("//wails:private/common.bzl", "bash_launcher")
|
||||
|
||||
def _shell_quote(value):
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
def _absolute_label(label_package, name):
|
||||
if label_package:
|
||||
return "//%s:%s" % (label_package, name)
|
||||
return "//:%s" % name
|
||||
|
||||
def _wails_bun_watch_run_impl(ctx):
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
|
||||
resolve_lines = """
|
||||
tool="$(resolve_runfile "{tool_short_path}")"
|
||||
binary="$(resolve_runfile "{binary_short_path}")"
|
||||
build_assets="$(resolve_runfile "{build_assets_short_path}")"
|
||||
bindings=""
|
||||
if [[ -n "{bindings_short_path}" ]]; then
|
||||
bindings="$(resolve_runfile "{bindings_short_path}")"
|
||||
fi
|
||||
icon=""
|
||||
if [[ -n "{icon_short_path}" ]]; then
|
||||
icon="$(resolve_runfile "{icon_short_path}")"
|
||||
fi
|
||||
""".format(
|
||||
tool_short_path = ctx.executable._tool.short_path,
|
||||
binary_short_path = ctx.executable.binary.short_path,
|
||||
build_assets_short_path = ctx.files.build_assets[0].short_path,
|
||||
bindings_short_path = ctx.executable.bindings.short_path if ctx.executable.bindings else "",
|
||||
icon_short_path = ctx.file.icon.short_path if ctx.file.icon else "",
|
||||
)
|
||||
|
||||
binding_command = ""
|
||||
if ctx.executable.bindings:
|
||||
binding_command = "\"$bindings\"\n"
|
||||
|
||||
command_lines = """
|
||||
{binding_command}exec "$tool" \
|
||||
--binary "$binary" \
|
||||
--build-assets "$build_assets" \
|
||||
--frontend-url {frontend_url} \
|
||||
--icon "$icon" \
|
||||
--mode 'dev'
|
||||
""".format(
|
||||
binding_command = binding_command,
|
||||
frontend_url = _shell_quote(ctx.attr.frontend_url),
|
||||
)
|
||||
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = bash_launcher(resolve_lines, command_lines),
|
||||
)
|
||||
|
||||
transitive_files = [
|
||||
ctx.attr._tool[DefaultInfo].default_runfiles.files,
|
||||
ctx.attr.binary[DefaultInfo].default_runfiles.files,
|
||||
]
|
||||
files = [
|
||||
ctx.executable._tool,
|
||||
ctx.executable.binary,
|
||||
] + ctx.files.build_assets + ([ctx.file.icon] if ctx.file.icon else [])
|
||||
|
||||
if ctx.attr.bindings:
|
||||
transitive_files.append(ctx.attr.bindings[DefaultInfo].default_runfiles.files)
|
||||
files.append(ctx.executable.bindings)
|
||||
|
||||
runfiles = ctx.runfiles(
|
||||
files = files,
|
||||
transitive_files = depset(transitive = transitive_files),
|
||||
)
|
||||
|
||||
return [DefaultInfo(executable = launcher, runfiles = runfiles)]
|
||||
|
||||
_wails_bun_watch_run = rule(
|
||||
implementation = _wails_bun_watch_run_impl,
|
||||
attrs = {
|
||||
"binary": attr.label(
|
||||
mandatory = True,
|
||||
executable = True,
|
||||
cfg = "target",
|
||||
),
|
||||
"bindings": attr.label(
|
||||
executable = True,
|
||||
cfg = "target",
|
||||
),
|
||||
"build_assets": attr.label(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
),
|
||||
"frontend_url": attr.string(default = "http://127.0.0.1:9245"),
|
||||
"icon": attr.label(allow_single_file = True),
|
||||
"_tool": attr.label(
|
||||
default = "//wails/tools:launch_app",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
},
|
||||
executable = True,
|
||||
)
|
||||
|
||||
def _wails_bun_dev_session_impl(ctx):
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
resolve_lines = """
|
||||
tool="$(resolve_runfile "{tool_short_path}")"
|
||||
frontend_dev="$(resolve_runfile "{frontend_dev_short_path}")"
|
||||
""".format(
|
||||
tool_short_path = ctx.executable._tool.short_path,
|
||||
frontend_dev_short_path = ctx.executable.frontend_dev.short_path,
|
||||
)
|
||||
|
||||
command_lines = """
|
||||
exec "$tool" \
|
||||
--frontend-dev "$frontend_dev" \
|
||||
--frontend-url {frontend_url} \
|
||||
--watch-target {watch_target} \
|
||||
--workspace-dir {workspace_dir}
|
||||
""".format(
|
||||
frontend_url = _shell_quote(ctx.attr.frontend_url),
|
||||
watch_target = _shell_quote(ctx.attr.watch_target),
|
||||
workspace_dir = _shell_quote(ctx.attr.workspace_dir),
|
||||
)
|
||||
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = bash_launcher(resolve_lines, command_lines),
|
||||
)
|
||||
|
||||
runfiles = ctx.runfiles(
|
||||
files = [
|
||||
ctx.executable._tool,
|
||||
ctx.executable.frontend_dev,
|
||||
],
|
||||
transitive_files = depset(transitive = [
|
||||
ctx.attr._tool[DefaultInfo].default_runfiles.files,
|
||||
ctx.attr.frontend_dev[DefaultInfo].default_runfiles.files,
|
||||
]),
|
||||
)
|
||||
|
||||
return [DefaultInfo(executable = launcher, runfiles = runfiles)]
|
||||
|
||||
_wails_bun_dev_session_rule = rule(
|
||||
implementation = _wails_bun_dev_session_impl,
|
||||
attrs = {
|
||||
"frontend_dev": attr.label(
|
||||
mandatory = True,
|
||||
executable = True,
|
||||
cfg = "target",
|
||||
),
|
||||
"frontend_url": attr.string(default = "http://127.0.0.1:9245"),
|
||||
"watch_target": attr.string(mandatory = True),
|
||||
"workspace_dir": attr.string(default = "."),
|
||||
"_tool": attr.label(
|
||||
default = "//wails_bun/tools:bun_dev_session",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
},
|
||||
executable = True,
|
||||
)
|
||||
|
||||
def wails_bun_dev_session(
|
||||
name,
|
||||
workspace_dir,
|
||||
frontend_dev,
|
||||
app_binary,
|
||||
build_assets,
|
||||
bindings_target = None,
|
||||
icon = None,
|
||||
frontend_url = "http://127.0.0.1:9245",
|
||||
tags = None,
|
||||
visibility = None):
|
||||
watch_name = name + "_watch"
|
||||
|
||||
_wails_bun_watch_run(
|
||||
name = watch_name,
|
||||
binary = app_binary,
|
||||
bindings = bindings_target,
|
||||
build_assets = build_assets,
|
||||
frontend_url = frontend_url,
|
||||
icon = icon,
|
||||
tags = ["manual"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
_wails_bun_dev_session_rule(
|
||||
name = name,
|
||||
frontend_dev = frontend_dev,
|
||||
frontend_url = frontend_url,
|
||||
tags = tags,
|
||||
visibility = visibility,
|
||||
watch_target = _absolute_label(native.package_name(), watch_name),
|
||||
workspace_dir = workspace_dir,
|
||||
)
|
||||
274
wails_bun/private/frontend_dist.bzl
Normal file
274
wails_bun/private/frontend_dist.bzl
Normal file
@@ -0,0 +1,274 @@
|
||||
"""Bun frontend build helpers for Wails."""
|
||||
|
||||
load("//wails:private/common.bzl", "bash_launcher", "write_manifest")
|
||||
|
||||
def _node_modules_root_from_path(path):
|
||||
marker = "/node_modules/"
|
||||
marker_index = path.find(marker)
|
||||
if marker_index < 0:
|
||||
return None
|
||||
return path[:marker_index + len("/node_modules")]
|
||||
|
||||
def _dirname(path):
|
||||
index = path.rfind("/")
|
||||
if index < 0:
|
||||
return "."
|
||||
return path[:index] or "."
|
||||
|
||||
def _wails_bun_frontend_dist_impl(ctx):
|
||||
out_dir = ctx.actions.declare_directory(ctx.label.name)
|
||||
|
||||
manifest_srcs = list(ctx.files.srcs)
|
||||
if ctx.file.package_json.path not in [src.path for src in manifest_srcs]:
|
||||
manifest_srcs.append(ctx.file.package_json)
|
||||
|
||||
manifest = write_manifest(
|
||||
ctx,
|
||||
ctx.label.name + ".manifest",
|
||||
manifest_srcs,
|
||||
ctx.attr.strip_prefix,
|
||||
)
|
||||
|
||||
if not ctx.files.node_modules:
|
||||
fail("wails_bun_frontend_dist requires a non-empty node_modules tree")
|
||||
|
||||
workspace_marker = ""
|
||||
if ctx.attr.workspace_dir:
|
||||
workspace_marker = "/%s/node_modules/" % ctx.attr.workspace_dir.strip("/")
|
||||
|
||||
shortest_path = None
|
||||
shared_node_modules_root = None
|
||||
workspace_node_modules_root = None
|
||||
|
||||
for src in ctx.files.node_modules:
|
||||
if shortest_path == None or len(src.path) < len(shortest_path):
|
||||
shortest_path = src.path
|
||||
if workspace_marker and workspace_marker in src.path and workspace_node_modules_root == None:
|
||||
workspace_node_modules_root = _node_modules_root_from_path(src.path)
|
||||
|
||||
if shortest_path:
|
||||
shared_node_modules_root = _node_modules_root_from_path(shortest_path)
|
||||
|
||||
node_modules_root = workspace_node_modules_root or shared_node_modules_root
|
||||
if node_modules_root == None or shared_node_modules_root == None:
|
||||
fail("unable to determine node_modules roots from node_modules inputs")
|
||||
|
||||
package_json_rel = ctx.file.package_json.short_path
|
||||
if ctx.attr.strip_prefix and package_json_rel.startswith(ctx.attr.strip_prefix):
|
||||
package_json_rel = package_json_rel[len(ctx.attr.strip_prefix):]
|
||||
package_dir = _dirname(package_json_rel)
|
||||
|
||||
args = ctx.actions.args()
|
||||
args.add("--build-script", ctx.attr.build_script)
|
||||
args.add("--manifest", manifest.path)
|
||||
args.add("--node-modules-root", node_modules_root)
|
||||
args.add("--out", out_dir.path)
|
||||
args.add("--package-dir", package_dir)
|
||||
args.add("--shared-node-modules-root", shared_node_modules_root)
|
||||
args.add("--bun-darwin-aarch64", ctx.file._bun_darwin_aarch64.path)
|
||||
args.add("--bun-darwin-x64", ctx.file._bun_darwin_x64.path)
|
||||
args.add("--bun-linux-aarch64", ctx.file._bun_linux_aarch64.path)
|
||||
args.add("--bun-linux-x64", ctx.file._bun_linux_x64.path)
|
||||
args.add("--bun-windows-x64", ctx.file._bun_windows_x64.path)
|
||||
if ctx.attr.workspace_dir:
|
||||
args.add("--workspace-dir", ctx.attr.workspace_dir)
|
||||
|
||||
ctx.actions.run(
|
||||
executable = ctx.executable._tool,
|
||||
arguments = [args],
|
||||
inputs = depset(
|
||||
manifest_srcs +
|
||||
ctx.files.node_modules + [
|
||||
manifest,
|
||||
ctx.file._bun_darwin_aarch64,
|
||||
ctx.file._bun_darwin_x64,
|
||||
ctx.file._bun_linux_aarch64,
|
||||
ctx.file._bun_linux_x64,
|
||||
ctx.file._bun_windows_x64,
|
||||
],
|
||||
),
|
||||
outputs = [out_dir],
|
||||
tools = [ctx.attr._tool[DefaultInfo].files_to_run],
|
||||
mnemonic = "WailsBunFrontendDist",
|
||||
progress_message = "Building Bun frontend dist for %s" % ctx.label,
|
||||
)
|
||||
|
||||
return [DefaultInfo(files = depset([out_dir]))]
|
||||
|
||||
def _wails_bun_frontend_dev_impl(ctx):
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
package_dir = _dirname(ctx.file.package_json.short_path)
|
||||
|
||||
resolve_lines = """
|
||||
tool="$(resolve_runfile "{tool_short_path}")"
|
||||
bun_darwin_aarch64="$(resolve_runfile "{bun_darwin_aarch64_short_path}")"
|
||||
bun_darwin_x64="$(resolve_runfile "{bun_darwin_x64_short_path}")"
|
||||
bun_linux_aarch64="$(resolve_runfile "{bun_linux_aarch64_short_path}")"
|
||||
bun_linux_x64="$(resolve_runfile "{bun_linux_x64_short_path}")"
|
||||
bun_windows_x64="$(resolve_runfile "{bun_windows_x64_short_path}")"
|
||||
""".format(
|
||||
tool_short_path = ctx.executable._tool.short_path,
|
||||
bun_darwin_aarch64_short_path = ctx.file._bun_darwin_aarch64.short_path,
|
||||
bun_darwin_x64_short_path = ctx.file._bun_darwin_x64.short_path,
|
||||
bun_linux_aarch64_short_path = ctx.file._bun_linux_aarch64.short_path,
|
||||
bun_linux_x64_short_path = ctx.file._bun_linux_x64.short_path,
|
||||
bun_windows_x64_short_path = ctx.file._bun_windows_x64.short_path,
|
||||
)
|
||||
|
||||
command_lines = """
|
||||
exec "$tool" \
|
||||
--bun-darwin-aarch64 "$bun_darwin_aarch64" \
|
||||
--bun-darwin-x64 "$bun_darwin_x64" \
|
||||
--bun-linux-aarch64 "$bun_linux_aarch64" \
|
||||
--bun-linux-x64 "$bun_linux_x64" \
|
||||
--bun-windows-x64 "$bun_windows_x64" \
|
||||
--package-dir {package_dir} \
|
||||
--script {script}
|
||||
""".format(
|
||||
package_dir = _shell_quote(ctx.attr.workspace_dir or package_dir),
|
||||
script = _shell_quote(ctx.attr.script),
|
||||
)
|
||||
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = bash_launcher(resolve_lines, command_lines),
|
||||
)
|
||||
|
||||
runfiles = ctx.runfiles(
|
||||
files = [
|
||||
ctx.executable._tool,
|
||||
ctx.file._bun_darwin_aarch64,
|
||||
ctx.file._bun_darwin_x64,
|
||||
ctx.file._bun_linux_aarch64,
|
||||
ctx.file._bun_linux_x64,
|
||||
ctx.file._bun_windows_x64,
|
||||
] + ([ctx.file.package_json] if ctx.file.package_json else []),
|
||||
transitive_files = depset(transitive = [
|
||||
ctx.attr._tool[DefaultInfo].default_runfiles.files,
|
||||
]),
|
||||
)
|
||||
|
||||
return [DefaultInfo(executable = launcher, runfiles = runfiles)]
|
||||
|
||||
def _shell_quote(value):
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
wails_bun_frontend_dist = rule(
|
||||
implementation = _wails_bun_frontend_dist_impl,
|
||||
attrs = {
|
||||
"build_script": attr.string(default = "build"),
|
||||
"node_modules": attr.label(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
),
|
||||
"package_json": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = True,
|
||||
),
|
||||
"srcs": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
),
|
||||
"strip_prefix": attr.string(default = ""),
|
||||
"workspace_dir": attr.string(default = ""),
|
||||
"_tool": attr.label(
|
||||
default = "//wails_bun/tools:frontend_dist_action",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
"_bun_darwin_aarch64": attr.label(
|
||||
default = "@bun_darwin_aarch64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_darwin_x64": attr.label(
|
||||
default = "@bun_darwin_x64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_linux_aarch64": attr.label(
|
||||
default = "@bun_linux_aarch64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_linux_x64": attr.label(
|
||||
default = "@bun_linux_x64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_windows_x64": attr.label(
|
||||
default = "@bun_windows_x64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
_wails_bun_frontend_dev = rule(
|
||||
implementation = _wails_bun_frontend_dev_impl,
|
||||
attrs = {
|
||||
"data": attr.label_list(allow_files = True),
|
||||
"node_modules": attr.label(
|
||||
mandatory = True,
|
||||
allow_files = True,
|
||||
),
|
||||
"package_json": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = True,
|
||||
),
|
||||
"script": attr.string(default = "dev"),
|
||||
"workspace_dir": attr.string(default = ""),
|
||||
"_tool": attr.label(
|
||||
default = "//wails_bun/tools:frontend_dev_server",
|
||||
cfg = "exec",
|
||||
executable = True,
|
||||
),
|
||||
"_bun_darwin_aarch64": attr.label(
|
||||
default = "@bun_darwin_aarch64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_darwin_x64": attr.label(
|
||||
default = "@bun_darwin_x64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_linux_aarch64": attr.label(
|
||||
default = "@bun_linux_aarch64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_linux_x64": attr.label(
|
||||
default = "@bun_linux_x64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_bun_windows_x64": attr.label(
|
||||
default = "@bun_windows_x64//:bun",
|
||||
cfg = "exec",
|
||||
allow_single_file = True,
|
||||
),
|
||||
},
|
||||
executable = True,
|
||||
)
|
||||
|
||||
def wails_bun_frontend_dev(
|
||||
name,
|
||||
data,
|
||||
node_modules,
|
||||
package_json,
|
||||
script = "dev",
|
||||
workspace_dir = "",
|
||||
tags = None,
|
||||
visibility = None):
|
||||
_wails_bun_frontend_dev(
|
||||
name = name,
|
||||
data = data,
|
||||
node_modules = node_modules,
|
||||
package_json = package_json,
|
||||
script = script,
|
||||
tags = tags,
|
||||
visibility = visibility,
|
||||
workspace_dir = workspace_dir,
|
||||
)
|
||||
71
wails_bun/private/macros.bzl
Normal file
71
wails_bun/private/macros.bzl
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Macros for Bun-backed Wails applications."""
|
||||
|
||||
load("//wails:defs.bzl", "wails_build_assets", "wails_generate_bindings", "wails_run")
|
||||
load(":private/dev_session.bzl", "wails_bun_dev_session")
|
||||
load(":private/frontend_dist.bzl", "wails_bun_frontend_dev", "wails_bun_frontend_dist")
|
||||
|
||||
def wails_bun_app(
|
||||
name,
|
||||
app_binary,
|
||||
frontend_srcs,
|
||||
package_json,
|
||||
node_modules,
|
||||
build_asset_srcs,
|
||||
app_name,
|
||||
binary_name,
|
||||
icon = None,
|
||||
bindings_package_dir = ".",
|
||||
frontend_strip_prefix = "",
|
||||
build_strip_prefix = "",
|
||||
visibility = None):
|
||||
wails_bun_frontend_dist(
|
||||
name = name + "_frontend_dist",
|
||||
srcs = frontend_srcs,
|
||||
node_modules = node_modules,
|
||||
package_json = package_json,
|
||||
strip_prefix = frontend_strip_prefix,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
wails_build_assets(
|
||||
name = name + "_build_assets",
|
||||
srcs = build_asset_srcs,
|
||||
app_name = app_name,
|
||||
binary_name = binary_name,
|
||||
strip_prefix = build_strip_prefix,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
wails_generate_bindings(
|
||||
name = name + "_bindings",
|
||||
out_dir = "frontend/src/lib/bindings",
|
||||
package_dir = bindings_package_dir,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
wails_bun_frontend_dev(
|
||||
name = name + "_frontend_dev",
|
||||
data = frontend_srcs,
|
||||
node_modules = node_modules,
|
||||
package_json = package_json,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
wails_run(
|
||||
name = name + "_run",
|
||||
binary = app_binary,
|
||||
build_assets = name + "_build_assets",
|
||||
icon = icon,
|
||||
visibility = visibility,
|
||||
)
|
||||
|
||||
wails_bun_dev_session(
|
||||
name = name + "_dev",
|
||||
workspace_dir = native.package_name() or ".",
|
||||
frontend_dev = name + "_frontend_dev",
|
||||
app_binary = app_binary,
|
||||
build_assets = name + "_build_assets",
|
||||
bindings_target = name + "_bindings",
|
||||
icon = icon,
|
||||
visibility = visibility,
|
||||
)
|
||||
25
wails_bun/tools/BUILD.bazel
Normal file
25
wails_bun/tools/BUILD.bazel
Normal file
@@ -0,0 +1,25 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_binary")
|
||||
|
||||
go_binary(
|
||||
name = "frontend_dist_action",
|
||||
srcs = ["frontend_dist_action.go"],
|
||||
importpath = "github.com/Eriyc/rules_wails/wails_bun/tools/frontend_dist_action",
|
||||
pure = "off",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "bun_dev_session",
|
||||
srcs = ["bun_dev_session.go"],
|
||||
importpath = "github.com/Eriyc/rules_wails/wails_bun/tools/bun_dev_session",
|
||||
pure = "off",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "frontend_dev_server",
|
||||
srcs = ["frontend_dev_server.go"],
|
||||
importpath = "github.com/Eriyc/rules_wails/wails_bun/tools/frontend_dev_server",
|
||||
pure = "off",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
177
wails_bun/tools/bun_dev_session.go
Normal file
177
wails_bun/tools/bun_dev_session.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var frontendDev string
|
||||
var frontendURL string
|
||||
var readyTimeout time.Duration
|
||||
var watchTarget string
|
||||
var workspaceDir string
|
||||
|
||||
flag.StringVar(&frontendDev, "frontend-dev", "", "")
|
||||
flag.StringVar(&frontendURL, "frontend-url", "http://127.0.0.1:9245", "")
|
||||
flag.DurationVar(&readyTimeout, "ready-timeout", 2*time.Minute, "")
|
||||
flag.StringVar(&watchTarget, "watch-target", "", "")
|
||||
flag.StringVar(&workspaceDir, "workspace-dir", ".", "")
|
||||
flag.Parse()
|
||||
|
||||
require(frontendDev != "", "missing --frontend-dev")
|
||||
require(watchTarget != "", "missing --watch-target")
|
||||
|
||||
workspaceRoot := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
|
||||
if workspaceRoot == "" {
|
||||
workspaceRoot = "."
|
||||
}
|
||||
runfilesDir := resolveRunfilesDir()
|
||||
|
||||
frontendCommand := exec.Command(frontendDev)
|
||||
frontendCommand.Dir = workspaceRoot
|
||||
frontendCommand.Env = withRunfilesEnv(os.Environ(), runfilesDir)
|
||||
frontendCommand.Stdout = os.Stdout
|
||||
frontendCommand.Stderr = os.Stderr
|
||||
|
||||
must(frontendCommand.Start())
|
||||
waitCh := make(chan error, 1)
|
||||
go func() {
|
||||
waitCh <- frontendCommand.Wait()
|
||||
}()
|
||||
defer terminate(frontendCommand, waitCh)
|
||||
|
||||
must(waitForURL(frontendURL, readyTimeout, waitCh))
|
||||
|
||||
watchCommand, err := resolveWatchCommand(watchTarget)
|
||||
must(err)
|
||||
watchCommand.Dir = workspaceRoot
|
||||
watchCommand.Env = os.Environ()
|
||||
watchCommand.Stdout = os.Stdout
|
||||
watchCommand.Stderr = os.Stderr
|
||||
watchCommand.Stdin = os.Stdin
|
||||
|
||||
if err := watchCommand.Run(); err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(exitError.ExitCode())
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_ = workspaceDir
|
||||
}
|
||||
|
||||
func resolveRunfilesDir() string {
|
||||
for _, candidate := range []string{
|
||||
os.Getenv("RUNFILES_DIR"),
|
||||
os.Getenv("RUNFILES"),
|
||||
} {
|
||||
if candidate != "" {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
executablePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
normalizedPath := filepath.Clean(executablePath)
|
||||
marker := ".runfiles"
|
||||
if index := strings.Index(normalizedPath, marker+string(os.PathSeparator)); index >= 0 {
|
||||
return normalizedPath[:index+len(marker)]
|
||||
}
|
||||
if strings.HasSuffix(normalizedPath, marker) {
|
||||
return normalizedPath
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func withRunfilesEnv(environment []string, runfilesDir string) []string {
|
||||
if runfilesDir == "" {
|
||||
return environment
|
||||
}
|
||||
|
||||
environment = setEnv(environment, "RUNFILES_DIR", runfilesDir)
|
||||
environment = setEnv(environment, "RUNFILES", runfilesDir)
|
||||
return environment
|
||||
}
|
||||
|
||||
func setEnv(environment []string, key string, value string) []string {
|
||||
prefix := key + "="
|
||||
for index, entry := range environment {
|
||||
if strings.HasPrefix(entry, prefix) {
|
||||
environment[index] = prefix + value
|
||||
return environment
|
||||
}
|
||||
}
|
||||
return append(environment, prefix+value)
|
||||
}
|
||||
|
||||
func waitForURL(url string, timeout time.Duration, waitCh <-chan error) error {
|
||||
client := http.Client{Timeout: 2 * time.Second}
|
||||
deadline := time.Now().Add(timeout)
|
||||
|
||||
for time.Now().Before(deadline) {
|
||||
select {
|
||||
case err := <-waitCh:
|
||||
if err == nil {
|
||||
return fmt.Errorf("frontend dev server exited before becoming ready")
|
||||
}
|
||||
return fmt.Errorf("frontend dev server exited before becoming ready: %w", err)
|
||||
default:
|
||||
}
|
||||
|
||||
response, err := client.Get(url)
|
||||
if err == nil {
|
||||
response.Body.Close()
|
||||
if response.StatusCode >= 200 && response.StatusCode < 400 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
return fmt.Errorf("frontend dev server did not become ready at %s", url)
|
||||
}
|
||||
|
||||
func resolveWatchCommand(target string) (*exec.Cmd, error) {
|
||||
for _, candidate := range []string{"ibazel", "bazelisk", "bazel"} {
|
||||
if _, err := exec.LookPath(candidate); err == nil {
|
||||
return exec.Command(candidate, "run", target), nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("neither ibazel, bazelisk, nor bazel is available in PATH")
|
||||
}
|
||||
|
||||
func terminate(command *exec.Cmd, waitCh <-chan error) {
|
||||
if command.Process == nil {
|
||||
return
|
||||
}
|
||||
_ = command.Process.Kill()
|
||||
select {
|
||||
case <-waitCh:
|
||||
case <-time.After(2 * time.Second):
|
||||
}
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func require(condition bool, message string) {
|
||||
if !condition {
|
||||
panic(message)
|
||||
}
|
||||
}
|
||||
139
wails_bun/tools/frontend_dev_server.go
Normal file
139
wails_bun/tools/frontend_dev_server.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var bunDarwinAarch64 string
|
||||
var bunDarwinX64 string
|
||||
var bunLinuxAarch64 string
|
||||
var bunLinuxX64 string
|
||||
var bunWindowsX64 string
|
||||
var packageDir string
|
||||
var script string
|
||||
|
||||
flag.StringVar(&bunDarwinAarch64, "bun-darwin-aarch64", "", "")
|
||||
flag.StringVar(&bunDarwinX64, "bun-darwin-x64", "", "")
|
||||
flag.StringVar(&bunLinuxAarch64, "bun-linux-aarch64", "", "")
|
||||
flag.StringVar(&bunLinuxX64, "bun-linux-x64", "", "")
|
||||
flag.StringVar(&bunWindowsX64, "bun-windows-x64", "", "")
|
||||
flag.StringVar(&packageDir, "package-dir", "", "")
|
||||
flag.StringVar(&script, "script", "dev", "")
|
||||
flag.Parse()
|
||||
|
||||
require(packageDir != "", "missing --package-dir")
|
||||
|
||||
bunPath, err := resolveBunBinary(bunDarwinAarch64, bunDarwinX64, bunLinuxAarch64, bunLinuxX64, bunWindowsX64)
|
||||
must(err)
|
||||
must(makeAbsolute(&bunPath))
|
||||
|
||||
workspaceRoot := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
|
||||
if workspaceRoot == "" {
|
||||
returnError("BUILD_WORKSPACE_DIRECTORY is required for frontend dev sessions")
|
||||
}
|
||||
|
||||
packageRoot := filepath.Join(workspaceRoot, filepath.FromSlash(packageDir))
|
||||
if _, err := os.Stat(filepath.Join(packageRoot, "package.json")); err != nil {
|
||||
returnError("frontend package.json not found in workspace package dir: " + packageRoot)
|
||||
}
|
||||
|
||||
environment := append([]string{}, os.Environ()...)
|
||||
environment = setPath(environment, buildPath(workspaceRoot, packageRoot))
|
||||
|
||||
command := exec.Command(bunPath, "--bun", "run", script)
|
||||
command.Dir = packageRoot
|
||||
command.Env = environment
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
command.Stdin = os.Stdin
|
||||
|
||||
if err := command.Run(); err != nil {
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
os.Exit(exitError.ExitCode())
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func resolveBunBinary(darwinAarch64 string, darwinX64 string, linuxAarch64 string, linuxX64 string, windowsX64 string) (string, error) {
|
||||
switch runtime.GOOS + "/" + runtime.GOARCH {
|
||||
case "darwin/arm64":
|
||||
return darwinAarch64, nil
|
||||
case "darwin/amd64":
|
||||
return darwinX64, nil
|
||||
case "linux/arm64":
|
||||
return linuxAarch64, nil
|
||||
case "linux/amd64":
|
||||
return linuxX64, nil
|
||||
case "windows/amd64":
|
||||
return windowsX64, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported Bun exec platform: %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
func buildPath(workspaceRoot string, packageRoot string) string {
|
||||
entries := make([]string, 0, 4)
|
||||
for _, candidate := range []string{
|
||||
filepath.Join(packageRoot, "node_modules", ".bin"),
|
||||
filepath.Join(workspaceRoot, "node_modules", ".bin"),
|
||||
os.Getenv("PATH"),
|
||||
} {
|
||||
if candidate != "" {
|
||||
entries = append(entries, candidate)
|
||||
}
|
||||
}
|
||||
return strings.Join(entries, string(os.PathListSeparator))
|
||||
}
|
||||
|
||||
func setPath(environment []string, pathValue string) []string {
|
||||
return setEnv(environment, "PATH", pathValue)
|
||||
}
|
||||
|
||||
func setEnv(environment []string, key string, value string) []string {
|
||||
prefix := key + "="
|
||||
for index, entry := range environment {
|
||||
if strings.HasPrefix(entry, prefix) {
|
||||
environment[index] = prefix + value
|
||||
return environment
|
||||
}
|
||||
}
|
||||
return append(environment, prefix+value)
|
||||
}
|
||||
|
||||
func makeAbsolute(path *string) error {
|
||||
if filepath.IsAbs(*path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
absolutePath, err := filepath.Abs(*path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*path = absolutePath
|
||||
return nil
|
||||
}
|
||||
|
||||
func returnError(message string) {
|
||||
fmt.Fprintln(os.Stderr, message)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func require(condition bool, message string) {
|
||||
if !condition {
|
||||
panic(message)
|
||||
}
|
||||
}
|
||||
423
wails_bun/tools/frontend_dist_action.go
Normal file
423
wails_bun/tools/frontend_dist_action.go
Normal file
@@ -0,0 +1,423 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type manifestEntry struct {
|
||||
sourcePath string
|
||||
relativePath string
|
||||
}
|
||||
|
||||
func main() {
|
||||
var buildScript string
|
||||
var bunDarwinAarch64 string
|
||||
var bunDarwinX64 string
|
||||
var bunLinuxAarch64 string
|
||||
var bunLinuxX64 string
|
||||
var bunWindowsX64 string
|
||||
var manifestPath string
|
||||
var nodeModulesRoot string
|
||||
var outDir string
|
||||
var packageDir string
|
||||
var sharedNodeModulesRoot string
|
||||
var workspaceDir string
|
||||
|
||||
flag.StringVar(&buildScript, "build-script", "build", "")
|
||||
flag.StringVar(&bunDarwinAarch64, "bun-darwin-aarch64", "", "")
|
||||
flag.StringVar(&bunDarwinX64, "bun-darwin-x64", "", "")
|
||||
flag.StringVar(&bunLinuxAarch64, "bun-linux-aarch64", "", "")
|
||||
flag.StringVar(&bunLinuxX64, "bun-linux-x64", "", "")
|
||||
flag.StringVar(&bunWindowsX64, "bun-windows-x64", "", "")
|
||||
flag.StringVar(&manifestPath, "manifest", "", "")
|
||||
flag.StringVar(&nodeModulesRoot, "node-modules-root", "", "")
|
||||
flag.StringVar(&outDir, "out", "", "")
|
||||
flag.StringVar(&packageDir, "package-dir", ".", "")
|
||||
flag.StringVar(&sharedNodeModulesRoot, "shared-node-modules-root", "", "")
|
||||
flag.StringVar(&workspaceDir, "workspace-dir", "", "")
|
||||
flag.Parse()
|
||||
|
||||
require(manifestPath != "", "missing --manifest")
|
||||
require(nodeModulesRoot != "", "missing --node-modules-root")
|
||||
require(outDir != "", "missing --out")
|
||||
require(packageDir != "", "missing --package-dir")
|
||||
require(sharedNodeModulesRoot != "", "missing --shared-node-modules-root")
|
||||
|
||||
bunPath, err := resolveBunBinary(bunDarwinAarch64, bunDarwinX64, bunLinuxAarch64, bunLinuxX64, bunWindowsX64)
|
||||
must(err)
|
||||
must(makeAbsolute(&bunPath))
|
||||
must(makeAbsolute(&manifestPath))
|
||||
must(makeAbsolute(&nodeModulesRoot))
|
||||
must(makeAbsolute(&outDir))
|
||||
must(makeAbsolute(&sharedNodeModulesRoot))
|
||||
|
||||
tempRoot, err := os.MkdirTemp("", "rules-wails-frontend-*")
|
||||
must(err)
|
||||
defer os.RemoveAll(tempRoot)
|
||||
|
||||
stageRoot := filepath.Join(tempRoot, "workspace")
|
||||
homeDir := filepath.Join(tempRoot, "home")
|
||||
must(os.MkdirAll(stageRoot, 0o755))
|
||||
must(os.MkdirAll(homeDir, 0o755))
|
||||
|
||||
must(stageManifest(manifestPath, stageRoot))
|
||||
must(os.MkdirAll(filepath.Join(stageRoot, "node_modules"), 0o755))
|
||||
must(copyDirectoryContents(nodeModulesRoot, filepath.Join(stageRoot, "node_modules"), true))
|
||||
|
||||
sharedBunStore := filepath.Join(sharedNodeModulesRoot, ".bun")
|
||||
if pathExists(sharedBunStore) {
|
||||
stageBunStore := filepath.Join(stageRoot, "node_modules", ".bun")
|
||||
_ = os.RemoveAll(stageBunStore)
|
||||
must(os.Symlink(sharedBunStore, stageBunStore))
|
||||
}
|
||||
|
||||
must(restoreBunLinks(stageRoot))
|
||||
must(overlayWorkspacePackages(stageRoot))
|
||||
must(installWorkspaceAlias(stageRoot, packageDir, workspaceDir))
|
||||
must(linkPackageNodeModules(stageRoot, packageDir))
|
||||
|
||||
packageRoot := stageRoot
|
||||
if packageDir != "." {
|
||||
packageRoot = filepath.Join(stageRoot, filepath.FromSlash(packageDir))
|
||||
}
|
||||
|
||||
command := exec.Command(bunPath, "--bun", "run", buildScript)
|
||||
command.Dir = packageRoot
|
||||
command.Env = append(os.Environ(),
|
||||
"HOME="+homeDir,
|
||||
"PATH="+buildPath(stageRoot, packageRoot),
|
||||
)
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
command.Stdin = os.Stdin
|
||||
must(command.Run())
|
||||
|
||||
must(os.RemoveAll(outDir))
|
||||
must(os.MkdirAll(outDir, 0o755))
|
||||
must(copyDirectoryContents(filepath.Join(packageRoot, "dist"), outDir, false))
|
||||
}
|
||||
|
||||
func resolveBunBinary(darwinAarch64 string, darwinX64 string, linuxAarch64 string, linuxX64 string, windowsX64 string) (string, error) {
|
||||
switch runtime.GOOS + "/" + runtime.GOARCH {
|
||||
case "darwin/arm64":
|
||||
return darwinAarch64, nil
|
||||
case "darwin/amd64":
|
||||
return darwinX64, nil
|
||||
case "linux/arm64":
|
||||
return linuxAarch64, nil
|
||||
case "linux/amd64":
|
||||
return linuxX64, nil
|
||||
case "windows/amd64":
|
||||
return windowsX64, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported Bun exec platform: %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
func overlayWorkspacePackages(stageRoot string) error {
|
||||
packageRoots := make([]string, 0)
|
||||
|
||||
err := filepath.Walk(stageRoot, func(path string, info os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
if info.IsDir() && info.Name() == "node_modules" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !info.IsDir() && info.Name() == "package.json" {
|
||||
packageRoots = append(packageRoots, filepath.Dir(path))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, packageRoot := range packageRoots {
|
||||
packageName, err := readPackageName(filepath.Join(packageRoot, "package.json"))
|
||||
if err != nil || packageName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
nodePackageDir := filepath.Join(append([]string{stageRoot, "node_modules"}, strings.Split(packageName, "/")...)...)
|
||||
if !pathExists(nodePackageDir) {
|
||||
continue
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(packageRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entryPath := filepath.Join(packageRoot, entry.Name())
|
||||
destinationPath := filepath.Join(nodePackageDir, entry.Name())
|
||||
_ = os.RemoveAll(destinationPath)
|
||||
if err := os.Symlink(entryPath, destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func restoreBunLinks(stageRoot string) error {
|
||||
bunPackagesRoot := filepath.Join(stageRoot, "node_modules", ".bun", "node_modules")
|
||||
if !pathExists(bunPackagesRoot) {
|
||||
return nil
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(bunPackagesRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
entryPath := filepath.Join(bunPackagesRoot, entry.Name())
|
||||
if strings.HasPrefix(entry.Name(), "@") {
|
||||
scopeRoot := filepath.Join(stageRoot, "node_modules", entry.Name())
|
||||
if err := os.MkdirAll(scopeRoot, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scopedEntries, err := os.ReadDir(entryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, scopedEntry := range scopedEntries {
|
||||
scopedPath := filepath.Join(entryPath, scopedEntry.Name())
|
||||
destinationPath := filepath.Join(scopeRoot, scopedEntry.Name())
|
||||
_ = os.RemoveAll(destinationPath)
|
||||
if err := os.Symlink(scopedPath, destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
destinationPath := filepath.Join(stageRoot, "node_modules", entry.Name())
|
||||
_ = os.RemoveAll(destinationPath)
|
||||
if err := os.Symlink(entryPath, destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installWorkspaceAlias(stageRoot string, packageDir string, workspaceDir string) error {
|
||||
if workspaceDir == "" || workspaceDir == "." || workspaceDir == packageDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
targetPath := stageRoot
|
||||
if packageDir != "." {
|
||||
targetPath = filepath.Join(stageRoot, filepath.FromSlash(packageDir))
|
||||
}
|
||||
aliasPath := filepath.Join(stageRoot, filepath.FromSlash(workspaceDir))
|
||||
if err := os.MkdirAll(filepath.Dir(aliasPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = os.RemoveAll(aliasPath)
|
||||
return os.Symlink(targetPath, aliasPath)
|
||||
}
|
||||
|
||||
func linkPackageNodeModules(stageRoot string, packageDir string) error {
|
||||
if packageDir == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
packageNodeModules := filepath.Join(stageRoot, filepath.FromSlash(packageDir), "node_modules")
|
||||
if pathExists(packageNodeModules) {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(packageNodeModules), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Symlink(filepath.Join(stageRoot, "node_modules"), packageNodeModules)
|
||||
}
|
||||
|
||||
func buildPath(stageRoot string, packageRoot string) string {
|
||||
entries := make([]string, 0, 3)
|
||||
for _, candidate := range []string{
|
||||
filepath.Join(packageRoot, "node_modules", ".bin"),
|
||||
filepath.Join(stageRoot, "node_modules", ".bin"),
|
||||
os.Getenv("PATH"),
|
||||
} {
|
||||
if candidate != "" {
|
||||
entries = append(entries, candidate)
|
||||
}
|
||||
}
|
||||
return strings.Join(entries, string(os.PathListSeparator))
|
||||
}
|
||||
|
||||
func stageManifest(manifestPath string, destinationRoot string) error {
|
||||
entries, err := readManifest(manifestPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
destinationPath := filepath.Join(destinationRoot, filepath.FromSlash(entry.relativePath))
|
||||
if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copyFile(entry.sourcePath, destinationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readManifest(path string) ([]manifestEntry, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
entries := make([]manifestEntry, 0)
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "\t", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("invalid manifest line: %s", line)
|
||||
}
|
||||
entries = append(entries, manifestEntry{
|
||||
sourcePath: parts[0],
|
||||
relativePath: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
return entries, scanner.Err()
|
||||
}
|
||||
|
||||
func readPackageName(path string) (string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var payload struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.NewDecoder(file).Decode(&payload); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return payload.Name, nil
|
||||
}
|
||||
|
||||
func copyDirectoryContents(sourceRoot string, destinationRoot string, preserveSymlinks bool) error {
|
||||
if preserveSymlinks {
|
||||
copyBinary, err := resolveCopyBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command := exec.Command(copyBinary, "-R", sourceRoot+"/.", destinationRoot+"/")
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
return command.Run()
|
||||
}
|
||||
|
||||
return filepath.Walk(sourceRoot, func(path string, info os.FileInfo, walkErr error) error {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
}
|
||||
|
||||
relativePath, err := filepath.Rel(sourceRoot, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if relativePath == "." {
|
||||
return nil
|
||||
}
|
||||
|
||||
destinationPath := filepath.Join(destinationRoot, relativePath)
|
||||
if info.IsDir() {
|
||||
return os.MkdirAll(destinationPath, 0o755)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(destinationPath), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return copyFile(path, destinationPath)
|
||||
})
|
||||
}
|
||||
|
||||
func resolveCopyBinary() (string, error) {
|
||||
for _, candidate := range []string{"/bin/cp", "/usr/bin/cp"} {
|
||||
if pathExists(candidate) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("unable to locate cp binary for %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
func copyFile(sourcePath string, destinationPath string) error {
|
||||
sourceFile, err := os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destinationFile, err := os.Create(destinationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destinationFile.Close()
|
||||
|
||||
if _, err := io.Copy(destinationFile, sourceFile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return destinationFile.Chmod(0o644)
|
||||
}
|
||||
|
||||
func makeAbsolute(path *string) error {
|
||||
if filepath.IsAbs(*path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
absolutePath, err := filepath.Abs(*path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*path = absolutePath
|
||||
return nil
|
||||
}
|
||||
|
||||
func pathExists(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func require(condition bool, message string) {
|
||||
if !condition {
|
||||
panic(message)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user