From 44a963aa695409f7b9c9c69f6c2abf79c22e4003 Mon Sep 17 00:00:00 2001 From: Konstantin Konstantinov Date: Fri, 14 Aug 2026 14:14:04 +0300 Subject: [PATCH] fix(scripts): stop the snippet sync walking node_modules and cloned fixture repos findPackageSrcDirs walked packages/ with readdirSync's recursive option and filtered node_modules out of the result. The filter came too late: recursive follows symlinks, so the walk entered every packages/*/node_modules link back into the pnpm store and re-traversed the dependency graph along every path, collecting each entry into one array until it exhausted the V8 heap. It also descended into packages/codemod/batch-test/repos and returned src dirs from the cloned fixture monorepos, whose sources the script would then process. Descend explicitly and skip node_modules, batch-test and dist before entering them. pnpm-workspace.yaml already excludes batch-test from the workspace. Severity depends on the local checkout: batch-test/repos is gitignored, so a developer who has run the codemod batch tests carries whole external monorepos under packages/, and there the check aborts out of memory and takes the pre-push hook down with it. --- scripts/sync-snippets.ts | 51 +++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/scripts/sync-snippets.ts b/scripts/sync-snippets.ts index 33258f4414..fd1ffc6932 100644 --- a/scripts/sync-snippets.ts +++ b/scripts/sync-snippets.ts @@ -504,31 +504,54 @@ function findMarkdownFiles(dir: string): string[] { return files; } +/** + * Directory names that are never descended into when looking for package sources. + * + * `node_modules` is the critical one: pnpm fills it with symlinks pointing back + * into the store, whose packages link onward in turn. Entering it means walking + * that graph along every distinct link path, re-visiting the same directories + * over and over. + * + * `batch-test` holds the codemod's cloned fixture repositories — whole external + * monorepos that `pnpm-workspace.yaml` already excludes from the workspace, and + * whose sources this script must never rewrite. + */ +const SKIPPED_DIRS = new Set(['node_modules', 'dist', 'batch-test']); + /** * Find all package src directories under the packages directory. + * + * Descends explicitly rather than using `readdirSync`'s `recursive` option. That + * option follows symlinks and collects every entry it visits into a single array + * before returning, so filtering unwanted directories out of the result is too + * late to keep the walk bounded — the traversal has already happened. + * * @param packagesDir The packages directory * @returns Array of absolute paths to src directories */ function findPackageSrcDirs(packagesDir: string): string[] { const srcDirs: string[] = []; - const entries = readdirSync(packagesDir, { - withFileTypes: true, - recursive: true, - }); - for (const entry of entries) { - if (!entry.isDirectory()) continue; - if (entry.name !== 'src') continue; + const descend = (dir: string): void => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + // isDirectory() is false for a symlink, so pnpm's links are never entered. + if (!entry.isDirectory()) continue; + if (SKIPPED_DIRS.has(entry.name)) continue; - const fullPath = join(entry.parentPath, entry.name); + const fullPath = join(dir, entry.name); - // Only include src dirs that are direct children of a package - // (e.g., packages/core-internal/src, packages/middleware/express/src) - // Skip nested src dirs like node_modules/*/src - if (fullPath.includes('node_modules')) continue; + // A package owns a single src dir; everything below it is that package's + // own tree, which findSourceFiles walks. + if (entry.name === 'src') { + srcDirs.push(fullPath); + continue; + } - srcDirs.push(fullPath); - } + descend(fullPath); + } + }; + + descend(packagesDir); return srcDirs; }