@@ -6,12 +6,15 @@ import { task } from "hereby";
6
6
import _glob from "glob" ;
7
7
import util from "util" ;
8
8
import 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" ;
11
11
import { buildProject as realBuildProject , cleanProject , watchProject } from "./scripts/build/projects.mjs" ;
12
12
import { localizationDirectories } from "./scripts/build/localization.mjs" ;
13
13
import cmdLineOptions from "./scripts/build/options.mjs" ;
14
14
import esbuild from "esbuild" ;
15
+ import chokidar from "chokidar" ;
16
+ import { EventEmitter } from "events" ;
17
+ import { CancelToken } from "@esfx/canceltoken" ;
15
18
16
19
const glob = util . promisify ( _glob ) ;
17
20
@@ -141,7 +144,7 @@ const localize = task({
141
144
dependencies : [ generateDiagnostics ] ,
142
145
run : async ( ) => {
143
146
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 } ) ;
145
148
}
146
149
}
147
150
} ) ;
@@ -191,6 +194,7 @@ async function runDtsBundler(entrypoint, output) {
191
194
* @property {string[] } [external]
192
195
* @property {boolean } [exportIsTsObject]
193
196
* @property {boolean } [treeShaking]
197
+ * @property {esbuild.WatchMode } [watchMode]
194
198
*/
195
199
function createBundler ( entrypoint , outfile , taskOptions = { } ) {
196
200
const getOptions = memoize ( async ( ) => {
@@ -269,7 +273,7 @@ function createBundler(entrypoint, outfile, taskOptions = {}) {
269
273
270
274
return {
271
275
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" } ) ,
273
277
} ;
274
278
}
275
279
@@ -320,7 +324,7 @@ function entrypointBuildTask(options) {
320
324
const outDir = path . dirname ( options . output ) ;
321
325
await fs . promises . mkdir ( outDir , { recursive : true } ) ;
322
326
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 , "/" ) } ")` ) ;
324
328
} ,
325
329
} ) ;
326
330
@@ -354,7 +358,7 @@ function entrypointBuildTask(options) {
354
358
}
355
359
356
360
357
- const { main : tsc , watch : watchTsc } = entrypointBuildTask ( {
361
+ const { main : tsc , build : buildTsc , watch : watchTsc } = entrypointBuildTask ( {
358
362
name : "tsc" ,
359
363
description : "Builds the command-line compiler" ,
360
364
buildDeps : [ generateDiagnostics ] ,
@@ -386,13 +390,13 @@ export const dtsServices = task({
386
390
dependencies : [ buildServices ] ,
387
391
run : async ( ) => {
388
392
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" ) ;
390
394
}
391
395
} ,
392
396
} ) ;
393
397
394
398
395
- const { main : tsserver , watch : watchTsserver } = entrypointBuildTask ( {
399
+ const { main : tsserver , build : buildTsserver , watch : watchTsserver } = entrypointBuildTask ( {
396
400
name : "tsserver" ,
397
401
description : "Builds the language server" ,
398
402
buildDeps : [ generateDiagnostics ] ,
@@ -410,10 +414,15 @@ const { main: tsserver, watch: watchTsserver } = entrypointBuildTask({
410
414
export { tsserver , watchTsserver } ;
411
415
412
416
417
+ const buildMin = task ( {
418
+ name : "build-min" ,
419
+ dependencies : [ buildTsc , buildTsserver ] ,
420
+ } ) ;
421
+
413
422
export const min = task ( {
414
423
name : "min" ,
415
424
description : "Builds only tsc and tsserver" ,
416
- dependencies : [ tsc , tsserver ] ,
425
+ dependencies : [ tsc , tsserver ] . concat ( cmdLineOptions . typecheck ? [ buildMin ] : [ ] ) ,
417
426
} ) ;
418
427
419
428
export const watchMin = task ( {
@@ -456,7 +465,7 @@ export const dts = task({
456
465
457
466
458
467
const testRunner = "./built/local/run.js" ;
459
-
468
+ const watchTestsEmitter = new EventEmitter ( ) ;
460
469
const { main : tests , watch : watchTests } = entrypointBuildTask ( {
461
470
name : "tests" ,
462
471
description : "Builds the test infrastructure" ,
@@ -477,6 +486,11 @@ const { main: tests, watch: watchTests } = entrypointBuildTask({
477
486
"mocha" ,
478
487
"ms" ,
479
488
] ,
489
+ watchMode : {
490
+ onRebuild ( ) {
491
+ watchTestsEmitter . emit ( "rebuild" ) ;
492
+ }
493
+ }
480
494
} ,
481
495
} ) ;
482
496
export { tests , watchTests } ;
@@ -577,10 +591,15 @@ export const watchOtherOutputs = task({
577
591
dependencies : [ watchCancellationToken , watchTypingsInstaller , watchWatchGuard , generateTypesMap , copyBuiltLocalDiagnosticMessages ] ,
578
592
} ) ;
579
593
594
+ const buildLocal = task ( {
595
+ name : "build-local" ,
596
+ dependencies : [ buildTsc , buildTsserver , buildServices , buildLssl ]
597
+ } ) ;
598
+
580
599
export const local = task ( {
581
600
name : "local" ,
582
601
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 ] : [ ] ) ,
584
603
} ) ;
585
604
export default local ;
586
605
@@ -591,11 +610,12 @@ export const watchLocal = task({
591
610
dependencies : [ localize , watchTsc , watchTsserver , watchServices , watchLssl , watchOtherOutputs , dts , watchSrc ] ,
592
611
} ) ;
593
612
613
+ const runtestsDeps = [ tests , generateLibs ] . concat ( cmdLineOptions . typecheck ? [ dts , buildSrc ] : [ ] ) ;
594
614
595
615
export const runTests = task ( {
596
616
name : "runtests" ,
597
617
description : "Runs the tests using the built run.js file." ,
598
- dependencies : [ tests , generateLibs , dts , buildSrc ] ,
618
+ dependencies : runtestsDeps ,
599
619
run : ( ) => runConsoleTests ( testRunner , "mocha-fivemat-progress-reporter" , /*runInParallel*/ false ) ,
600
620
} ) ;
601
621
// task("runtests").flags = {
@@ -614,10 +634,121 @@ export const runTests = task({
614
634
// " --shardId": "1-based ID of this shard (default: 1)",
615
635
// };
616
636
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
+
617
748
export const runTestsParallel = task ( {
618
749
name : "runtests-parallel" ,
619
750
description : "Runs all the tests in parallel using the built run.js file." ,
620
- dependencies : [ tests , generateLibs , dts , buildSrc ] ,
751
+ dependencies : runtestsDeps ,
621
752
run : ( ) => runConsoleTests ( testRunner , "min" , /*runInParallel*/ cmdLineOptions . workers > 1 ) ,
622
753
} ) ;
623
754
// task("runtests-parallel").flags = {
@@ -715,7 +846,7 @@ export const importDefinitelyTypedTests = task({
715
846
export const produceLKG = task ( {
716
847
name : "LKG" ,
717
848
description : "Makes a new LKG out of the built js files" ,
718
- dependencies : [ localize , tsc , tsserver , services , lssl , otherOutputs , dts ] ,
849
+ dependencies : [ local ] ,
719
850
run : async ( ) => {
720
851
if ( ! cmdLineOptions . bundle ) {
721
852
throw new Error ( "LKG cannot be created when --bundle=false" ) ;
0 commit comments