Inital commit

This commit is contained in:
eric
2026-03-12 18:58:43 +01:00
commit 8555b02752
36 changed files with 3312 additions and 0 deletions

View 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)
}
}