| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- /**
- * 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/<group>/<name>
- * 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 ')}`,
- )
|