88} from 'node:fs' ;
99import { homedir } from 'node:os' ;
1010import { basename , join } from 'node:path' ;
11+ import { getServerRegistry } from '../tdd/server-registry.js' ;
1112import * as output from '../utils/output.js' ;
1213import { tddCommand } from './tdd.js' ;
13- import { getServerRegistry } from '../tdd/server-registry.js' ;
1414
1515/**
1616 * Start TDD server in daemon mode
@@ -24,37 +24,69 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
2424 color : ! globalOptions . noColor ,
2525 } ) ;
2626
27- // Check if server already running
28- if ( await isServerRunning ( options . port || 47392 ) ) {
29- const port = options . port || 47392 ;
30- let colors = output . getColors ( ) ;
27+ let registry = getServerRegistry ( ) ;
28+ let colors = output . getColors ( ) ;
3129
32- output . header ( 'tdd' , 'local' ) ;
33- output . print ( ` ${ output . statusDot ( 'success' ) } Already running` ) ;
34- output . blank ( ) ;
35- output . printBox (
36- colors . brand . info ( colors . underline ( `http://localhost:${ port } ` ) ) ,
37- {
38- title : 'Dashboard' ,
39- style : 'branded' ,
40- }
41- ) ;
30+ // Check if THIS directory already has a server running
31+ let existingServer = registry . find ( { directory : process . cwd ( ) } ) ;
32+ if ( existingServer ) {
33+ // Verify it's actually running
34+ if ( await isServerRunning ( existingServer . port ) ) {
35+ output . header ( 'tdd' , 'local' ) ;
36+ output . print ( ` ${ output . statusDot ( 'success' ) } Already running` ) ;
37+ output . blank ( ) ;
38+ output . printBox (
39+ colors . brand . info (
40+ colors . underline ( `http://localhost:${ existingServer . port } ` )
41+ ) ,
42+ {
43+ title : 'Dashboard' ,
44+ style : 'branded' ,
45+ }
46+ ) ;
4247
43- if ( options . open ) {
44- openDashboard ( port ) ;
48+ if ( options . open ) {
49+ openDashboard ( existingServer . port ) ;
50+ }
51+ return ;
52+ } else {
53+ // Stale entry - clean it up
54+ registry . unregister ( { directory : process . cwd ( ) } ) ;
4555 }
56+ }
57+
58+ // Determine port: user-specified or auto-allocate
59+ let port ;
60+ let autoAllocated = false ;
61+
62+ if ( options . port ) {
63+ // User specified a port - use it (will fail if busy)
64+ port = options . port ;
65+ } else {
66+ // Auto-allocate an available port
67+ port = await registry . findAvailablePort ( ) ;
68+ autoAllocated = port !== 47392 ;
69+ }
70+
71+ // If user specified a port, check if it's in use
72+ if ( options . port && ( await isServerRunning ( port ) ) ) {
73+ output . header ( 'tdd' , 'local' ) ;
74+ output . print (
75+ ` ${ output . statusDot ( 'error' ) } Port ${ port } is already in use`
76+ ) ;
77+ output . blank ( ) ;
78+ output . hint ( 'Try a different port: vizzly tdd start --port 47393' ) ;
79+ output . hint ( 'Or let Vizzly auto-allocate: vizzly tdd start' ) ;
4680 return ;
4781 }
4882
4983 try {
5084 // Ensure .vizzly directory exists
51- const vizzlyDir = join ( process . cwd ( ) , '.vizzly' ) ;
85+ let vizzlyDir = join ( process . cwd ( ) , '.vizzly' ) ;
5286 if ( ! existsSync ( vizzlyDir ) ) {
5387 mkdirSync ( vizzlyDir , { recursive : true } ) ;
5488 }
5589
56- const port = options . port || 47392 ;
57-
5890 // Show header first so debug messages appear below it
5991 output . header ( 'tdd' , 'local' ) ;
6092
@@ -166,10 +198,10 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
166198
167199 // Register server in global registry (for menubar app)
168200 try {
169- let registry = getServerRegistry ( )
201+ let registry = getServerRegistry ( ) ;
170202
171203 // Clean up any stale servers first
172- registry . cleanupStale ( )
204+ registry . cleanupStale ( ) ;
173205
174206 // Register this server
175207 registry . register ( {
@@ -178,7 +210,7 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
178210 directory : process . cwd ( ) ,
179211 name : basename ( process . cwd ( ) ) ,
180212 startedAt : new Date ( ) . toISOString ( ) ,
181- } )
213+ } ) ;
182214 } catch {
183215 // Non-fatal
184216 }
@@ -200,8 +232,13 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
200232 // Non-fatal, SDK can still use health check
201233 }
202234
203- // Get colors for styled output
204- let colors = output . getColors ( ) ;
235+ // Show auto-allocated port message if applicable
236+ if ( autoAllocated ) {
237+ output . print (
238+ ` ${ output . statusDot ( 'info' ) } Auto-assigned port ${ colors . brand . textTertiary ( `:${ port } ` ) } `
239+ ) ;
240+ output . blank ( ) ;
241+ }
205242
206243 // Show dashboard URL in a branded box
207244 let dashboardUrl = `http://localhost:${ port } ` ;
@@ -288,8 +325,8 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
288325
289326 // Unregister from global registry (for menubar app)
290327 try {
291- let registry = getServerRegistry ( )
292- registry . unregister ( { port : port , directory : process . cwd ( ) } )
328+ let registry = getServerRegistry ( ) ;
329+ registry . unregister ( { port : port , directory : process . cwd ( ) } ) ;
293330 } catch {
294331 // Non-fatal
295332 }
@@ -420,8 +457,8 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
420457
421458 // Unregister from global registry (for menubar app)
422459 try {
423- let registry = getServerRegistry ( )
424- registry . unregister ( { port : port , directory : process . cwd ( ) } )
460+ let registry = getServerRegistry ( ) ;
461+ registry . unregister ( { port : port , directory : process . cwd ( ) } ) ;
425462 } catch {
426463 // Non-fatal
427464 }
@@ -436,8 +473,8 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
436473
437474 // Still unregister from registry
438475 try {
439- let registry = getServerRegistry ( )
440- registry . unregister ( { port : port , directory : process . cwd ( ) } )
476+ let registry = getServerRegistry ( ) ;
477+ registry . unregister ( { port : port , directory : process . cwd ( ) } ) ;
441478 } catch {
442479 // Non-fatal
443480 }
@@ -601,60 +638,66 @@ export async function tddListCommand(_options, globalOptions = {}) {
601638 color : ! globalOptions . noColor ,
602639 } ) ;
603640
604- let registry = getServerRegistry ( )
641+ let registry = getServerRegistry ( ) ;
605642
606643 // Clean up stale servers first
607- let cleaned = registry . cleanupStale ( )
644+ let cleaned = registry . cleanupStale ( ) ;
608645 if ( cleaned > 0 && globalOptions . verbose ) {
609- output . debug ( 'tdd' , `Cleaned up ${ cleaned } stale server(s)` )
646+ output . debug ( 'tdd' , `Cleaned up ${ cleaned } stale server(s)` ) ;
610647 }
611648
612- let servers = registry . list ( )
649+ let servers = registry . list ( ) ;
613650
614651 // JSON output
615652 if ( globalOptions . json ) {
616- console . log ( JSON . stringify ( { servers } , null , 2 ) )
617- return
653+ console . log ( JSON . stringify ( { servers } , null , 2 ) ) ;
654+ return ;
618655 }
619656
620657 // No servers
621658 if ( servers . length === 0 ) {
622- output . info ( 'No TDD servers running' )
623- output . hint ( 'Start one with: vizzly tdd start' )
624- return
659+ output . info ( 'No TDD servers running' ) ;
660+ output . hint ( 'Start one with: vizzly tdd start' ) ;
661+ return ;
625662 }
626663
627664 // Table output
628- let colors = output . getColors ( )
665+ let colors = output . getColors ( ) ;
629666
630- output . header ( 'tdd' , 'servers' )
631- output . blank ( )
667+ output . header ( 'tdd' , 'servers' ) ;
668+ output . blank ( ) ;
632669
633670 for ( let server of servers ) {
634- let uptimeStr = ''
671+ let uptimeStr = '' ;
635672 if ( server . startedAt ) {
636- let startTime = new Date ( server . startedAt ) . getTime ( )
637- let uptime = Math . floor ( ( Date . now ( ) - startTime ) / 1000 )
638- let hours = Math . floor ( uptime / 3600 )
639- let minutes = Math . floor ( ( uptime % 3600 ) / 60 )
640- if ( hours > 0 ) uptimeStr += `${ hours } h `
641- if ( minutes > 0 || hours > 0 ) uptimeStr += `${ minutes } m`
642- else uptimeStr = '<1m'
673+ let startTime = new Date ( server . startedAt ) . getTime ( ) ;
674+ let uptime = Math . floor ( ( Date . now ( ) - startTime ) / 1000 ) ;
675+ let hours = Math . floor ( uptime / 3600 ) ;
676+ let minutes = Math . floor ( ( uptime % 3600 ) / 60 ) ;
677+ if ( hours > 0 ) uptimeStr += `${ hours } h ` ;
678+ if ( minutes > 0 || hours > 0 ) uptimeStr += `${ minutes } m` ;
679+ else uptimeStr = '<1m' ;
643680 }
644681
645- let name = server . name || basename ( server . directory )
646- let portStr = colors . brand . textTertiary ( `:${ server . port } ` )
647- let uptimeLabel = uptimeStr ? colors . brand . textMuted ( ` · ${ uptimeStr } ` ) : ''
682+ let name = server . name || basename ( server . directory ) ;
683+ let portStr = colors . brand . textTertiary ( `:${ server . port } ` ) ;
684+ let uptimeLabel = uptimeStr
685+ ? colors . brand . textMuted ( ` · ${ uptimeStr } ` )
686+ : '' ;
648687
649- output . print ( ` ${ output . statusDot ( 'success' ) } ${ name } ${ portStr } ${ uptimeLabel } ` )
650- output . print ( ` ${ colors . brand . textMuted ( server . directory ) } ` )
688+ output . print (
689+ ` ${ output . statusDot ( 'success' ) } ${ name } ${ portStr } ${ uptimeLabel } `
690+ ) ;
691+ output . print ( ` ${ colors . brand . textMuted ( server . directory ) } ` ) ;
651692
652693 if ( globalOptions . verbose ) {
653- output . print ( ` ${ colors . brand . textMuted ( `PID: ${ server . pid } ` ) } ` )
694+ output . print ( ` ${ colors . brand . textMuted ( `PID: ${ server . pid } ` ) } ` ) ;
654695 }
655696
656- output . blank ( )
697+ output . blank ( ) ;
657698 }
658699
659- output . print ( ` ${ colors . brand . textTertiary ( `${ servers . length } server(s) running` ) } ` )
700+ output . print (
701+ ` ${ colors . brand . textTertiary ( `${ servers . length } server(s) running` ) } `
702+ ) ;
660703}
0 commit comments