@@ -8,12 +8,13 @@ import { task } from "hereby";
88import assert from "node:assert" ;
99import crypto from "node:crypto" ;
1010import fs from "node:fs" ;
11+ import os from "node:os" ;
1112import path from "node:path" ;
1213import url from "node:url" ;
1314import { parseArgs } from "node:util" ;
14- import os from "os" ;
1515import pLimit from "p-limit" ;
1616import pc from "picocolors" ;
17+ import tmp from "tmp" ;
1718import which from "which" ;
1819
1920const __filename = url . fileURLToPath ( new URL ( import . meta. url ) ) ;
@@ -302,11 +303,64 @@ const goTestEnv = {
302303 ...( process . platform === "win32" ? { GOFLAGS : "-count=1" } : { } ) ,
303304} ;
304305
306+ const baselineTrackingEnabled = isTypeScriptSubmoduleCloned ( ) && ! [
307+ options . tests ,
308+ options . noembed ,
309+ options . concurrentTestPrograms ,
310+ options . race ,
311+ options . dirty ,
312+ ] . some ( Boolean ) ;
313+
305314const goTestSumFlags = [
306315 "--format-hide-empty-pkg" ,
307316 ...( ! isCI ? [ "--hide-summary" , "skipped" ] : [ ] ) ,
308317] ;
309318
319+ /**
320+ * Collects all baseline files that were used during the test run.
321+ * @param {string } trackingDir
322+ * @returns {Promise<Set<string>> }
323+ */
324+ async function collectUsedBaselines ( trackingDir ) {
325+ /** @type {Set<string> } */
326+ const usedBaselines = new Set ( ) ;
327+ if ( ! fs . existsSync ( trackingDir ) ) {
328+ return usedBaselines ;
329+ }
330+
331+ const trackingFiles = await fs . promises . readdir ( trackingDir ) ;
332+ for ( const file of trackingFiles ) {
333+ const content = await fs . promises . readFile ( path . join ( trackingDir , file ) , "utf-8" ) ;
334+ for ( const line of content . split ( "\n" ) ) {
335+ const trimmed = line . trim ( ) ;
336+ if ( trimmed ) {
337+ usedBaselines . add ( trimmed ) ;
338+ }
339+ }
340+ }
341+ return usedBaselines ;
342+ }
343+
344+ /**
345+ * Checks for unused baseline files and reports them.
346+ * @param {string } trackingDir
347+ * @returns {Promise<string[]> } List of unused baseline file paths.
348+ */
349+ async function checkUnusedBaselines ( trackingDir ) {
350+ const usedBaselines = await collectUsedBaselines ( trackingDir ) ;
351+ if ( usedBaselines . size === 0 ) {
352+ // No baselines recorded - either no tests ran or tracking wasn't set up properly
353+ return [ ] ;
354+ }
355+
356+ const allBaselines = await glob ( `${ refBaseline } /**` , { nodir : true } ) ;
357+ const unusedBaselines = allBaselines
358+ . map ( p => path . relative ( refBaseline , p ) )
359+ . filter ( p => ! usedBaselines . has ( p ) ) ;
360+
361+ return unusedBaselines ;
362+ }
363+
310364const $test = $ ( { env : goTestEnv } ) ;
311365
312366/**
@@ -332,7 +386,55 @@ async function runTests() {
332386 await fs . promises . mkdir ( localBaseline , { recursive : true } ) ;
333387 }
334388
335- await $test `${ gotestsum ( "tests" ) } ./... ${ isCI ? [ "--timeout=45m" ] : [ ] } ` ;
389+ // Create a tmp directory for baseline tracking if enabled
390+ /** @type {string | undefined } */
391+ let trackingDir ;
392+ /** @type {(() => void) | undefined } */
393+ let cleanupTracking ;
394+
395+ if ( baselineTrackingEnabled ) {
396+ const tmpDir = tmp . dirSync ( { prefix : "tsgo-baseline-tracking-" , unsafeCleanup : true } ) ;
397+ trackingDir = tmpDir . name ;
398+ cleanupTracking = tmpDir . removeCallback ;
399+ }
400+
401+ try {
402+ const testEnv = {
403+ ...goTestEnv ,
404+ ...( trackingDir ? { TSGO_BASELINE_TRACKING_DIR : trackingDir } : { } ) ,
405+ } ;
406+ const $testWithTracking = $ ( { env : testEnv } ) ;
407+ await $testWithTracking `${ gotestsum ( "tests" ) } ./... ${ isCI ? [ "--timeout=45m" ] : [ ] } ` ;
408+
409+ // Check for unused baselines after tests complete
410+ if ( trackingDir ) {
411+ const unusedBaselines = await checkUnusedBaselines ( trackingDir ) ;
412+ if ( unusedBaselines . length > 0 ) {
413+ console . error ( pc . red ( `\nFound ${ unusedBaselines . length } unused baseline file(s):` ) ) ;
414+ for ( const baseline of unusedBaselines . slice ( 0 , 20 ) ) {
415+ console . error ( pc . red ( ` ${ baseline } ` ) ) ;
416+ }
417+ if ( unusedBaselines . length > 20 ) {
418+ console . error ( pc . red ( ` ... and ${ unusedBaselines . length - 20 } more` ) ) ;
419+ }
420+
421+ // Create .delete files for each unused baseline so baseline-accept can remove them
422+ for ( const baseline of unusedBaselines ) {
423+ const deleteFilePath = path . join ( localBaseline , baseline + ".delete" ) ;
424+ await fs . promises . mkdir ( path . dirname ( deleteFilePath ) , { recursive : true } ) ;
425+ await fs . promises . writeFile ( deleteFilePath , "" ) ;
426+ }
427+ console . error ( pc . red ( `\nRun 'hereby baseline-accept' to delete them.` ) ) ;
428+
429+ throw new Error ( `Found ${ unusedBaselines . length } unused baseline file(s). Run 'hereby baseline-accept' to delete them.` ) ;
430+ }
431+ }
432+ }
433+ finally {
434+ if ( cleanupTracking ) {
435+ cleanupTracking ( ) ;
436+ }
437+ }
336438}
337439
338440export const test = task ( {
0 commit comments