diff --git a/package.json b/package.json index 5e73f0c479e..c1ddc682234 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "changeset:version": "cp CHANGELOG.md src/CHANGELOG.md && changeset version && cp -vf src/CHANGELOG.md .", "knip": "knip --include files", "update-contributors": "node scripts/update-contributors.js", - "evals": "dotenvx run -f packages/evals/.env.development packages/evals/.env.local -- docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0" + "evals": "dotenvx run -f packages/evals/.env.development packages/evals/.env.local -- docker compose -f packages/evals/docker-compose.yml --profile server --profile runner up --build --scale runner=0", + "link-workspace-packages": "node scripts/link-packages.js", + "unlink-workspace-packages": "node scripts/link-packages.js --unlink" }, "devDependencies": { "@changesets/cli": "^2.27.10", diff --git a/scripts/link-packages.js b/scripts/link-packages.js new file mode 100755 index 00000000000..60ef3be8653 --- /dev/null +++ b/scripts/link-packages.js @@ -0,0 +1,105 @@ +#!/usr/bin/env node + +const { spawn, execSync } = require("child_process") +const path = require("path") +const fs = require("fs") + +// Package configuration - Add new packages here as needed. +const config = { + packages: [ + { + name: "@roo-code/cloud", + sourcePath: "../Roo-Code-Cloud/packages/sdk", + targetPath: "src/node_modules/@roo-code/cloud", + npmPath: "npm", + watchCommand: "pnpm build:development:watch", + }, + ], +} + +const args = process.argv.slice(2) +const packageName = args.find((arg) => !arg.startsWith("--")) +const watch = !args.includes("--no-watch") +const unlink = args.includes("--unlink") + +const packages = packageName ? config.packages.filter((p) => p.name === packageName) : config.packages + +if (!packages.length) { + console.error(`Package '${packageName}' not found`) + process.exit(1) +} + +packages.forEach(unlink ? unlinkPackage : linkPackage) + +// After unlinking, restore npm packages with a single pnpm install. +if (unlink && packages.length > 0) { + const srcPath = path.resolve(__dirname, "..", "src") + console.log("\nRestoring npm packages...") + + try { + execSync("pnpm install", { cwd: srcPath, stdio: "inherit" }) + console.log("Successfully restored npm packages") + } catch (error) { + console.error(`Failed to restore packages: ${error.message}`) + console.log("You may need to run 'pnpm install' manually in the src directory") + } +} + +if (!unlink && watch) { + const watchers = packages.filter((pkg) => pkg.watchCommand).map(startWatch) + + if (watchers.length) { + process.on("SIGINT", () => { + console.log("\nStopping...") + watchers.forEach((w) => w.kill()) + process.exit(0) + }) + console.log("\nWatching for changes. Press Ctrl+C to stop.\n") + } +} + +function linkPackage(pkg) { + const sourcePath = path.resolve(__dirname, "..", pkg.sourcePath) + const targetPath = path.resolve(__dirname, "..", pkg.targetPath) + + if (!fs.existsSync(sourcePath)) { + console.error(`Source not found: ${sourcePath}`) + process.exit(1) + } + + // Install dependencies if needed. + if (!fs.existsSync(path.join(sourcePath, "node_modules"))) { + console.log(`Installing dependencies for ${pkg.name}...`) + + try { + execSync("pnpm install", { cwd: sourcePath, stdio: "inherit" }) + } catch (e) { + execSync("pnpm install --no-frozen-lockfile", { cwd: sourcePath, stdio: "inherit" }) + } + } + + // Create symlink. + fs.rmSync(targetPath, { recursive: true, force: true }) + fs.mkdirSync(path.dirname(targetPath), { recursive: true }) + const linkSource = pkg.npmPath ? path.join(sourcePath, pkg.npmPath) : sourcePath + fs.symlinkSync(linkSource, targetPath, "dir") + console.log(`Linked ${pkg.name}`) +} + +function unlinkPackage(pkg) { + const targetPath = path.resolve(__dirname, "..", pkg.targetPath) + if (fs.existsSync(targetPath)) { + fs.rmSync(targetPath, { recursive: true, force: true }) + console.log(`Unlinked ${pkg.name}`) + } +} + +function startWatch(pkg) { + console.log(`Watching ${pkg.name}...`) + const [cmd, ...args] = pkg.watchCommand.split(" ") + return spawn(cmd, args, { + cwd: path.resolve(__dirname, "..", pkg.sourcePath), + stdio: "inherit", + shell: true, + }) +} diff --git a/src/extension.ts b/src/extension.ts index 1fee81a4823..2d95902295d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -207,28 +207,54 @@ export async function activate(context: vscode.ExtensionContext) { // Watch the core files and automatically reload the extension host. if (process.env.NODE_ENV === "development") { - const pattern = "**/*.ts" - const watchPaths = [ - { path: context.extensionPath, name: "extension" }, - { path: path.join(context.extensionPath, "../packages/types"), name: "types" }, - { path: path.join(context.extensionPath, "../packages/telemetry"), name: "telemetry" }, + { path: context.extensionPath, pattern: "**/*.ts" }, + { path: path.join(context.extensionPath, "../packages/types"), pattern: "**/*.ts" }, + { path: path.join(context.extensionPath, "../packages/telemetry"), pattern: "**/*.ts" }, + { path: path.join(context.extensionPath, "node_modules/@roo-code/cloud"), pattern: "**/*" }, ] console.log( - `♻️♻️♻️ Core auto-reloading is ENABLED. Watching for changes in: ${watchPaths.map(({ name }) => name).join(", ")}`, + `♻️♻️♻️ Core auto-reloading: Watching for changes in ${watchPaths.map(({ path }) => path).join(", ")}`, ) - watchPaths.forEach(({ path: watchPath, name }) => { - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(watchPath, pattern)) + // Create a debounced reload function to prevent excessive reloads + let reloadTimeout: NodeJS.Timeout | undefined + const DEBOUNCE_DELAY = 1_000 + + const debouncedReload = (uri: vscode.Uri) => { + if (reloadTimeout) { + clearTimeout(reloadTimeout) + } - watcher.onDidChange((uri) => { - console.log(`♻️ ${name} file changed: ${uri.fsPath}. Reloading host…`) + console.log(`♻️ ${uri.fsPath} changed; scheduling reload...`) + + reloadTimeout = setTimeout(() => { + console.log(`♻️ Reloading host after debounce delay...`) vscode.commands.executeCommand("workbench.action.reloadWindow") - }) + }, DEBOUNCE_DELAY) + } + + watchPaths.forEach(({ path: watchPath, pattern }) => { + const relPattern = new vscode.RelativePattern(vscode.Uri.file(watchPath), pattern) + const watcher = vscode.workspace.createFileSystemWatcher(relPattern, false, false, false) + + // Listen to all change types to ensure symlinked file updates trigger reloads. + watcher.onDidChange(debouncedReload) + watcher.onDidCreate(debouncedReload) + watcher.onDidDelete(debouncedReload) context.subscriptions.push(watcher) }) + + // Clean up the timeout on deactivation + context.subscriptions.push({ + dispose: () => { + if (reloadTimeout) { + clearTimeout(reloadTimeout) + } + }, + }) } return new API(outputChannel, provider, socketPath, enableLogging) diff --git a/src/tsconfig.json b/src/tsconfig.json index 6b7158c4ab3..90bdb860cd0 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -21,5 +21,12 @@ "useUnknownInCatchVariables": false }, "include": ["."], - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "watchOptions": { + "watchFile": "useFsEvents", + "watchDirectory": "useFsEvents", + "fallbackPolling": "dynamicPriority", + "synchronousWatchDirectory": true, + "excludeDirectories": ["**/node_modules", "**/dist", "**/.turbo"] + } }