From ee7e78eabf62eee4401789a37c23ee9a2c61a978 Mon Sep 17 00:00:00 2001 From: David Michon Date: Mon, 4 Aug 2025 19:19:03 +0000 Subject: [PATCH 1/3] [heft-swc] Fix watch infinite loop --- .../main_2025-08-04-19-18.json | 10 ++ .../src/SwcIsolatedTranspilePlugin.ts | 93 ++++++------------- 2 files changed, 40 insertions(+), 63 deletions(-) create mode 100644 common/changes/@rushstack/heft-isolated-typescript-transpile-plugin/main_2025-08-04-19-18.json diff --git a/common/changes/@rushstack/heft-isolated-typescript-transpile-plugin/main_2025-08-04-19-18.json b/common/changes/@rushstack/heft-isolated-typescript-transpile-plugin/main_2025-08-04-19-18.json new file mode 100644 index 00000000000..03cf6c84643 --- /dev/null +++ b/common/changes/@rushstack/heft-isolated-typescript-transpile-plugin/main_2025-08-04-19-18.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-isolated-typescript-transpile-plugin", + "comment": "Manually process wildcard directories for watching instead of watching all read directories.", + "type": "patch" + } + ], + "packageName": "@rushstack/heft-isolated-typescript-transpile-plugin" +} \ No newline at end of file diff --git a/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts b/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts index c1921dd1526..f55a8f41c1e 100644 --- a/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts +++ b/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { Dirent, Stats } from 'node:fs'; +import type { Dirent } from 'node:fs'; import path from 'node:path'; import { type ChildProcess, fork } from 'node:child_process'; @@ -159,68 +159,6 @@ async function transpileProjectAsync( }); const { ts } = tool; - if (getWatchFs) { - const watchFs: IWatchFileSystem = getWatchFs(); - const { system } = tool; - const emptyFileSystemEntries: { files: string[]; directories: string[] } = { files: [], directories: [] }; - // Copied from TypeScript, but using the watch file system - function getAccessibleFileSystemEntries(directory: string): { files: string[]; directories: string[] } { - try { - const entries: Dirent[] = watchFs.readdirSync(directory || '.', { withFileTypes: true }); - const files: string[] = []; - const directories: string[] = []; - for (const dirent of entries) { - const entry: string = dirent.name; - if (entry === '.' || entry === '..') { - continue; - } - let stat: Stats | Dirent | undefined; - if (dirent.isSymbolicLink()) { - const name: string = ts.combinePaths(directory, entry); - stat = watchFs.statSync(name); - if (!stat) { - continue; - } - } else { - stat = dirent; - } - - if (stat.isFile()) { - files.push(entry); - } else if (stat.isDirectory()) { - directories.push(entry); - } - } - files.sort(); - directories.sort(); - return { files, directories }; - } catch { - return emptyFileSystemEntries; - } - } - - system.readDirectory = ( - dirPath: string, - extensions?: string[], - excludes?: string[], - includes?: string[], - depth?: number - ): string[] => { - return ts.matchFiles( - dirPath, - extensions, - excludes, - includes, - system.useCaseSensitiveFileNames, - buildFolderPath, - depth, - getAccessibleFileSystemEntries, - system.realpath!, - system.directoryExists - ); - }; - } - const tsconfigPath: string = getTsconfigFilePath(heftConfiguration, pluginOptions.tsConfigPath); const parsedTsConfig: TTypeScript.ParsedCommandLine | undefined = loadTsconfig({ tool, tsconfigPath }); @@ -229,6 +167,35 @@ async function transpileProjectAsync( return; } + if (getWatchFs && parsedTsConfig.wildcardDirectories) { + // If the tsconfig has wildcard directories, we need to ensure that they are watched for file changes. + const directoryQueue: Map = new Map(); + for (const [wildcardDirectory, type] of Object.entries(parsedTsConfig.wildcardDirectories)) { + directoryQueue.set(wildcardDirectory, type !== 0); + } + + if (directoryQueue.size > 0) { + const watchFs: IWatchFileSystem = getWatchFs(); + + for (const [wildcardDirectory, isRecursive] of directoryQueue) { + const dirents: Dirent[] = watchFs.readdirSync(path.normalize(wildcardDirectory), { + withFileTypes: true + }); + + if (isRecursive) { + for (const dirent of dirents) { + if (dirent.isDirectory()) { + const absoluteDirentPath: string = ts.combinePaths(wildcardDirectory, dirent.name); + directoryQueue.set(absoluteDirentPath, true); + } + } + } + } + + logger.terminal.writeDebugLine(`Watching for changes in ${directoryQueue.size} directories`); + } + } + if (emitKinds.length < 1) { throw new Error( 'One or more emit kinds must be specified in the plugin options. To disable SWC transpilation, ' + From 87edf5e2168f961fe918a65ff830e001afc44170 Mon Sep 17 00:00:00 2001 From: David Michon Date: Mon, 4 Aug 2025 20:38:32 +0000 Subject: [PATCH 2/3] Adjust normalization --- .../src/SwcIsolatedTranspilePlugin.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts b/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts index f55a8f41c1e..e6ba9e1d22f 100644 --- a/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts +++ b/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts @@ -171,21 +171,22 @@ async function transpileProjectAsync( // If the tsconfig has wildcard directories, we need to ensure that they are watched for file changes. const directoryQueue: Map = new Map(); for (const [wildcardDirectory, type] of Object.entries(parsedTsConfig.wildcardDirectories)) { - directoryQueue.set(wildcardDirectory, type !== 0); + directoryQueue.set(path.normalize(wildcardDirectory), type !== 0); } if (directoryQueue.size > 0) { const watchFs: IWatchFileSystem = getWatchFs(); for (const [wildcardDirectory, isRecursive] of directoryQueue) { - const dirents: Dirent[] = watchFs.readdirSync(path.normalize(wildcardDirectory), { + const dirents: Dirent[] = watchFs.readdirSync(wildcardDirectory, { withFileTypes: true }); if (isRecursive) { for (const dirent of dirents) { if (dirent.isDirectory()) { - const absoluteDirentPath: string = ts.combinePaths(wildcardDirectory, dirent.name); + // Using path.join because we want platform-normalized paths. + const absoluteDirentPath: string = path.join(wildcardDirectory, dirent.name); directoryQueue.set(absoluteDirentPath, true); } } From 8031fa9e8b8be3bb9ef8912b6335001d08e97d3d Mon Sep 17 00:00:00 2001 From: David Michon Date: Mon, 4 Aug 2025 23:05:19 +0000 Subject: [PATCH 3/3] Use enum value --- .../src/SwcIsolatedTranspilePlugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts b/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts index e6ba9e1d22f..4d281846216 100644 --- a/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts +++ b/heft-plugins/heft-isolated-typescript-transpile-plugin/src/SwcIsolatedTranspilePlugin.ts @@ -171,7 +171,7 @@ async function transpileProjectAsync( // If the tsconfig has wildcard directories, we need to ensure that they are watched for file changes. const directoryQueue: Map = new Map(); for (const [wildcardDirectory, type] of Object.entries(parsedTsConfig.wildcardDirectories)) { - directoryQueue.set(path.normalize(wildcardDirectory), type !== 0); + directoryQueue.set(path.normalize(wildcardDirectory), type === ts.WatchDirectoryFlags.Recursive); } if (directoryQueue.size > 0) {