@@ -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
@@ -141,7 +144,7 @@ const localize = task({
141144 dependencies : [ generateDiagnostics ] ,
142145 run : async ( ) => {
143146 if ( needsUpdate ( diagnosticMessagesGeneratedJson , generatedLCGFile ) ) {
144- return exec ( process . execPath , [ "scripts/generateLocalizedDiagnosticMessages.mjs" , "src/loc/lcl" , "built/local" , diagnosticMessagesGeneratedJson ] , { ignoreExitCode : true } ) ;
147+ await exec ( process . execPath , [ "scripts/generateLocalizedDiagnosticMessages.mjs" , "src/loc/lcl" , "built/local" , diagnosticMessagesGeneratedJson ] , { ignoreExitCode : true } ) ;
145148 }
146149 }
147150} ) ;
@@ -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
@@ -320,7 +324,7 @@ function entrypointBuildTask(options) {
320324 const outDir = path . dirname ( options . output ) ;
321325 await fs . promises . mkdir ( outDir , { recursive : true } ) ;
322326 const moduleSpecifier = path . relative ( outDir , options . builtEntrypoint ) ;
323- await fs . promises . writeFile ( options . output , `module.exports = require("./${ moduleSpecifier } ")` ) ;
327+ await fs . promises . writeFile ( options . output , `module.exports = require("./${ moduleSpecifier . replace ( / [ \\ / ] / g , "/" ) } ")` ) ;
324328 } ,
325329 } ) ;
326330
@@ -354,7 +358,7 @@ function entrypointBuildTask(options) {
354358}
355359
356360
357- const { main : tsc , watch : watchTsc } = entrypointBuildTask ( {
361+ const { main : tsc , build : buildTsc , watch : watchTsc } = entrypointBuildTask ( {
358362 name : "tsc" ,
359363 description : "Builds the command-line compiler" ,
360364 buildDeps : [ generateDiagnostics ] ,
@@ -386,13 +390,13 @@ export const dtsServices = task({
386390 dependencies : [ buildServices ] ,
387391 run : async ( ) => {
388392 if ( needsUpdate ( "./built/local/typescript/tsconfig.tsbuildinfo" , [ "./built/local/typescript.d.ts" , "./built/local/typescript.internal.d.ts" ] ) ) {
389- runDtsBundler ( "./built/local/typescript/typescript.d.ts" , "./built/local/typescript.d.ts" ) ;
393+ await runDtsBundler ( "./built/local/typescript/typescript.d.ts" , "./built/local/typescript.d.ts" ) ;
390394 }
391395 } ,
392396} ) ;
393397
394398
395- const { main : tsserver , watch : watchTsserver } = entrypointBuildTask ( {
399+ const { main : tsserver , build : buildTsserver , watch : watchTsserver } = entrypointBuildTask ( {
396400 name : "tsserver" ,
397401 description : "Builds the language server" ,
398402 buildDeps : [ generateDiagnostics ] ,
@@ -410,10 +414,15 @@ const { main: tsserver, watch: watchTsserver } = entrypointBuildTask({
410414export { tsserver , watchTsserver } ;
411415
412416
417+ const buildMin = task ( {
418+ name : "build-min" ,
419+ dependencies : [ buildTsc , buildTsserver ] ,
420+ } ) ;
421+
413422export const min = task ( {
414423 name : "min" ,
415424 description : "Builds only tsc and tsserver" ,
416- dependencies : [ tsc , tsserver ] ,
425+ dependencies : [ tsc , tsserver ] . concat ( cmdLineOptions . typecheck ? [ buildMin ] : [ ] ) ,
417426} ) ;
418427
419428export const watchMin = task ( {
@@ -456,7 +465,7 @@ export const dts = task({
456465
457466
458467const testRunner = "./built/local/run.js" ;
459-
468+ const watchTestsEmitter = new EventEmitter ( ) ;
460469const { main : tests , watch : watchTests } = entrypointBuildTask ( {
461470 name : "tests" ,
462471 description : "Builds the test infrastructure" ,
@@ -477,6 +486,11 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({
477486 "mocha" ,
478487 "ms" ,
479488 ] ,
489+ watchMode : {
490+ onRebuild ( ) {
491+ watchTestsEmitter . emit ( "rebuild" ) ;
492+ }
493+ }
480494 } ,
481495} ) ;
482496export { tests , watchTests } ;
@@ -577,10 +591,15 @@ export const watchOtherOutputs = task({
577591 dependencies : [ watchCancellationToken , watchTypingsInstaller , watchWatchGuard , generateTypesMap , copyBuiltLocalDiagnosticMessages ] ,
578592} ) ;
579593
594+ const buildLocal = task ( {
595+ name : "build-local" ,
596+ dependencies : [ buildTsc , buildTsserver , buildServices , buildLssl ]
597+ } ) ;
598+
580599export const local = task ( {
581600 name : "local" ,
582601 description : "Builds the full compiler and services" ,
583- dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts , buildSrc ] ,
602+ dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] . concat ( cmdLineOptions . typecheck ? [ buildLocal ] : [ ] ) ,
584603} ) ;
585604export default local ;
586605
@@ -591,11 +610,12 @@ export const watchLocal = task({
591610 dependencies : [ localize , watchTsc , watchTsserver , watchServices , watchLssl , watchOtherOutputs , dts , watchSrc ] ,
592611} ) ;
593612
613+ const runtestsDeps = [ tests , generateLibs ] . concat ( cmdLineOptions . typecheck ? [ dts , buildSrc ] : [ ] ) ;
594614
595615export const runTests = task ( {
596616 name : "runtests" ,
597617 description : "Runs the tests using the built run.js file." ,
598- dependencies : [ tests , generateLibs , dts , buildSrc ] ,
618+ dependencies : runtestsDeps ,
599619 run : ( ) => runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false ) ,
600620} ) ;
601621// task("runtests").flags = {
@@ -614,10 +634,121 @@ export const runTests = task({
614634// " --shardId": "1-based ID of this shard (default: 1)",
615635// };
616636
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+
617748export const runTestsParallel = task ( {
618749 name : "runtests-parallel" ,
619750 description : "Runs all the tests in parallel using the built run.js file." ,
620- dependencies : [ tests , generateLibs , dts , buildSrc ] ,
751+ dependencies : runtestsDeps ,
621752 run : ( ) => runConsoleTests ( testRunner , "min" , /*runInParallel*/ cmdLineOptions . workers > 1 ) ,
622753} ) ;
623754// task("runtests-parallel").flags = {
@@ -715,7 +846,7 @@ export const importDefinitelyTypedTests = task({
715846export const produceLKG = task ( {
716847 name : "LKG" ,
717848 description : "Makes a new LKG out of the built js files" ,
718- dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] ,
849+ dependencies : [ local ] ,
719850 run : async ( ) => {
720851 if ( ! cmdLineOptions . bundle ) {
721852 throw new Error ( "LKG cannot be created when --bundle=false" ) ;
0 commit comments