@@ -3,6 +3,7 @@ import * as JSONC from "@std/jsonc";
33import * as tsmorph from "ts-morph" ;
44import * as colors from "@std/fmt/colors" ;
55import { ProgressBar } from "@std/cli/unstable-progress-bar" ;
6+ import { walk } from "@std/fs/walk" ;
67
78export 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
1415const HIDE_FILES = / [ \\ / ] + ( n o d e _ m o d u l e s | v e n d o r ) [ \\ / ] + / ;
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+ / (?: [ \\ / ] \. [ ^ \\ / ] + (?: [ \\ / ] | $ ) | [ \\ / ] ( n o d e _ m o d u l e s | v e n d o r | d o c s | \. g i t | \. n e x t | \. t u r b o | _ f r e s h | d i s t | b u i l d | t a r g e t | \. c a c h e ) (?: [ \\ / ] | $ ) ) / ;
1520export 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