Inital commit
This commit is contained in:
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