diff --git a/scripts/copydir.go b/scripts/copydir.go index 35622ab17..6e2777612 100644 --- a/scripts/copydir.go +++ b/scripts/copydir.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" ) @@ -57,6 +58,17 @@ func main() { } func findRepoRoot() (string, error) { + _, file, _, ok := runtime.Caller(0) + if !ok { + return "", fmt.Errorf("unable to locate copydir.go source path") + } + + scriptDir := filepath.Dir(file) + candidate := filepath.Clean(filepath.Join(scriptDir, "..")) + if err := validateRepoRoot(candidate); err == nil { + return candidate, nil + } + wd, err := os.Getwd() if err != nil { return "", err @@ -68,17 +80,31 @@ func findRepoRoot() (string, error) { } for { - if _, err := os.Stat(filepath.Join(cur, ".git")); err == nil { + if err := validateRepoRoot(cur); err == nil { return filepath.Clean(cur), nil } parent := filepath.Dir(cur) if parent == cur { - return "", fmt.Errorf("could not find .git from %s", wd) + return "", fmt.Errorf("could not find repository root from %s", wd) } cur = parent } } +func validateRepoRoot(root string) error { + anchors := []string{ + filepath.Join(root, "go.sum"), + filepath.Join(root, "LICENSE"), + filepath.Join(root, ".github"), + } + for _, anchor := range anchors { + if _, err := os.Stat(anchor); err != nil { + return fmt.Errorf("missing repo anchor %s: %w", anchor, err) + } + } + return nil +} + func normalizePathArg(arg, repoRoot string) (string, error) { resolved := strings.ReplaceAll(arg, "${codespace}", repoRoot) abs, err := filepath.Abs(resolved) diff --git a/web/backend/api/exec_nonwindows.go b/web/backend/api/exec_nonwindows.go index a68a3bfd7..0dc3c0e94 100644 --- a/web/backend/api/exec_nonwindows.go +++ b/web/backend/api/exec_nonwindows.go @@ -8,4 +8,4 @@ func launcherExecCommand(name string, args ...string) *exec.Cmd { return exec.Command(name, args...) } -func applyLauncherWindowsProcAttrs(_ *exec.Cmd) {} +func applyLauncherProcAttrs(_ *exec.Cmd) {} diff --git a/web/backend/api/exec_windows.go b/web/backend/api/exec_windows.go index 1e76f8c73..86d3193a0 100644 --- a/web/backend/api/exec_windows.go +++ b/web/backend/api/exec_windows.go @@ -9,11 +9,11 @@ import ( func launcherExecCommand(name string, args ...string) *exec.Cmd { cmd := exec.Command(name, args...) - applyLauncherWindowsProcAttrs(cmd) + applyLauncherProcAttrs(cmd) return cmd } -func applyLauncherWindowsProcAttrs(cmd *exec.Cmd) { +func applyLauncherProcAttrs(cmd *exec.Cmd) { if cmd == nil { return } diff --git a/web/backend/api/gateway.go b/web/backend/api/gateway.go index d3e5ae1d5..606c8351d 100644 --- a/web/backend/api/gateway.go +++ b/web/backend/api/gateway.go @@ -706,7 +706,7 @@ func (h *Handler) startGatewayLocked(initialStatus string, existingPid int) (int logger.InfoC("gateway", fmt.Sprintf("Starting gateway process (%s)", execPath)) cmd = gatewayExecCommand(execPath, h.gatewayCommandArgs()...) - applyLauncherWindowsProcAttrs(cmd) + applyLauncherProcAttrs(cmd) cmd.Env = os.Environ() // Forward the launcher's config path via the environment variable that // GetConfigPath() already reads, so the gateway sub-process uses the same