Spaces:
Running
Running
| /** | |
| * watch-ext.js | |
| * Watches src/ and package.json for changes, then automatically: | |
| * 1. Compiles TypeScript (npm run compile) | |
| * 2. Packages the extension (vsce package) | |
| * 3. Reinstalls into VS Code (code --install-extension) | |
| * | |
| * Usage: | |
| * node scripts/watch-ext.js | |
| * npm run dev:ext | |
| */ | |
| ; | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { execSync, spawn } = require('child_process'); | |
| const ROOT = path.resolve(__dirname, '..'); | |
| const WATCH_DIRS = [path.join(ROOT, 'src')]; | |
| const WATCH_FILES = [path.join(ROOT, 'package.json')]; | |
| /** Read package.json fresh each time β publisher/name may change between builds */ | |
| function readPkg() { | |
| const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')); | |
| return { | |
| extId: `${PKG.publisher}.${PKG.name}`, | |
| vsix: `${PKG.name}-${PKG.version}.vsix` | |
| }; | |
| } | |
| const DEBOUNCE_MS = 800; | |
| let debounceTimer = null; | |
| let building = false; | |
| // ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function log(msg) { | |
| console.log(`[watch-ext] ${new Date().toLocaleTimeString()} ${msg}`); | |
| } | |
| function run(cmd, label, { silent = false } = {}) { | |
| if (!silent) log(`βΆ ${label}`); | |
| try { | |
| execSync(cmd, { cwd: ROOT, stdio: silent ? 'pipe' : 'inherit' }); | |
| return true; | |
| } catch { | |
| if (!silent) log(`β ${label} failed`); | |
| return false; | |
| } | |
| } | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // ββ Build pipeline ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function buildAndReinstall(changedFile) { | |
| if (building) { | |
| log(`(skipping β build already in progress)`); | |
| return; | |
| } | |
| building = true; | |
| // Re-read package.json so publisher/version changes are picked up immediately | |
| const { extId, vsix } = readPkg(); | |
| console.log('\n' + 'β'.repeat(60)); | |
| log(`Change detected: ${path.relative(ROOT, changedFile)}`); | |
| const ok1 = run('npm run compile', 'TypeScript compile'); | |
| if (!ok1) { building = false; return; } | |
| const ok2 = run(`npx @vscode/vsce package --allow-missing-repository --no-update-package-json`, 'vsce package'); | |
| if (!ok2) { building = false; return; } | |
| // Uninstall first β silent, non-fatal (extension may not be installed yet) | |
| run(`code --uninstall-extension ${extId}`, `Uninstall ${extId}`, { silent: true }); | |
| // Brief pause so VS Code finishes cleanup before the reinstall | |
| await sleep(1500); | |
| const ok3 = run(`code --install-extension ${vsix}`, 'Install .vsix'); | |
| if (ok3) { | |
| log('β Done β reload the Extension Development Host window (Ctrl+R / Cmd+R) to pick up changes.'); | |
| } | |
| console.log('β'.repeat(60) + '\n'); | |
| building = false; | |
| } | |
| // ββ Watcher βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function scheduleRebuild(filePath) { | |
| clearTimeout(debounceTimer); | |
| debounceTimer = setTimeout(() => buildAndReinstall(filePath), DEBOUNCE_MS); | |
| } | |
| function watchDir(dir) { | |
| if (!fs.existsSync(dir)) return; | |
| fs.watch(dir, { recursive: true }, (eventType, filename) => { | |
| if (!filename) return; | |
| // Only care about TypeScript source files | |
| if (!filename.endsWith('.ts')) return; | |
| scheduleRebuild(path.join(dir, filename)); | |
| }); | |
| log(`Watching ${path.relative(ROOT, dir)}/**/*.ts`); | |
| } | |
| function watchFile(filePath) { | |
| if (!fs.existsSync(filePath)) return; | |
| fs.watch(filePath, () => scheduleRebuild(filePath)); | |
| log(`Watching ${path.relative(ROOT, filePath)}`); | |
| } | |
| // ββ Entry point βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| log('Starting extension watcherβ¦'); | |
| const { extId: startExtId, vsix: startVsix } = readPkg(); | |
| log(`Extension ID: ${startExtId}`); | |
| log(`VSIX target: ${startVsix}`); | |
| console.log(''); | |
| WATCH_DIRS.forEach(watchDir); | |
| WATCH_FILES.forEach(watchFile); | |
| // Do an initial build so the installed extension is always in sync on start | |
| buildAndReinstall(path.join(ROOT, 'src', 'extension.ts')); | |
| process.on('SIGINT', () => { | |
| log('Stopped.'); | |
| process.exit(0); | |
| }); | |