@@ -55,12 +55,22 @@ export function setup(): Promise<void> {
5555 return initPromise ;
5656}
5757
58- async function doSetup ( ) : Promise < void > {
59- brakitDebug ( `[setup] doSetup called at ${ new Date ( ) . toISOString ( ) } ` ) ;
60-
61- const bus = new EventBus ( ) ;
62- const registry = new ServiceRegistry ( ) ;
58+ /* ------------------------------------------------------------------ */
59+ /* Phase 1 — Create stores & wire event subscriptions */
60+ /* ------------------------------------------------------------------ */
61+
62+ interface Stores {
63+ requestStore : RequestStore ;
64+ fetchStore : FetchStore ;
65+ logStore : LogStore ;
66+ errorStore : ErrorStore ;
67+ queryStore : QueryStore ;
68+ }
6369
70+ function createStores (
71+ bus : EventBus ,
72+ registry : ServiceRegistry ,
73+ ) : Stores {
6474 const requestStore = new RequestStore ( ) ;
6575 const fetchStore = new FetchStore ( ) ;
6676 const logStore = new LogStore ( ) ;
@@ -81,6 +91,16 @@ async function doSetup(): Promise<void> {
8191
8292 requestStore . onRequest ( ( req ) => bus . emit ( "request:completed" , req ) ) ;
8393
94+ return { requestStore, fetchStore, logStore, errorStore, queryStore } ;
95+ }
96+
97+ /* ------------------------------------------------------------------ */
98+ /* Phase 2 — Install instrumentation hooks */
99+ /* ------------------------------------------------------------------ */
100+
101+ function installHooks (
102+ bus : EventBus ,
103+ ) : { framework : Framework ; adapterNames : string [ ] } {
84104 const telemetryEmit = ( event : TelemetryEvent ) : void => {
85105 const channel = `telemetry:${ event . type } ` as keyof ChannelMap ;
86106 bus . emit ( channel , event . data as ChannelMap [ typeof channel ] ) ;
@@ -98,22 +118,37 @@ async function doSetup(): Promise<void> {
98118 let framework : Framework = "unknown" ;
99119 try {
100120 const pkg = JSON . parse (
101- await readFile ( resolve ( cwd , "package.json" ) , "utf-8" ) ,
121+ // readFileSync is acceptable here — runs once at startup
122+ require ( "node:fs" ) . readFileSync ( resolve ( cwd , "package.json" ) , "utf-8" ) ,
102123 ) ;
103124 const allDeps = { ...pkg . dependencies , ...pkg . devDependencies } ;
104125 framework = detectFrameworkFromDeps ( allDeps ) ;
105126 } catch {
106127 /* no package.json */
107128 }
108129
109- initSession (
130+ return {
110131 framework,
111- detectPackageManagerSync ( cwd ) ,
112- false ,
113- adapterRegistry . getActive ( ) . map ( ( a ) => a . name ) ,
114- ) ;
132+ adapterNames : adapterRegistry . getActive ( ) . map ( ( a ) => a . name ) ,
133+ } ;
134+ }
115135
116- const dataDir = getProjectDataDir ( cwd ) ;
136+ /* ------------------------------------------------------------------ */
137+ /* Phase 3 — Start analysis, metrics & issue tracking */
138+ /* ------------------------------------------------------------------ */
139+
140+ interface AnalysisServices {
141+ analysisEngine : AnalysisEngine ;
142+ metricsStore : MetricsStore ;
143+ issueStore : IssueStore ;
144+ }
145+
146+ function startAnalysis (
147+ registry : ServiceRegistry ,
148+ stores : Stores ,
149+ dataDir : string ,
150+ ) : AnalysisServices {
151+ const bus = registry . get ( "event-bus" ) ;
117152
118153 const metricsStore = new MetricsStore ( new FileMetricsPersistence ( dataDir ) ) ;
119154 metricsStore . start ( ) ;
@@ -128,15 +163,102 @@ async function doSetup(): Promise<void> {
128163 registry . register ( "analysis-engine" , analysisEngine ) ;
129164
130165 bus . on ( "request:completed" , ( req ) => {
131- const queries = queryStore . getByRequest ( req . id ) ;
132- const fetches = fetchStore . getByRequest ( req . id ) ;
166+ const queries = stores . queryStore . getByRequest ( req . id ) ;
167+ const fetches = stores . fetchStore . getByRequest ( req . id ) ;
133168 metricsStore . recordRequest ( req , {
134169 queryCount : queries . length ,
135170 queryTimeMs : queries . reduce ( ( s , q ) => s + q . durationMs , 0 ) ,
136171 fetchTimeMs : fetches . reduce ( ( s , f ) => s + f . durationMs , 0 ) ,
137172 } ) ;
138173 } ) ;
139174
175+ return { analysisEngine, metricsStore, issueStore } ;
176+ }
177+
178+ /* ------------------------------------------------------------------ */
179+ /* Phase 4 — Register lifecycle (teardown + process handlers) */
180+ /* ------------------------------------------------------------------ */
181+
182+ function registerLifecycle (
183+ registry : ServiceRegistry ,
184+ stores : Stores ,
185+ services : AnalysisServices ,
186+ cwd : string ,
187+ ) : void {
188+ let telemetrySent = false ;
189+ const sendTelemetry = ( ) : void => {
190+ if ( telemetrySent ) return ;
191+ telemetrySent = true ;
192+ recordRequestCount ( stores . requestStore . getAll ( ) . length ) ;
193+ recordInsightTypes ( services . analysisEngine . getInsights ( ) . map ( ( i ) => i . type ) ) ;
194+ recordRulesTriggered (
195+ services . analysisEngine . getFindings ( ) . map ( ( f ) => f . rule ) ,
196+ ) ;
197+ trackSession ( registry ) ;
198+ } ;
199+
200+ let teardownCalled = false ;
201+ const runTeardown = ( ) : void => {
202+ if ( teardownCalled ) return ;
203+ teardownCalled = true ;
204+
205+ sendTelemetry ( ) ;
206+ uninstallInterceptor ( ) ;
207+ services . analysisEngine . stop ( ) ;
208+ services . issueStore . stop ( ) ;
209+ services . metricsStore . stop ( ) ;
210+
211+ try {
212+ const portPath = resolve ( cwd , PORT_FILE ) ;
213+ if ( existsSync ( portPath ) ) unlinkSync ( portPath ) ;
214+ } catch ( err ) {
215+ brakitDebug ( `[setup] port file cleanup failed: ${ getErrorMessage ( err ) } ` ) ;
216+ }
217+ } ;
218+
219+ health . setTeardown ( runTeardown ) ;
220+
221+ // Send telemetry while async operations still work (before 'exit').
222+ process . on ( "beforeExit" , ( ) => {
223+ sendTelemetry ( ) ;
224+ } ) ;
225+ // Run full teardown on exit — only sync code runs here, which is fine
226+ // because runTeardown is fully synchronous. Do NOT call process.exit()
227+ // — let the host app control its own shutdown lifecycle.
228+ process . on ( "exit" , ( ) => {
229+ runTeardown ( ) ;
230+ } ) ;
231+ }
232+
233+ /* ------------------------------------------------------------------ */
234+ /* Orchestrator */
235+ /* ------------------------------------------------------------------ */
236+
237+ async function doSetup ( ) : Promise < void > {
238+ brakitDebug ( `[setup] doSetup called at ${ new Date ( ) . toISOString ( ) } ` ) ;
239+
240+ const bus = new EventBus ( ) ;
241+ const registry = new ServiceRegistry ( ) ;
242+ const cwd = process . cwd ( ) ;
243+
244+ // Phase 1 — stores & event wiring
245+ const stores = createStores ( bus , registry ) ;
246+
247+ // Phase 2 — instrumentation hooks
248+ const { framework, adapterNames } = installHooks ( bus ) ;
249+
250+ initSession (
251+ framework ,
252+ detectPackageManagerSync ( cwd ) ,
253+ false ,
254+ adapterNames ,
255+ ) ;
256+
257+ // Phase 3 — analysis, metrics & issues
258+ const dataDir = getProjectDataDir ( cwd ) ;
259+ const services = startAnalysis ( registry , stores , dataDir ) ;
260+
261+ // Phase 4 — HTTP interceptor + dashboard
140262 const config : BrakitConfig = {
141263 proxyPort : 0 ,
142264 targetPort : 0 ,
@@ -146,15 +268,12 @@ async function doSetup(): Promise<void> {
146268
147269 const handleDashboard = createDashboardHandler ( registry ) ;
148270
149- let terminalDispose : ( ( ) => void ) | null = null ;
150-
151271 installInterceptor ( {
152272 handleDashboard,
153273 config,
154- requestStore,
274+ requestStore : stores . requestStore ,
155275 onFirstRequest ( port ) {
156276 setBrakitPort ( port ) ;
157-
158277 brakitDebug ( `[setup] onFirstRequest fired, port=${ port } ` ) ;
159278
160279 void ( async ( ) => {
@@ -184,53 +303,13 @@ async function doSetup(): Promise<void> {
184303 }
185304 } ) ( ) ;
186305
187- terminalDispose = startTerminalInsights ( registry , port ) ;
306+ startTerminalInsights ( registry , port ) ;
188307 process . stdout . write (
189308 ` brakit v${ VERSION } — http://localhost:${ port } ${ DASHBOARD_PREFIX } \n` ,
190309 ) ;
191310 } ,
192311 } ) ;
193312
194- let telemetrySent = false ;
195- const sendTelemetry = ( ) : void => {
196- if ( telemetrySent ) return ;
197- telemetrySent = true ;
198- recordRequestCount ( requestStore . getAll ( ) . length ) ;
199- recordInsightTypes ( analysisEngine . getInsights ( ) . map ( ( i ) => i . type ) ) ;
200- recordRulesTriggered ( analysisEngine . getFindings ( ) . map ( ( f ) => f . rule ) ) ;
201- trackSession ( registry ) ;
202- } ;
203-
204- let teardownCalled = false ;
205- const runTeardown = ( ) : void => {
206- if ( teardownCalled ) return ;
207- teardownCalled = true ;
208-
209- sendTelemetry ( ) ;
210- uninstallInterceptor ( ) ;
211- terminalDispose ?.( ) ;
212- analysisEngine . stop ( ) ;
213- issueStore . stop ( ) ;
214- metricsStore . stop ( ) ;
215-
216- try {
217- const portPath = resolve ( cwd , PORT_FILE ) ;
218- if ( existsSync ( portPath ) ) unlinkSync ( portPath ) ;
219- } catch ( err ) {
220- brakitDebug ( `[setup] port file cleanup failed: ${ getErrorMessage ( err ) } ` ) ;
221- }
222- } ;
223-
224- health . setTeardown ( runTeardown ) ;
225-
226- // Send telemetry while async operations still work (before 'exit').
227- process . on ( "beforeExit" , ( ) => {
228- sendTelemetry ( ) ;
229- } ) ;
230- // Run full teardown on exit — only sync code runs here, which is fine
231- // because runTeardown is fully synchronous. Do NOT call process.exit()
232- // — let the host app control its own shutdown lifecycle.
233- process . on ( "exit" , ( ) => {
234- runTeardown ( ) ;
235- } ) ;
313+ // Phase 5 — lifecycle (teardown + process handlers)
314+ registerLifecycle ( registry , stores , services , cwd ) ;
236315}
0 commit comments