@@ -6,12 +6,15 @@ import { task } from "hereby";
66import _glob from "glob" ;
77import util from "util" ;
88import chalk from "chalk" ;
9- import { exec , readJson , getDiffTool , getDirSize , memoize , needsUpdate } from "./scripts/build/utils.mjs" ;
10- import { runConsoleTests , refBaseline , localBaseline , refRwcBaseline , localRwcBaseline } from "./scripts/build/tests.mjs" ;
9+ import { exec , readJson , getDiffTool , getDirSize , memoize , needsUpdate , Debouncer , Deferred } from "./scripts/build/utils.mjs" ;
10+ import { runConsoleTests , refBaseline , localBaseline , refRwcBaseline , localRwcBaseline , cleanTestDirs } from "./scripts/build/tests.mjs" ;
1111import { buildProject as realBuildProject , cleanProject , watchProject } from "./scripts/build/projects.mjs" ;
1212import { localizationDirectories } from "./scripts/build/localization.mjs" ;
1313import cmdLineOptions from "./scripts/build/options.mjs" ;
1414import esbuild from "esbuild" ;
15+ import chokidar from "chokidar" ;
16+ import { EventEmitter } from "events" ;
17+ import { CancelToken } from "@esfx/canceltoken" ;
1518
1619const glob = util . promisify ( _glob ) ;
1720
@@ -191,6 +194,7 @@ async function runDtsBundler(entrypoint, output) {
191194 * @property {string[] } [external]
192195 * @property {boolean } [exportIsTsObject]
193196 * @property {boolean } [treeShaking]
197+ * @property {esbuild.WatchMode } [watchMode]
194198 */
195199function createBundler ( entrypoint , outfile , taskOptions = { } ) {
196200 const getOptions = memoize ( async ( ) => {
@@ -269,7 +273,7 @@ function createBundler(entrypoint, outfile, taskOptions = {}) {
269273
270274 return {
271275 build : async ( ) => esbuild . build ( await getOptions ( ) ) ,
272- watch : async ( ) => esbuild . build ( { ...await getOptions ( ) , watch : true , logLevel : "info" } ) ,
276+ watch : async ( ) => esbuild . build ( { ...await getOptions ( ) , watch : taskOptions . watchMode ?? true , logLevel : "info" } ) ,
273277 } ;
274278}
275279
@@ -461,7 +465,7 @@ export const dts = task({
461465
462466
463467const testRunner = "./built/local/run.js" ;
464-
468+ const watchTestsEmitter = new EventEmitter ( ) ;
465469const { main : tests , watch : watchTests } = entrypointBuildTask ( {
466470 name : "tests" ,
467471 description : "Builds the test infrastructure" ,
@@ -482,6 +486,11 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({
482486 "mocha" ,
483487 "ms" ,
484488 ] ,
489+ watchMode : {
490+ onRebuild ( ) {
491+ watchTestsEmitter . emit ( "rebuild" ) ;
492+ }
493+ }
485494 } ,
486495} ) ;
487496export { tests , watchTests } ;
@@ -625,6 +634,117 @@ export const runTests = task({
625634// " --shardId": "1-based ID of this shard (default: 1)",
626635// };
627636
637+ export const runTestsAndWatch = task ( {
638+ name : "runtests-watch" ,
639+ dependencies : [ watchTests ] ,
640+ run : async ( ) => {
641+ if ( ! cmdLineOptions . tests && ! cmdLineOptions . failed ) {
642+ console . log ( chalk . redBright ( `You must specifiy either --tests/-t or --failed to use 'runtests-watch'.` ) ) ;
643+ return ;
644+ }
645+
646+ let watching = true ;
647+ let running = true ;
648+ let lastTestChangeTimeMs = Date . now ( ) ;
649+ let testsChangedDeferred = /** @type {Deferred<void> } */ ( new Deferred ( ) ) ;
650+ let testsChangedCancelSource = CancelToken . source ( ) ;
651+
652+ const testsChangedDebouncer = new Debouncer ( 1_000 , endRunTests ) ;
653+ const testCaseWatcher = chokidar . watch ( [
654+ "tests/cases/**/*.*" ,
655+ "tests/lib/**/*.*" ,
656+ "tests/projects/**/*.*" ,
657+ ] , {
658+ ignorePermissionErrors : true ,
659+ alwaysStat : true
660+ } ) ;
661+
662+ process . on ( "SIGINT" , endWatchMode ) ;
663+ process . on ( "SIGKILL" , endWatchMode ) ;
664+ process . on ( "beforeExit" , endWatchMode ) ;
665+ watchTestsEmitter . on ( "rebuild" , onRebuild ) ;
666+ testCaseWatcher . on ( "all" , onChange ) ;
667+
668+ while ( watching ) {
669+ const promise = testsChangedDeferred . promise ;
670+ const token = testsChangedCancelSource . token ;
671+ if ( ! token . signaled ) {
672+ running = true ;
673+ try {
674+ await runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false , { token, watching : true } ) ;
675+ }
676+ catch {
677+ // ignore
678+ }
679+ running = false ;
680+ }
681+ if ( watching ) {
682+ console . log ( chalk . yellowBright ( `[watch] test run complete, waiting for changes...` ) ) ;
683+ await promise ;
684+ }
685+ }
686+
687+ function onRebuild ( ) {
688+ beginRunTests ( testRunner ) ;
689+ }
690+
691+ /**
692+ * @param {'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir' } eventName
693+ * @param {string } path
694+ * @param {fs.Stats | undefined } stats
695+ */
696+ function onChange ( eventName , path , stats ) {
697+ switch ( eventName ) {
698+ case "change" :
699+ case "unlink" :
700+ case "unlinkDir" :
701+ break ;
702+ case "add" :
703+ case "addDir" :
704+ // skip files that are detected as 'add' but haven't actually changed since the last time tests were
705+ // run.
706+ if ( stats && stats . mtimeMs <= lastTestChangeTimeMs ) {
707+ return ;
708+ }
709+ break ;
710+ }
711+ beginRunTests ( path ) ;
712+ }
713+
714+ /**
715+ * @param {string } path
716+ */
717+ function beginRunTests ( path ) {
718+ if ( testsChangedDebouncer . empty ) {
719+ console . log ( chalk . yellowBright ( `[watch] tests changed due to '${ path } ', restarting...` ) ) ;
720+ if ( running ) {
721+ console . log ( chalk . yellowBright ( "[watch] aborting in-progress test run..." ) ) ;
722+ }
723+ testsChangedCancelSource . cancel ( ) ;
724+ testsChangedCancelSource = CancelToken . source ( ) ;
725+ }
726+
727+ testsChangedDebouncer . enqueue ( ) ;
728+ }
729+
730+ function endRunTests ( ) {
731+ lastTestChangeTimeMs = Date . now ( ) ;
732+ testsChangedDeferred . resolve ( ) ;
733+ testsChangedDeferred = /** @type {Deferred<void> } */ ( new Deferred ( ) ) ;
734+ }
735+
736+ function endWatchMode ( ) {
737+ if ( watching ) {
738+ watching = false ;
739+ console . log ( chalk . yellowBright ( "[watch] exiting watch mode..." ) ) ;
740+ testsChangedCancelSource . cancel ( ) ;
741+ testCaseWatcher . close ( ) ;
742+ watchTestsEmitter . off ( "rebuild" , onRebuild ) ;
743+ }
744+ }
745+ } ,
746+ } ) ;
747+
628748export const runTestsParallel = task ( {
629749 name : "runtests-parallel" ,
630750 description : "Runs all the tests in parallel using the built run.js file." ,
0 commit comments