Skip to content

Commit 48b86de

Browse files
committed
Fix #3483 Updater now streams files one-by-one, skipping hidden/large dirs (node_modules, vendor, docs, etc.) to avoid OOMs and hangs; frees ASTs immediately.
- ts-morph dependency bumped to ^27.0.2.
1 parent 2c36bec commit 48b86de

File tree

3 files changed

+54
-26
lines changed

3 files changed

+54
-26
lines changed

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"tailwindcss": "npm:tailwindcss@^4.1.10",
8787
"postcss": "npm:postcss@8.5.6",
8888

89-
"ts-morph": "npm:ts-morph@^26.0.0",
89+
"ts-morph": "npm:ts-morph@^27.0.2",
9090

9191
"@std/front-matter": "jsr:@std/front-matter@^1.0.5",
9292
"github-slugger": "npm:github-slugger@^2.0.0",

deno.lock

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/update/src/update.ts

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as JSONC from "@std/jsonc";
33
import * as tsmorph from "ts-morph";
44
import * as colors from "@std/fmt/colors";
55
import { ProgressBar } from "@std/cli/unstable-progress-bar";
6+
import { walk } from "@std/fs/walk";
67

78
export const SyntaxKind = tsmorph.ts.SyntaxKind;
89

@@ -12,6 +13,10 @@ export const PREACT_SIGNALS_VERSION = "2.5.0";
1213

1314
// Function to filter out node_modules and vendor directories from logs
1415
const HIDE_FILES = /[\\/]+(node_modules|vendor)[\\/]+/;
16+
// Directories we should completely skip when walking the tree. This keeps us
17+
// from loading vendored dependencies (often thousands of files) into ts-morph.
18+
const SKIP_DIRS =
19+
/(?:[\\/]\.[^\\/]+(?:[\\/]|$)|[\\/](node_modules|vendor|docs|\.git|\.next|\.turbo|_fresh|dist|build|target|\.cache)(?:[\\/]|$))/;
1520
export interface DenoJson {
1621
lock?: boolean;
1722
tasks?: Record<string, string>;
@@ -170,17 +175,27 @@ export async function updateProject(dir: string) {
170175

171176
// Update routes folder
172177
const project = new tsmorph.Project();
173-
const sfs = project.addSourceFilesAtPaths(
174-
path.join(dir, "**", "*.{js,jsx,ts,tsx}"),
175-
);
176178

177-
// Filter out node_modules and vendor files for user display
178-
const userFiles = sfs.filter((sf) => !HIDE_FILES.test(sf.getFilePath()));
179+
// First pass: count files (without storing them) so we can size the progress
180+
// bar while still skipping heavy dirs early.
181+
let totalFiles = 0;
182+
let userFileCount = 0;
183+
for await (
184+
const entry of walk(dir, {
185+
includeDirs: false,
186+
includeFiles: true,
187+
exts: ["js", "jsx", "ts", "tsx"],
188+
skip: [SKIP_DIRS],
189+
})
190+
) {
191+
totalFiles++;
192+
if (!HIDE_FILES.test(entry.path)) userFileCount++;
193+
}
179194

180195
// deno-lint-ignore no-console
181-
console.log(colors.cyan(`📁 Found ${userFiles.length} files to process`));
196+
console.log(colors.cyan(`📁 Found ${userFileCount} files to process`));
182197

183-
if (sfs.length === 0) {
198+
if (totalFiles === 0) {
184199
// deno-lint-ignore no-console
185200
console.log(colors.green("🎉 Migration completed successfully!"));
186201
return;
@@ -195,30 +210,41 @@ export async function updateProject(dir: string) {
195210

196211
// Create a progress bar
197212
const bar = new ProgressBar({
198-
max: sfs.length,
213+
max: totalFiles,
199214
formatter(x) {
200215
return `[${x.styledTime}] [${x.progressBar}] [${x.value}/${x.max} files]`;
201216
},
202217
});
203218

204-
// process files sequentially to show proper progress
205-
await Promise.all(sfs.map(async (sourceFile) => {
219+
// Second pass: process each file one-by-one to keep memory flat. We add a
220+
// SourceFile, transform it, then immediately forget it so ts-morph releases
221+
// the AST from memory.
222+
for await (
223+
const entry of walk(dir, {
224+
includeDirs: false,
225+
includeFiles: true,
226+
exts: ["js", "jsx", "ts", "tsx"],
227+
skip: [SKIP_DIRS],
228+
})
229+
) {
230+
const sourceFile = project.addSourceFileAtPath(entry.path);
206231
try {
207232
const wasModified = await updateFile(sourceFile);
208233
if (wasModified) {
209234
modifiedFilesList.push(sourceFile.getFilePath());
210235
}
211-
212-
return wasModified;
213236
} catch (err) {
214237
// deno-lint-ignore no-console
215238
console.error(`Could not process ${sourceFile.getFilePath()}`);
216239
throw err;
217240
} finally {
218241
completedFiles++;
219242
bar.value = completedFiles;
243+
// Drop the AST to avoid unbounded memory growth
244+
sourceFile.forget();
245+
project.removeSourceFile(sourceFile);
220246
}
221-
}));
247+
}
222248

223249
// Clear the progress line and add a newline
224250
await bar.stop();
@@ -227,19 +253,21 @@ export async function updateProject(dir: string) {
227253
const modifiedFilesToShow = modifiedFilesList.filter((filePath) =>
228254
!HIDE_FILES.test(filePath)
229255
);
256+
const unmodifiedCount = Math.max(
257+
userFileCount - modifiedFilesToShow.length,
258+
0,
259+
);
230260

231261
// add migration summary
232262
// deno-lint-ignore no-console
233263
console.log("\n" + colors.bold("📊 Migration Summary:"));
234264
// deno-lint-ignore no-console
235-
console.log(` Total files processed: ${userFiles.length}`);
265+
console.log(` Total files processed: ${userFileCount}`);
236266
// deno-lint-ignore no-console
237267
console.log(` Successfully modified: ${modifiedFilesToShow.length}`);
238268
// deno-lint-ignore no-console
239269
console.log(
240-
` Unmodified (no changes needed): ${
241-
userFiles.length - modifiedFilesToShow.length
242-
}`,
270+
` Unmodified (no changes needed): ${unmodifiedCount}`,
243271
);
244272

245273
// Display modified files list (filtered)

0 commit comments

Comments
 (0)