Compare commits

...

2 Commits

Author SHA1 Message Date
Nikhil Sonti
8cd7c985e3 fix: address review feedback for PR #891 2026-04-30 12:54:44 -07:00
Nikhil Sonti
548c9dd996 feat(dev): bootstrap setup from dev watch 2026-04-30 12:49:25 -07:00
6 changed files with 164 additions and 12 deletions

View File

@@ -12,7 +12,7 @@
"dev:watch": "./tools/dev/run.sh watch",
"dev:watch:new": "./tools/dev/run.sh watch --new",
"dev:manual": "./tools/dev/run.sh watch --manual",
"dev:setup": "./tools/dev/setup.sh",
"dev:setup": "./tools/dev/run.sh setup",
"dev:cleanup": "./tools/dev/run.sh cleanup",
"dev:reset": "./tools/dev/run.sh reset",
"install:browseros-dogfood": "make -C tools/dogfood install",

View File

@@ -0,0 +1,81 @@
package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"browseros-dev/proc"
"github.com/spf13/cobra"
)
var setupIfNeeded bool
const setupModeIfNeeded = true
var setupCmd = &cobra.Command{
Use: "setup",
Short: "Install dev dependencies and generate required code",
Long: "Installs Bun dependencies and generates agent GraphQL code needed by the dev environment.",
RunE: func(cmd *cobra.Command, args []string) error {
root, err := proc.FindMonorepoRoot()
if err != nil {
return err
}
return runDevSetup(cmd.Context(), root, setupIfNeeded)
},
}
type setupPlan struct {
RunInstall bool
RunCodegen bool
}
func init() {
setupCmd.Flags().BoolVar(&setupIfNeeded, "if-needed", false, "Skip generated code refresh when it already exists")
rootCmd.AddCommand(setupCmd)
}
func buildSetupPlan(root string, ifNeeded bool) setupPlan {
return setupPlan{
RunInstall: true,
RunCodegen: !ifNeeded || !generatedGraphQLExists(root),
}
}
func generatedGraphQLExists(root string) bool {
for _, file := range []string{"gql.ts", "graphql.ts", "schema.graphql"} {
info, err := os.Stat(filepath.Join(root, "apps/agent/generated/graphql", file))
if err != nil || info.IsDir() {
return false
}
}
return true
}
// runDevSetup prepares the repo for local development. Dependency install always
// runs because Bun is fast and this keeps watch resilient after branch changes.
func runDevSetup(ctx context.Context, root string, ifNeeded bool) error {
plan := buildSetupPlan(root, ifNeeded)
if plan.RunInstall {
proc.LogMsg(proc.TagSetup, "Installing dependencies...")
if err := proc.RunBlocking(ctx, root, proc.TagSetup, "bun", "install", "--frozen-lockfile"); err != nil {
return fmt.Errorf("installing dependencies: %w", err)
}
}
if plan.RunCodegen {
proc.LogMsg(proc.TagSetup, "Generating agent code...")
if err := proc.RunBlocking(ctx, root, proc.TagSetup, "bun", "run", "codegen:agent"); err != nil {
return fmt.Errorf("generating agent code: %w", err)
}
} else {
proc.LogMsg(proc.TagSetup, "Agent code already generated")
}
proc.LogMsg(proc.TagSetup, "Setup ready")
return nil
}

View File

@@ -0,0 +1,76 @@
package cmd
import (
"os"
"path/filepath"
"testing"
)
func TestBuildSetupPlanAlwaysInstallsDependencies(t *testing.T) {
root := t.TempDir()
plan := buildSetupPlan(root, true)
if !plan.RunInstall {
t.Fatal("expected dependency install to always run")
}
}
func TestBuildSetupPlanIfNeededSkipsExistingGeneratedGraphQL(t *testing.T) {
root := t.TempDir()
writeGeneratedGraphQLSentinels(t, root)
plan := buildSetupPlan(root, true)
if plan.RunCodegen {
t.Fatal("expected --if-needed setup to skip codegen when generated GraphQL exists")
}
}
func TestBuildSetupPlanIfNeededRunsCodegenWhenGeneratedGraphQLEmpty(t *testing.T) {
root := t.TempDir()
generatedDir := filepath.Join(root, "apps/agent/generated/graphql")
if err := os.MkdirAll(generatedDir, 0o755); err != nil {
t.Fatal(err)
}
plan := buildSetupPlan(root, true)
if !plan.RunCodegen {
t.Fatal("expected --if-needed setup to run codegen when generated GraphQL is empty")
}
}
func TestBuildSetupPlanIfNeededRunsCodegenWhenGeneratedGraphQLMissing(t *testing.T) {
root := t.TempDir()
plan := buildSetupPlan(root, true)
if !plan.RunCodegen {
t.Fatal("expected --if-needed setup to run codegen when generated GraphQL is missing")
}
}
func TestBuildSetupPlanExplicitSetupRunsCodegen(t *testing.T) {
root := t.TempDir()
writeGeneratedGraphQLSentinels(t, root)
plan := buildSetupPlan(root, false)
if !plan.RunCodegen {
t.Fatal("expected explicit setup to refresh codegen")
}
}
func writeGeneratedGraphQLSentinels(t *testing.T, root string) {
t.Helper()
generatedDir := filepath.Join(root, "apps/agent/generated/graphql")
if err := os.MkdirAll(generatedDir, 0o755); err != nil {
t.Fatal(err)
}
for _, file := range []string{"gql.ts", "graphql.ts", "schema.graphql"} {
if err := os.WriteFile(filepath.Join(generatedDir, file), []byte("generated"), 0o644); err != nil {
t.Fatal(err)
}
}
}

View File

@@ -115,6 +115,10 @@ func runWatch(cmd *cobra.Command, args []string) error {
}()
defer reservations.ReleaseAll()
if err := runDevSetup(cmd.Context(), root, setupModeIfNeeded); err != nil {
return err
}
fmt.Println()
proc.LogMsgf(proc.TagInfo, "Mode: %s", proc.BoldColor.Sprint(mode))
proc.LogMsgf(proc.TagInfo, "Ports: CDP=%d Server=%d Extension=%d", p.CDP, p.Server, p.Extension)

View File

@@ -14,6 +14,7 @@ type Tag struct {
var (
TagBuild = Tag{"build", color.New(color.FgYellow)}
TagSetup = Tag{"setup", color.New(color.FgHiYellow)}
TagAgent = Tag{"agent", color.New(color.FgMagenta)}
TagServer = Tag{"server", color.New(color.FgCyan)}
TagBrowser = Tag{"browser", color.New(color.FgBlue)}

View File

@@ -2,14 +2,4 @@
set -euo pipefail
DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$DIR/../.." && pwd)"
cd "$ROOT"
echo "[setup] Installing dependencies..."
bun install --frozen-lockfile
echo "[setup] Generating agent code..."
bun run codegen:agent
echo "[setup] Ready"
exec "$DIR/run.sh" setup "$@"