| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- #!/usr/bin/env node
- import { execSync, spawn } from "child_process"
- import chokidar from "chokidar"
- import path from "path"
- import { fileURLToPath } from "url"
- const __filename = fileURLToPath(import.meta.url)
- const __dirname = path.dirname(__filename)
- const projectRoot = path.resolve(__dirname, "..")
- // ANSI color codes
- const colors = {
- reset: "\x1b[0m",
- bright: "\x1b[1m",
- dim: "\x1b[2m",
- green: "\x1b[32m",
- yellow: "\x1b[33m",
- blue: "\x1b[34m",
- red: "\x1b[31m",
- cyan: "\x1b[36m",
- }
- let isBuilding = false
- let debounceTimer = null
- let esbuildProcess = null
- let initialBuildDone = false
- console.log(`${colors.bright}${colors.cyan}🚀 Cline CLI Dev Watch Mode (Fast Incremental)${colors.reset}`)
- console.log(`${colors.dim}Starting initial build...${colors.reset}\n`)
- // Function to kill all CLI instances
- function killAllInstances() {
- try {
- execSync("./cli/bin/cline instance kill --all", {
- cwd: projectRoot,
- stdio: "pipe",
- })
- } catch (error) {
- // Ignore errors - instances might not be running
- }
- }
- // Function to start a new CLI instance
- function startNewInstance() {
- try {
- console.log(`${colors.blue}▶️ Starting new CLI instance...${colors.reset}`)
- const result = execSync("./cli/bin/cline instance new", {
- cwd: projectRoot,
- stdio: "pipe",
- encoding: "utf-8",
- })
- console.log(`${colors.green}✓ CLI instance started${colors.reset}`)
- console.log(`${colors.dim}${result.trim()}${colors.reset}\n`)
- } catch (error) {
- console.error(`${colors.red}✗ Failed to start instance: ${error.message}${colors.reset}\n`)
- }
- }
- // Function to rebuild Go CLI
- async function rebuildGo() {
- if (isBuilding) {
- return
- }
- isBuilding = true
- const startTime = Date.now()
- try {
- console.log(`${colors.cyan}🔨 Rebuilding Go CLI...${colors.reset}`)
- killAllInstances()
- // Just rebuild Go binaries (skip proto generation)
- execSync("cd cli && GO111MODULE=on go build -o bin/cline ./cmd/cline", {
- cwd: projectRoot,
- stdio: "inherit",
- shell: true,
- })
- execSync("cd cli && GO111MODULE=on go build -o bin/cline-host ./cmd/cline-host", {
- cwd: projectRoot,
- stdio: "inherit",
- shell: true,
- })
- startNewInstance()
- const duration = ((Date.now() - startTime) / 1000).toFixed(2)
- console.log(`${colors.green}✓ Go rebuild complete in ${duration}s${colors.reset}`)
- console.log(`${colors.dim}Watching for changes...${colors.reset}\n`)
- } catch (error) {
- console.error(`${colors.red}✗ Go build failed: ${error.message}${colors.reset}\n`)
- } finally {
- isBuilding = false
- }
- }
- // Function to regenerate protos and rebuild everything
- async function rebuildProtos() {
- if (isBuilding) {
- return
- }
- isBuilding = true
- const startTime = Date.now()
- try {
- console.log(`${colors.cyan}🔨 Regenerating protos...${colors.reset}`)
- killAllInstances()
- // Regenerate protos
- execSync("npm run protos", { cwd: projectRoot, stdio: "inherit" })
- execSync("npm run protos-go", { cwd: projectRoot, stdio: "inherit" })
- // esbuild will auto-rebuild TS due to changed generated files
- // Rebuild Go CLI
- execSync("cd cli && GO111MODULE=on go build -o bin/cline ./cmd/cline", {
- cwd: projectRoot,
- stdio: "inherit",
- shell: true,
- })
- execSync("cd cli && GO111MODULE=on go build -o bin/cline-host ./cmd/cline-host", {
- cwd: projectRoot,
- stdio: "inherit",
- shell: true,
- })
- startNewInstance()
- const duration = ((Date.now() - startTime) / 1000).toFixed(2)
- console.log(`${colors.green}✓ Proto rebuild complete in ${duration}s${colors.reset}`)
- console.log(`${colors.dim}Watching for changes...${colors.reset}\n`)
- } catch (error) {
- console.error(`${colors.red}✗ Proto build failed: ${error.message}${colors.reset}\n`)
- } finally {
- isBuilding = false
- }
- }
- // Debounced rebuild trigger
- function triggerGoRebuild(filepath) {
- if (debounceTimer) {
- clearTimeout(debounceTimer)
- }
- debounceTimer = setTimeout(() => {
- const relativePath = path.relative(projectRoot, filepath)
- console.log(`${colors.dim}Go file changed: ${relativePath}${colors.reset}`)
- rebuildGo()
- }, 300)
- }
- function triggerProtoRebuild(filepath) {
- if (debounceTimer) {
- clearTimeout(debounceTimer)
- }
- debounceTimer = setTimeout(() => {
- const relativePath = path.relative(projectRoot, filepath)
- console.log(`${colors.dim}Proto file changed: ${relativePath}${colors.reset}`)
- rebuildProtos()
- }, 300)
- }
- // Initial build
- async function initialBuild() {
- try {
- // Run protos first
- console.log(`${colors.blue}📦 Generating protos...${colors.reset}`)
- execSync("npm run protos", { cwd: projectRoot, stdio: "inherit" })
- execSync("npm run protos-go", { cwd: projectRoot, stdio: "inherit" })
- // Build standalone (skip check-types and lint for speed)
- console.log(`${colors.blue}📦 Building standalone...${colors.reset}`)
- execSync("node esbuild.mjs --standalone", { cwd: projectRoot, stdio: "inherit" })
- // Build Go CLI
- console.log(`${colors.blue}🔧 Building Go CLI...${colors.reset}`)
- execSync("cd cli && GO111MODULE=on go build -o bin/cline ./cmd/cline", {
- cwd: projectRoot,
- stdio: "inherit",
- shell: true,
- })
- execSync("cd cli && GO111MODULE=on go build -o bin/cline-host ./cmd/cline-host", {
- cwd: projectRoot,
- stdio: "inherit",
- shell: true,
- })
- // Start CLI instance
- startNewInstance()
- console.log(`${colors.green}${colors.bright}✓ Initial build complete!${colors.reset}`)
- console.log(`${colors.cyan}Now watching for changes with fast incremental rebuilds...${colors.reset}\n`)
- initialBuildDone = true
- // Start esbuild in watch mode for TypeScript (incremental rebuilds)
- console.log(`${colors.dim}Starting esbuild watch mode...${colors.reset}`)
- esbuildProcess = spawn("node", ["esbuild.mjs", "--watch", "--standalone"], {
- cwd: projectRoot,
- stdio: ["inherit", "pipe", "inherit"], // Pipe stdout to parse it
- })
- // Parse esbuild output to detect when rebuild completes
- esbuildProcess.stdout.on("data", (data) => {
- const output = data.toString()
- // Forward esbuild output to console
- process.stdout.write(output)
- // Detect when esbuild finishes a rebuild
- if (output.includes("[watch] build finished") && initialBuildDone && !isBuilding) {
- console.log(`${colors.cyan}📦 TypeScript rebuilt by esbuild${colors.reset}`)
- killAllInstances()
- startNewInstance()
- }
- })
- esbuildProcess.on("error", (error) => {
- console.error(`${colors.red}esbuild error: ${error.message}${colors.reset}`)
- })
- } catch (error) {
- console.error(`${colors.red}✗ Initial build failed: ${error.message}${colors.reset}`)
- process.exit(1)
- }
- }
- // Watch Proto files (chokidar v4 - no glob support, watch directory and filter)
- const protoWatcher = chokidar.watch("proto", {
- ignored: (filepath, stats) => {
- // Ignore if it's a file but not a .proto file
- return stats?.isFile() && !filepath.endsWith(".proto")
- },
- persistent: true,
- ignoreInitial: true,
- cwd: projectRoot,
- awaitWriteFinish: {
- stabilityThreshold: 100,
- pollInterval: 50,
- },
- })
- protoWatcher
- .on("change", (filepath) => {
- if (initialBuildDone) {
- console.log(`${colors.dim}[DEBUG] Proto change event: ${filepath}${colors.reset}`)
- triggerProtoRebuild(path.join(projectRoot, filepath))
- }
- })
- .on("add", (filepath) => {
- if (initialBuildDone) {
- console.log(`${colors.dim}[DEBUG] Proto add event: ${filepath}${colors.reset}`)
- triggerProtoRebuild(path.join(projectRoot, filepath))
- }
- })
- // Watch Go files (chokidar v4 - no glob support, watch directory and filter)
- const goWatcher = chokidar.watch("cli", {
- ignored: (filepath, stats) => {
- // Ignore node_modules and non-.go files
- if (filepath.includes("node_modules")) return true
- return stats?.isFile() && !filepath.endsWith(".go")
- },
- persistent: true,
- ignoreInitial: true,
- cwd: projectRoot,
- awaitWriteFinish: {
- stabilityThreshold: 100,
- pollInterval: 50,
- },
- })
- goWatcher
- .on("change", (filepath) => {
- if (initialBuildDone) {
- console.log(`${colors.dim}[DEBUG] Go change event: ${filepath}${colors.reset}`)
- triggerGoRebuild(path.join(projectRoot, filepath))
- }
- })
- .on("add", (filepath) => {
- if (initialBuildDone) {
- console.log(`${colors.dim}[DEBUG] Go add event: ${filepath}${colors.reset}`)
- triggerGoRebuild(path.join(projectRoot, filepath))
- }
- })
- // Handle shutdown gracefully
- process.on("SIGINT", () => {
- console.log(`\n${colors.yellow}Shutting down...${colors.reset}`)
- if (esbuildProcess) {
- esbuildProcess.kill()
- }
- killAllInstances()
- process.exit(0)
- })
- process.on("SIGTERM", () => {
- console.log(`\n${colors.yellow}Shutting down...${colors.reset}`)
- if (esbuildProcess) {
- esbuildProcess.kill()
- }
- killAllInstances()
- process.exit(0)
- })
- // Start
- initialBuild()
|