/** * Watch-build for client-plugin HMR: runs every dshClient plugin package * through the tsdown JS API in watch mode. Reload signaling is not this * script's business — the host webserver stat-polls the bundles it serves and * broadcasts `rebuilt` frames itself (`dsh web --dev`), so any process that * rewrites `lib/client.js` files triggers reloads; this script is merely the * convenient way to keep them all rebuilt on source change. * * Usage: `pnpm exec tsx scripts/dev-web.ts [--poll[=ms]]`. Requires the * packages' node halves built once (`tsc -b tsconfig.build.json`): the lib * config's entries are tsc output. `--poll` switches the source-file watcher * to polling (default 500ms): network mounts (weka) deliver no inotify * events, so native watching sees the initial build only and never a source * change. * * Each package keeps its own tsdown.config.ts untouched: this script layers * `watch` through API-level inline config (tsdown workspace mode fills inline * keys under each package's file config, and no package config defines it). */ import { globSync, readFileSync } from 'node:fs' import { dirname, join, sep } from 'node:path' import { fileURLToPath } from 'node:url' import { build } from 'tsdown' const repoRoot = fileURLToPath(new URL('..', import.meta.url)) /** * Discover the watch workspace by declaration: every packages// * whose package.json carries `dshClient` with platform "web" is a client * plugin bundle emitter. Scanned once at startup — a package added while * watching means restarting this script. * @returns workspace-relative plugin package directories. */ function discoverPluginDirs(): string[] { const dirs: string[] = [] for (const manifestPath of globSync('packages/*/*/package.json', { cwd: repoRoot }).sort()) { const manifest = JSON.parse(readFileSync(join(repoRoot, manifestPath), 'utf8')) as { dshClient?: { platform?: unknown } } if (manifest.dshClient?.platform === 'web') dirs.push(dirname(manifestPath).split(sep).join('/')) } return dirs } const PLUGIN_DIRS = discoverPluginDirs() if (PLUGIN_DIRS.length === 0) { console.error('dev-web: no dshClient (platform "web") packages found under packages/') process.exit(1) } const args = process.argv.slice(2) const pollArg = args.find(a => a === '--poll' || a.startsWith('--poll=')) if (args.some(a => a !== pollArg)) { console.error('dev-web: usage: tsx scripts/dev-web.ts [--poll[=ms]]') process.exit(1) } const pollInterval = pollArg === undefined ? undefined : Number(pollArg.split('=')[1] ?? '500') if (pollInterval !== undefined && (!Number.isInteger(pollInterval) || pollInterval <= 0)) { console.error(`dev-web: invalid --poll interval "${pollArg ?? ''}"`) process.exit(1) } await build({ cwd: repoRoot, workspace: PLUGIN_DIRS, watch: true, // Rolldown watch options ride through inputOptions (tsdown has no watcher // tuning of its own); polling is opt-in for network mounts without inotify. ...pollInterval !== undefined ? { inputOptions: { watch: { watcher: { usePolling: true, pollInterval } } } } : {}, }) console.log( `dev-web: watching ${String(PLUGIN_DIRS.length)} dshClient plugin packages` + `${pollInterval !== undefined ? ` (polling ${String(pollInterval)}ms)` : ''}:\n ${PLUGIN_DIRS.join('\n ')}`, )