Files
cline/scripts/dev-cli-watch.mjs
T
celestial-vault 541b51c0fb Add watch build script (#6655)
* add dev command for cline-core changes for cli

* add cli watch build command

* remove other script
2025-10-06 11:53:15 -07:00

307 lines
8.5 KiB
JavaScript
Executable File

#!/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()