@@ -10,6 +10,7 @@ import {
1010 registerFramework ,
1111} from '@tanstack/cta-engine'
1212import { FileSyncer } from './file-syncer.js'
13+ import { createUIEnvironment } from './ui-environment.js'
1314import type {
1415 Environment ,
1516 Framework ,
@@ -61,6 +62,10 @@ class DebounceQueue {
6162 } , this . delay )
6263 }
6364
65+ size ( ) : number {
66+ return this . changes . size
67+ }
68+
6469 clear ( ) : void {
6570 if ( this . timer ) {
6671 clearTimeout ( this . timer )
@@ -100,11 +105,12 @@ export class DevWatchManager {
100105 throw new Error ( 'Cannot use the --no-install flag when using --dev-watch' )
101106 }
102107
103- // Log startup
104- this . log . info ( `Starting dev watch mode` )
105- this . log . info ( `Watching: ${ chalk . cyan ( this . options . watchPath ) } ` )
106- this . log . info ( `Target: ${ chalk . cyan ( this . options . targetDir ) } ` )
107- this . log . info ( `Waiting for file changes...` )
108+ // Log startup with tree style
109+ console . log ( )
110+ console . log ( chalk . bold ( 'dev-watch' ) )
111+ this . log . tree ( '' , `watching: ${ chalk . cyan ( this . options . watchPath ) } ` )
112+ this . log . tree ( '' , `target: ${ chalk . cyan ( this . options . targetDir ) } ` )
113+ this . log . tree ( '' , 'ready' , true )
108114
109115 // Setup signal handlers
110116 process . on ( 'SIGINT' , ( ) => this . cleanup ( ) )
@@ -115,7 +121,8 @@ export class DevWatchManager {
115121 }
116122
117123 async stop ( ) : Promise < void > {
118- this . log . info ( 'Stopping dev watch mode' )
124+ console . log ( )
125+ this . log . info ( 'Stopping dev watch mode...' )
119126
120127 if ( this . watcher ) {
121128 await this . watcher . close ( )
@@ -158,12 +165,20 @@ export class DevWatchManager {
158165 this . log . error ( `Watcher error: ${ error . message } ` ) ,
159166 )
160167
161- this . log . success ( 'File watcher started' )
168+ this . watcher . on ( 'ready' , ( ) => {
169+ // Already shown in startup, no need to repeat
170+ } )
162171 }
163172
164173 private handleChange ( _type : ChangeEvent [ 'type' ] , filePath : string ) : void {
165174 const relativePath = path . relative ( this . options . watchPath , filePath )
166- this . log . change ( relativePath )
175+ // Log change only once for the first file in debounce queue
176+ if ( this . debounceQueue . size ( ) === 0 ) {
177+ this . log . section ( 'change detected' )
178+ this . log . subsection ( `└─ ${ relativePath } ` )
179+ } else {
180+ this . log . subsection ( `└─ ${ relativePath } ` )
181+ }
167182 this . debounceQueue . add ( filePath )
168183 }
169184
@@ -178,7 +193,7 @@ export class DevWatchManager {
178193 const buildId = this . buildCount
179194
180195 try {
181- this . log . build ( `Starting build #${ buildId } `)
196+ this . log . section ( ` build #${ buildId } `)
182197 const startTime = Date . now ( )
183198
184199 if ( ! this . options . frameworkDefinitionInitializers ) {
@@ -241,8 +256,17 @@ export class DevWatchManager {
241256 install : packageJsonModified ,
242257 }
243258
244- // Create app in temp directory
245- await createApp ( this . options . environment , updatedOptions )
259+ // Show package installation indicator if needed
260+ if ( packageJsonModified ) {
261+ this . log . tree ( ' ' , `${ chalk . yellow ( '⟳' ) } installing packages...` )
262+ }
263+
264+ // Create app in temp directory with silent environment
265+ const silentEnvironment = createUIEnvironment (
266+ this . options . environment . appName ,
267+ true ,
268+ )
269+ await createApp ( silentEnvironment , updatedOptions )
246270
247271 // Sync files to target directory
248272 const syncResult = await this . syncer . sync (
@@ -260,18 +284,61 @@ export class DevWatchManager {
260284 }
261285
262286 const elapsed = Date . now ( ) - startTime
263- this . log . success ( `Build #${ buildId } completed in ${ elapsed } ms` )
264287
265- // Report sync results
288+ // Build tree-style summary
289+ this . log . tree ( ' ' , `duration: ${ chalk . cyan ( elapsed + 'ms' ) } ` )
290+
291+ if ( packageJsonModified ) {
292+ this . log . tree ( ' ' , `packages: ${ chalk . green ( '✓ installed' ) } ` )
293+ }
294+
295+ // Always show the last item in tree without checking for files to show
296+ const noMoreTreeItems =
297+ syncResult . updated . length === 0 &&
298+ syncResult . created . length === 0 &&
299+ syncResult . errors . length === 0
300+
266301 if ( syncResult . updated . length > 0 ) {
267- this . log . sync ( `Updated ${ syncResult . updated . length } files` )
268- syncResult . updated . forEach ( ( update ) => {
269- this . log . sync ( ` ↳ ${ update . path } ` )
302+ this . log . tree (
303+ ' ' ,
304+ `updated: ${ chalk . green ( syncResult . updated . length + ' file' + ( syncResult . updated . length > 1 ? 's' : '' ) ) } ` ,
305+ syncResult . created . length === 0 && syncResult . errors . length === 0 ,
306+ )
307+ }
308+ if ( syncResult . created . length > 0 ) {
309+ this . log . tree (
310+ ' ' ,
311+ `created: ${ chalk . green ( syncResult . created . length + ' file' + ( syncResult . created . length > 1 ? 's' : '' ) ) } ` ,
312+ syncResult . errors . length === 0 ,
313+ )
314+ }
315+ if ( syncResult . errors . length > 0 ) {
316+ this . log . tree (
317+ ' ' ,
318+ `failed: ${ chalk . red ( syncResult . errors . length + ' file' + ( syncResult . errors . length > 1 ? 's' : '' ) ) } ` ,
319+ true ,
320+ )
321+ }
270322
271- // Show diff if available
323+ // If nothing changed, show that
324+ if ( noMoreTreeItems ) {
325+ this . log . tree ( ' ' , `no changes` , true )
326+ }
327+
328+ // Always show changed files with diffs
329+ if ( syncResult . updated . length > 0 ) {
330+ syncResult . updated . forEach ( ( update , index ) => {
331+ const isLastFile =
332+ index === syncResult . updated . length - 1 &&
333+ syncResult . created . length === 0
334+
335+ // For files with diffs, always use ├─
336+ const fileIsLast = isLastFile && ! update . diff
337+ this . log . treeItem ( ' ' , update . path , fileIsLast )
338+
339+ // Always show diff if available
272340 if ( update . diff ) {
273341 const diffLines = update . diff . split ( '\n' )
274- // Skip the header lines and show the actual diff
275342 const relevantLines = diffLines
276343 . slice ( 4 )
277344 . filter (
@@ -282,49 +349,58 @@ export class DevWatchManager {
282349 )
283350
284351 if ( relevantLines . length > 0 ) {
285- this . log . sync ( ` ${ chalk . dim ( 'Changes:' ) } ` )
352+ // Always use │ to continue the tree line through the diff
353+ const prefix = ' │ '
286354 relevantLines . forEach ( ( line ) => {
287355 if ( line . startsWith ( '+' ) && ! line . startsWith ( '+++' ) ) {
288- this . log . sync ( ` ${ chalk . green ( line ) } ` )
356+ console . log ( chalk . gray ( prefix ) + ' ' + chalk . green ( line ) )
289357 } else if ( line . startsWith ( '-' ) && ! line . startsWith ( '---' ) ) {
290- this . log . sync ( ` ${ chalk . red ( line ) } ` )
358+ console . log ( chalk . gray ( prefix ) + ' ' + chalk . red ( line ) )
291359 } else if ( line . startsWith ( '@' ) ) {
292- this . log . sync ( ` ${ chalk . cyan ( line ) } ` )
360+ console . log ( chalk . gray ( prefix ) + ' ' + chalk . cyan ( line ) )
293361 }
294362 } )
295363 }
296364 }
297365 } )
298366 }
367+
368+ // Show created files
299369 if ( syncResult . created . length > 0 ) {
300- this . log . sync ( `Created ${ syncResult . created . length } files` )
301- syncResult . created . forEach ( ( file ) => this . log . sync ( ` ↳ ${ file } ` ) )
302- }
303- if ( syncResult . skipped . length > 0 ) {
304- this . log . sync ( `Skipped ${ syncResult . skipped . length } unchanged files` )
370+ syncResult . created . forEach ( ( file , index ) => {
371+ const isLast = index === syncResult . created . length - 1
372+ this . log . treeItem ( ' ' , `${ chalk . green ( '+' ) } ${ file } ` , isLast )
373+ } )
305374 }
375+
376+ // Always show errors
306377 if ( syncResult . errors . length > 0 ) {
307- this . log . error ( `Failed to sync ${ syncResult . errors . length } files` )
308- syncResult . errors . forEach ( ( err ) => this . log . error ( ` ↳ ${ err } ` ) )
378+ console . log ( ) // Add spacing
379+ syncResult . errors . forEach ( ( err , index ) => {
380+ this . log . tree (
381+ ' ' ,
382+ `${ chalk . red ( 'error:' ) } ${ err } ` ,
383+ index === syncResult . errors . length - 1 ,
384+ )
385+ } )
309386 }
310387 } catch ( error ) {
311388 this . log . error (
312389 `Build #${ buildId } failed: ${ error instanceof Error ? error . message : String ( error ) } ` ,
313390 )
314- console . error ( error )
315391 } finally {
316392 this . isBuilding = false
317393 }
318394 }
319395
320396 private cleanup ( ) : void {
321- this . log . info ( 'Cleaning up...' )
397+ console . log ( )
398+ console . log ( 'Cleaning up...' )
322399
323400 // Clean up temp directory
324401 if ( this . tempDir && fs . existsSync ( this . tempDir ) ) {
325402 try {
326403 fs . rmSync ( this . tempDir , { recursive : true , force : true } )
327- this . log . success ( 'Temp directory cleaned up' )
328404 } catch ( error ) {
329405 this . log . error (
330406 `Failed to clean up temp directory: ${ error instanceof Error ? error . message : String ( error ) } ` ,
@@ -336,13 +412,19 @@ export class DevWatchManager {
336412 }
337413
338414 private log = {
339- info : ( msg : string ) => console . log ( chalk . blue ( 'ℹ️ ' ) + ` ${ msg } ` ) ,
340- change : ( path : string ) =>
341- console . log ( chalk . yellow ( '📝' ) + ` File changed: ${ path } ` ) ,
342- build : ( msg : string ) => console . log ( chalk . cyan ( '🔨' ) + ` ${ msg } ` ) ,
343- sync : ( msg : string ) => console . log ( chalk . magenta ( ' ' ) + ` ${ msg } ` ) ,
344- error : ( msg : string ) => console . error ( chalk . red ( '❌' ) + ` ${ msg } ` ) ,
345- success : ( msg : string ) => console . log ( chalk . green ( '✅' ) + ` ${ msg } ` ) ,
346- warning : ( msg : string ) => console . log ( chalk . yellow ( '⚠️ ' ) + ` ${ msg } ` ) ,
415+ tree : ( prefix : string , msg : string , isLast = false ) => {
416+ const connector = isLast ? '└─' : '├─'
417+ console . log ( chalk . gray ( prefix + connector ) + ' ' + msg )
418+ } ,
419+ treeItem : ( prefix : string , msg : string , isLast = false ) => {
420+ const connector = isLast ? '└─' : '├─'
421+ console . log ( chalk . gray ( prefix + ' ' + connector ) + ' ' + msg )
422+ } ,
423+ info : ( msg : string ) => console . log ( msg ) ,
424+ error : ( msg : string ) => console . error ( chalk . red ( '✗' ) + ' ' + msg ) ,
425+ success : ( msg : string ) => console . log ( chalk . green ( '✓' ) + ' ' + msg ) ,
426+ warning : ( msg : string ) => console . log ( chalk . yellow ( '⚠' ) + ' ' + msg ) ,
427+ section : ( title : string ) => console . log ( '\n' + chalk . bold ( '▸ ' + title ) ) ,
428+ subsection : ( msg : string ) => console . log ( ' ' + msg ) ,
347429 }
348430}
0 commit comments