import { applyStyle, Arg, Config } from '@boost/cli'; import { Bind, formatMs } from '@boost/common'; import { loadModule } from '../helpers/loadModule'; import { Package } from '../Package'; import { BaseCommand } from './Base'; export interface WatchOptions { debounce: number; poll: boolean; } @Config('watch', 'Watch local files for changes and rebuild') export class WatchCommand extends BaseCommand { @Arg.Number('Number of milliseconds to wait after a change before triggering a rebuild') debounce: number = 150; @Arg.Flag('Poll for file changes instead of using file system events') poll: boolean = false; protected package: Package | null = null; protected rebuild: boolean = false; protected rebuilding: boolean = false; protected rebuildTimer?: NodeJS.Timeout; @Bind() enqueueRebuild(event: string, path: string) { if (event !== 'add' && event !== 'change' && event !== 'unlink') { return; } this.log(applyStyle(' - %s', 'muted'), path.replace(`${this.packemon.workingDir.path()}/`, '')); this.triggerRebuild(); } async run() { const { packemon } = this; const chokidar = loadModule( 'chokidar', 'Chokidar is required for file watching.', ) as typeof import('chokidar'); packemon.debug('Starting `watch` process'); // Generate all our build artifacts this.package = await this.getPackage(); // Instantiate the watcher for each package source const watchPaths = this.package.path.append('src/**/*').path(); packemon.debug('Initializing chokidar watcher for paths:'); packemon.debug(watchPaths); const watcher = chokidar.watch(watchPaths, { ignored: /(^|[/\\])\../u, // dotfiles ignoreInitial: true, persistent: true, usePolling: this.poll, }); // Rebuild when files change watcher.on('all', this.enqueueRebuild); this.log('Watching for changes...'); } triggerRebuild() { if (this.rebuildTimer) { clearTimeout(this.rebuildTimer); } this.rebuildTimer = setTimeout(() => { void this.rebuildPackage(); }, this.debounce); } async rebuildPackage() { if (this.rebuilding) { this.triggerRebuild(); return; } if (!this.package) { return; } this.rebuilding = true; try { const start = Date.now(); await this.packemon.build(this.package, { loadConfigs: this.loadConfigs, }); this.log( applyStyle('Built %s in %s', 'success'), this.package.getName(), formatMs(Date.now() - start), ); } catch (error: unknown) { if (error instanceof Error) { this.log.error(error.message); } this.log(applyStyle('Failed to build %s', 'failure'), this.package.getName()); } finally { this.rebuilding = false; } } }