| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- import { spawn, execSync, type ChildProcess } from "child_process"
- import * as path from "path"
- import * as fs from "fs"
- import { fileURLToPath } from "url"
- import { glob } from "glob"
- // @ts-expect-error - TS1470: We only run this script with tsx so it will never
- // compile to CJS and it's safe to ignore this tsc error.
- const __filename = fileURLToPath(import.meta.url)
- const __dirname = path.dirname(__filename)
- interface PackageConfig {
- readonly name: string
- readonly sourcePath: string
- readonly targetPaths: readonly string[]
- readonly replacePath?: string
- readonly npmPath: string
- readonly watchCommand?: string
- readonly watchOutput?: {
- readonly start: string[]
- readonly stop: string[]
- }
- }
- interface Config {
- readonly packages: readonly PackageConfig[]
- }
- interface WatcherResult {
- child: ChildProcess
- }
- interface NpmPackage {
- name?: string
- version?: string
- type: "module"
- dependencies: Record<string, string>
- main: string
- module: string
- types: string
- exports: {
- ".": {
- types: string
- import: string
- require: {
- types: string
- default: string
- }
- }
- }
- files: string[]
- }
- const config: Config = {
- packages: [
- {
- name: "@roo-code/cloud",
- sourcePath: "../Roo-Code-Cloud/packages/sdk",
- targetPaths: ["src/node_modules/@roo-code/cloud"] as const,
- replacePath: "node_modules/.pnpm/@roo-code+cloud*",
- npmPath: "npm",
- watchCommand: "pnpm build:development:watch",
- watchOutput: {
- start: ["CLI Building", "CLI Change detected"],
- stop: ["DTS ⚡️ Build success"],
- },
- },
- ],
- } as const
- const args = process.argv.slice(2)
- const packageName = args.find((arg) => !arg.startsWith("--"))
- const watchMode = !args.includes("--no-watch")
- const unlink = args.includes("--unlink")
- const packages: readonly PackageConfig[] = packageName
- ? config.packages.filter((p) => p.name === packageName)
- : config.packages
- if (!packages.length) {
- console.error(`Package '${packageName}' not found`)
- process.exit(1)
- }
- function pathExists(filePath: string): boolean {
- try {
- fs.accessSync(filePath)
- return true
- } catch {
- return false
- }
- }
- function copyRecursiveSync(src: string, dest: string): void {
- const exists = pathExists(src)
- if (!exists) {
- return
- }
- const stats = fs.statSync(src)
- const isDirectory = stats.isDirectory()
- if (isDirectory) {
- if (!pathExists(dest)) {
- fs.mkdirSync(dest, { recursive: true })
- }
- const children = fs.readdirSync(src)
- children.forEach((childItemName) => {
- copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName))
- })
- } else {
- fs.copyFileSync(src, dest)
- }
- }
- function generateNpmPackageJson(sourcePath: string, npmPath: string): string {
- const npmDir = path.join(sourcePath, npmPath)
- const npmPackagePath = path.join(npmDir, "package.json")
- const npmMetadataPath = path.join(npmDir, "package.metadata.json")
- const monorepoPackagePath = path.join(sourcePath, "package.json")
- if (pathExists(npmPackagePath)) {
- return npmPackagePath
- }
- if (!pathExists(npmMetadataPath)) {
- throw new Error(`No package.metadata.json found in ${npmDir}`)
- }
- const monorepoPackageContent = fs.readFileSync(monorepoPackagePath, "utf8")
- const monorepoPackage = JSON.parse(monorepoPackageContent) as {
- dependencies?: Record<string, string>
- }
- const npmMetadataContent = fs.readFileSync(npmMetadataPath, "utf8")
- const npmMetadata = JSON.parse(npmMetadataContent) as Partial<NpmPackage>
- const npmPackage: NpmPackage = {
- ...npmMetadata,
- type: "module",
- dependencies: monorepoPackage.dependencies || {},
- main: "./dist/index.cjs",
- module: "./dist/index.js",
- types: "./dist/index.d.ts",
- exports: {
- ".": {
- types: "./dist/index.d.ts",
- import: "./dist/index.js",
- require: {
- types: "./dist/index.d.cts",
- default: "./dist/index.cjs",
- },
- },
- },
- files: ["dist"],
- }
- fs.writeFileSync(npmPackagePath, JSON.stringify(npmPackage, null, 2) + "\n")
- return npmPackagePath
- }
- function linkPackage(pkg: PackageConfig): void {
- const sourcePath = path.resolve(__dirname, "..", pkg.sourcePath)
- if (!pathExists(sourcePath)) {
- console.error(`❌ Source not found: ${sourcePath}`)
- process.exit(1)
- }
- generateNpmPackageJson(sourcePath, pkg.npmPath)
- for (const currentTargetPath of pkg.targetPaths) {
- const targetPath = path.resolve(__dirname, "..", currentTargetPath)
- if (pathExists(targetPath)) {
- fs.rmSync(targetPath, { recursive: true, force: true })
- }
- const parentDir = path.dirname(targetPath)
- fs.mkdirSync(parentDir, { recursive: true })
- const linkSource = pkg.npmPath ? path.join(sourcePath, pkg.npmPath) : sourcePath
- copyRecursiveSync(linkSource, targetPath)
- }
- }
- function unlinkPackage(pkg: PackageConfig): void {
- for (const currentTargetPath of pkg.targetPaths) {
- const targetPath = path.resolve(__dirname, "..", currentTargetPath)
- if (pathExists(targetPath)) {
- fs.rmSync(targetPath, { recursive: true, force: true })
- console.log(`🗑️ Removed ${pkg.name} from ${currentTargetPath}`)
- }
- }
- }
- function startWatch(pkg: PackageConfig): WatcherResult {
- if (!pkg.watchCommand) {
- throw new Error(`Package ${pkg.name} has no watch command configured`)
- }
- const commandParts = pkg.watchCommand.split(" ")
- const [cmd, ...args] = commandParts
- if (!cmd) {
- throw new Error(`Invalid watch command for ${pkg.name}`)
- }
- console.log(`👀 Watching for changes to ${pkg.sourcePath} with ${cmd} ${args.join(" ")}`)
- const child = spawn(cmd, args, {
- cwd: path.resolve(__dirname, "..", pkg.sourcePath),
- stdio: "pipe",
- shell: true,
- })
- let debounceTimer: NodeJS.Timeout | null = null
- const DEBOUNCE_DELAY = 500
- if (child.stdout) {
- child.stdout.on("data", (data: Buffer) => {
- const output = data.toString()
- const isStarting = pkg.watchOutput?.start.some((start) => output.includes(start))
- const isDone = pkg.watchOutput?.stop.some((stop) => output.includes(stop))
- if (isStarting) {
- console.log(`🔨 Building ${pkg.name}...`)
- if (debounceTimer) {
- clearTimeout(debounceTimer)
- debounceTimer = null
- }
- }
- if (isDone) {
- console.log(`✅ Built ${pkg.name}`)
- if (debounceTimer) {
- clearTimeout(debounceTimer)
- }
- debounceTimer = setTimeout(() => {
- linkPackage(pkg)
- console.log(`♻️ Copied ${pkg.name} to ${pkg.targetPaths.length} paths\n`)
- debounceTimer = null
- }, DEBOUNCE_DELAY)
- }
- })
- }
- if (child.stderr) {
- child.stderr.on("data", (data: Buffer) => {
- console.log(`❌ "${data.toString()}"`)
- })
- }
- return { child }
- }
- function main(): void {
- if (unlink) {
- packages.forEach(unlinkPackage)
- console.log("\n📦 Restoring npm packages...")
- try {
- execSync("pnpm install", { cwd: __dirname, stdio: "ignore" })
- console.log("✅ npm packages restored")
- } catch (error) {
- console.error(`❌ Failed to restore packages: ${error instanceof Error ? error.message : String(error)}`)
- console.log(" Run 'pnpm install' manually if needed")
- }
- } else {
- packages.forEach((pkg) => {
- linkPackage(pkg)
- if (pkg.replacePath) {
- const replacePattern = path.resolve(__dirname, "..", pkg.replacePath)
- try {
- const matchedPaths = glob.sync(replacePattern)
- if (matchedPaths.length > 0) {
- matchedPaths.forEach((matchedPath: string) => {
- if (pathExists(matchedPath)) {
- fs.rmSync(matchedPath, { recursive: true, force: true })
- console.log(`🗑️ Removed ${pkg.name} from ${matchedPath}`)
- }
- })
- } else {
- if (pathExists(replacePattern)) {
- fs.rmSync(replacePattern, { recursive: true, force: true })
- console.log(`🗑️ Removed ${pkg.name} from ${replacePattern}`)
- }
- }
- } catch (error) {
- console.error(
- `❌ Error processing replace path: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
- }
- })
- if (watchMode) {
- const packagesWithWatch = packages.filter(
- (pkg): pkg is PackageConfig & { watchCommand: string } => pkg.watchCommand !== undefined,
- )
- const watchers = packagesWithWatch.map(startWatch)
- if (watchers.length > 0) {
- process.on("SIGINT", () => {
- console.log("\n👋 Stopping watchers...")
- watchers.forEach((w) => {
- if (w.child) {
- w.child.kill()
- }
- })
- process.exit(0)
- })
- }
- }
- }
- }
- main()
|