| package webui |
|
|
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "strings" |
| "time" |
|
|
| "ds2api/internal/config" |
| ) |
|
|
| const ( |
| defaultBuildTimeout = 5 * time.Minute |
| ) |
|
|
| func EnsureBuiltOnStartup() { |
| if !shouldAutoBuild() { |
| return |
| } |
| staticDir := resolveStaticAdminDir(config.StaticAdminDir()) |
| if hasBuiltUI(staticDir) { |
| return |
| } |
| if err := buildWebUI(staticDir); err != nil { |
| config.Logger.Warn("[webui] auto build failed", "error", err) |
| return |
| } |
| if hasBuiltUI(staticDir) { |
| config.Logger.Info("[webui] auto build completed", "dir", staticDir) |
| return |
| } |
| config.Logger.Warn("[webui] auto build finished but output missing", "dir", staticDir) |
| } |
|
|
| func shouldAutoBuild() bool { |
| raw := strings.TrimSpace(os.Getenv("DS2API_AUTO_BUILD_WEBUI")) |
| if raw == "" { |
| return !config.IsVercel() |
| } |
| switch strings.ToLower(raw) { |
| case "1", "true", "yes", "on": |
| return true |
| case "0", "false", "no", "off": |
| return false |
| default: |
| return !config.IsVercel() |
| } |
| } |
|
|
| func hasBuiltUI(staticDir string) bool { |
| if strings.TrimSpace(staticDir) == "" { |
| return false |
| } |
| indexPath := filepath.Join(staticDir, "index.html") |
| st, err := os.Stat(indexPath) |
| return err == nil && !st.IsDir() |
| } |
|
|
| func buildWebUI(staticDir string) error { |
| if _, err := exec.LookPath("npm"); err != nil { |
| return fmt.Errorf("npm not found in PATH: %w", err) |
| } |
| if strings.TrimSpace(staticDir) == "" { |
| return errors.New("static admin dir is empty") |
| } |
|
|
| config.Logger.Info("[webui] static files missing, running npm build") |
| ctx, cancel := context.WithTimeout(context.Background(), defaultBuildTimeout) |
| defer cancel() |
|
|
| if _, err := os.Stat(filepath.Join("webui", "node_modules")); err != nil { |
| if !os.IsNotExist(err) { |
| return err |
| } |
| installCmd := exec.CommandContext(ctx, "npm", "ci", "--prefix", "webui") |
| installCmd.Stdout = os.Stdout |
| installCmd.Stderr = os.Stderr |
| if err := installCmd.Run(); err != nil { |
| if errors.Is(ctx.Err(), context.DeadlineExceeded) { |
| return fmt.Errorf("webui npm ci timed out after %s", defaultBuildTimeout) |
| } |
| return err |
| } |
| } |
|
|
| if err := os.MkdirAll(staticDir, 0o755); err != nil { |
| return err |
| } |
| cmd := exec.CommandContext(ctx, "npm", "run", "build", "--prefix", "webui", "--", "--outDir", staticDir, "--emptyOutDir") |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| if err := cmd.Run(); err != nil { |
| if errors.Is(ctx.Err(), context.DeadlineExceeded) { |
| return fmt.Errorf("webui build timed out after %s", defaultBuildTimeout) |
| } |
| return err |
| } |
| return nil |
| } |
|
|