@@ -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" ;
11- import { buildProject as realBuildProject , cleanProject , watchProject } from "./scripts/build/projects.mjs" ;
9+ import { Debouncer , Deferred , exec , getDiffTool , getDirSize , memoize , needsUpdate , readJson } from "./scripts/build/utils.mjs" ;
10+ import { cleanTestDirs , localBaseline , localRwcBaseline , refBaseline , refRwcBaseline , runConsoleTests } from "./scripts/build/tests.mjs" ;
11+ import { cleanProject , buildProject as realBuildProject , 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
@@ -324,10 +328,21 @@ function entrypointBuildTask(options) {
324328 } ,
325329 } ) ;
326330
331+ const mainDeps = options . mainDeps ?. slice ( 0 ) ?? [ ] ;
332+ if ( cmdLineOptions . bundle ) {
333+ mainDeps . push ( bundle ) ;
334+ if ( cmdLineOptions . typecheck ) {
335+ mainDeps . push ( build ) ;
336+ }
337+ }
338+ else {
339+ mainDeps . push ( build , shim ) ;
340+ }
341+
327342 const main = task ( {
328343 name : options . name ,
329344 description : options . description ,
330- dependencies : ( options . mainDeps ?? [ ] ) . concat ( cmdLineOptions . bundle ? [ bundle ] : [ build , shim ] ) ,
345+ dependencies : mainDeps ,
331346 } ) ;
332347
333348 const watch = task ( {
@@ -354,7 +369,7 @@ function entrypointBuildTask(options) {
354369}
355370
356371
357- const { main : tsc , build : buildTsc , watch : watchTsc } = entrypointBuildTask ( {
372+ const { main : tsc , watch : watchTsc } = entrypointBuildTask ( {
358373 name : "tsc" ,
359374 description : "Builds the command-line compiler" ,
360375 buildDeps : [ generateDiagnostics ] ,
@@ -392,7 +407,7 @@ export const dtsServices = task({
392407} ) ;
393408
394409
395- const { main : tsserver , build : buildTsserver , watch : watchTsserver } = entrypointBuildTask ( {
410+ const { main : tsserver , watch : watchTsserver } = entrypointBuildTask ( {
396411 name : "tsserver" ,
397412 description : "Builds the language server" ,
398413 buildDeps : [ generateDiagnostics ] ,
@@ -410,15 +425,10 @@ const { main: tsserver, build: buildTsserver, watch: watchTsserver } = entrypoin
410425export { tsserver , watchTsserver } ;
411426
412427
413- const buildMin = task ( {
414- name : "build-min" ,
415- dependencies : [ buildTsc , buildTsserver ] ,
416- } ) ;
417-
418428export const min = task ( {
419429 name : "min" ,
420430 description : "Builds only tsc and tsserver" ,
421- dependencies : [ tsc , tsserver ] . concat ( cmdLineOptions . typecheck ? [ buildMin ] : [ ] ) ,
431+ dependencies : [ tsc , tsserver ] ,
422432} ) ;
423433
424434export const watchMin = task ( {
@@ -461,7 +471,7 @@ export const dts = task({
461471
462472
463473const testRunner = "./built/local/run.js" ;
464-
474+ const watchTestsEmitter = new EventEmitter ( ) ;
465475const { main : tests , watch : watchTests } = entrypointBuildTask ( {
466476 name : "tests" ,
467477 description : "Builds the test infrastructure" ,
@@ -482,6 +492,11 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({
482492 "mocha" ,
483493 "ms" ,
484494 ] ,
495+ watchMode : {
496+ onRebuild ( ) {
497+ watchTestsEmitter . emit ( "rebuild" ) ;
498+ }
499+ }
485500 } ,
486501} ) ;
487502export { tests , watchTests } ;
@@ -582,15 +597,10 @@ export const watchOtherOutputs = task({
582597 dependencies : [ watchCancellationToken , watchTypingsInstaller , watchWatchGuard , generateTypesMap , copyBuiltLocalDiagnosticMessages ] ,
583598} ) ;
584599
585- const buildLocal = task ( {
586- name : "build-local" ,
587- dependencies : [ buildTsc , buildTsserver , buildServices , buildLssl ]
588- } ) ;
589-
590600export const local = task ( {
591601 name : "local" ,
592602 description : "Builds the full compiler and services" ,
593- dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] . concat ( cmdLineOptions . typecheck ? [ buildLocal ] : [ ] ) ,
603+ dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] ,
594604} ) ;
595605export default local ;
596606
@@ -601,7 +611,7 @@ export const watchLocal = task({
601611 dependencies : [ localize , watchTsc , watchTsserver , watchServices , watchLssl , watchOtherOutputs , dts , watchSrc ] ,
602612} ) ;
603613
604- const runtestsDeps = [ tests , generateLibs ] . concat ( cmdLineOptions . typecheck ? [ dts , buildSrc ] : [ ] ) ;
614+ const runtestsDeps = [ tests , generateLibs ] . concat ( cmdLineOptions . typecheck ? [ dts ] : [ ] ) ;
605615
606616export const runTests = task ( {
607617 name : "runtests" ,
@@ -625,6 +635,117 @@ export const runTests = task({
625635// " --shardId": "1-based ID of this shard (default: 1)",
626636// };
627637
638+ export const runTestsAndWatch = task ( {
639+ name : "runtests-watch" ,
640+ dependencies : [ watchTests ] ,
641+ run : async ( ) => {
642+ if ( ! cmdLineOptions . tests && ! cmdLineOptions . failed ) {
643+ console . log ( chalk . redBright ( `You must specifiy either --tests/-t or --failed to use 'runtests-watch'.` ) ) ;
644+ return ;
645+ }
646+
647+ let watching = true ;
648+ let running = true ;
649+ let lastTestChangeTimeMs = Date . now ( ) ;
650+ let testsChangedDeferred = /** @type {Deferred<void> } */ ( new Deferred ( ) ) ;
651+ let testsChangedCancelSource = CancelToken . source ( ) ;
652+
653+ const testsChangedDebouncer = new Debouncer ( 1_000 , endRunTests ) ;
654+ const testCaseWatcher = chokidar . watch ( [
655+ "tests/cases/**/*.*" ,
656+ "tests/lib/**/*.*" ,
657+ "tests/projects/**/*.*" ,
658+ ] , {
659+ ignorePermissionErrors : true ,
660+ alwaysStat : true
661+ } ) ;
662+
663+ process . on ( "SIGINT" , endWatchMode ) ;
664+ process . on ( "SIGKILL" , endWatchMode ) ;
665+ process . on ( "beforeExit" , endWatchMode ) ;
666+ watchTestsEmitter . on ( "rebuild" , onRebuild ) ;
667+ testCaseWatcher . on ( "all" , onChange ) ;
668+
669+ while ( watching ) {
670+ const promise = testsChangedDeferred . promise ;
671+ const token = testsChangedCancelSource . token ;
672+ if ( ! token . signaled ) {
673+ running = true ;
674+ try {
675+ await runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false , { token, watching : true } ) ;
676+ }
677+ catch {
678+ // ignore
679+ }
680+ running = false ;
681+ }
682+ if ( watching ) {
683+ console . log ( chalk . yellowBright ( `[watch] test run complete, waiting for changes...` ) ) ;
684+ await promise ;
685+ }
686+ }
687+
688+ function onRebuild ( ) {
689+ beginRunTests ( testRunner ) ;
690+ }
691+
692+ /**
693+ * @param {'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir' } eventName
694+ * @param {string } path
695+ * @param {fs.Stats | undefined } stats
696+ */
697+ function onChange ( eventName , path , stats ) {
698+ switch ( eventName ) {
699+ case "change" :
700+ case "unlink" :
701+ case "unlinkDir" :
702+ break ;
703+ case "add" :
704+ case "addDir" :
705+ // skip files that are detected as 'add' but haven't actually changed since the last time tests were
706+ // run.
707+ if ( stats && stats . mtimeMs <= lastTestChangeTimeMs ) {
708+ return ;
709+ }
710+ break ;
711+ }
712+ beginRunTests ( path ) ;
713+ }
714+
715+ /**
716+ * @param {string } path
717+ */
718+ function beginRunTests ( path ) {
719+ if ( testsChangedDebouncer . empty ) {
720+ console . log ( chalk . yellowBright ( `[watch] tests changed due to '${ path } ', restarting...` ) ) ;
721+ if ( running ) {
722+ console . log ( chalk . yellowBright ( "[watch] aborting in-progress test run..." ) ) ;
723+ }
724+ testsChangedCancelSource . cancel ( ) ;
725+ testsChangedCancelSource = CancelToken . source ( ) ;
726+ }
727+
728+ testsChangedDebouncer . enqueue ( ) ;
729+ }
730+
731+ function endRunTests ( ) {
732+ lastTestChangeTimeMs = Date . now ( ) ;
733+ testsChangedDeferred . resolve ( ) ;
734+ testsChangedDeferred = /** @type {Deferred<void> } */ ( new Deferred ( ) ) ;
735+ }
736+
737+ function endWatchMode ( ) {
738+ if ( watching ) {
739+ watching = false ;
740+ console . log ( chalk . yellowBright ( "[watch] exiting watch mode..." ) ) ;
741+ testsChangedCancelSource . cancel ( ) ;
742+ testCaseWatcher . close ( ) ;
743+ watchTestsEmitter . off ( "rebuild" , onRebuild ) ;
744+ }
745+ }
746+ } ,
747+ } ) ;
748+
628749export const runTestsParallel = task ( {
629750 name : "runtests-parallel" ,
630751 description : "Runs all the tests in parallel using the built run.js file." ,
@@ -726,7 +847,7 @@ export const importDefinitelyTypedTests = task({
726847export const produceLKG = task ( {
727848 name : "LKG" ,
728849 description : "Makes a new LKG out of the built js files" ,
729- dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] ,
850+ dependencies : [ local ] ,
730851 run : async ( ) => {
731852 if ( ! cmdLineOptions . bundle ) {
732853 throw new Error ( "LKG cannot be created when --bundle=false" ) ;
0 commit comments