11import { fileURLToPath } from 'node:url' ;
22import { consola } from 'consola' ;
3+ import * as chokidar from 'chokidar' ;
34import { cac } from 'cac' ;
45import { readFileSync } from 'node:fs' ;
56import { join , dirname } from 'node:path' ;
67import { createCore } from './core/create.js' ;
8+ import type { FSWatcher } from 'chokidar' ;
9+ import type { StartCommandHandle , StartCloseReason , BuildCommandHandle } from './types.js' ;
10+ import { createBatchChangeHandler } from './helpers/watcher.js' ;
711
812const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
913
1014const cli = cac ( 'ice-pkg' ) ;
1115
16+ function getSignalExitCode ( signal : NodeJS . Signals ) : number {
17+ return signal === 'SIGINT' ? 130 : 143 ;
18+ }
19+
20+ function exitWithSignal ( signal : NodeJS . Signals ) : never {
21+ process . exit ( getSignalExitCode ( signal ) ) ;
22+ }
23+
24+ async function runBuildWithSignalClose ( options : { rootDir : string ; [ key : string ] : unknown } ) {
25+ delete options [ '--' ] ;
26+ const { rootDir, ...commandArgs } = options ;
27+
28+ const pkg = await createCore ( {
29+ rootDir,
30+ command : 'build' ,
31+ commandArgs,
32+ } ) ;
33+
34+ let commandHandle : BuildCommandHandle | null = null ;
35+
36+ const shutdown = ( signal : NodeJS . Signals ) => {
37+ exitWithSignal ( signal ) ;
38+ } ;
39+
40+ const onSigint = ( ) => shutdown ( 'SIGINT' ) ;
41+ const onSigterm = ( ) => shutdown ( 'SIGTERM' ) ;
42+ process . on ( 'SIGINT' , onSigint ) ;
43+ process . on ( 'SIGTERM' , onSigterm ) ;
44+
45+ try {
46+ commandHandle = ( await pkg . run ( ) ) as BuildCommandHandle ;
47+
48+ if ( commandHandle . error ) {
49+ await commandHandle . dispose ( 'build-error' ) ;
50+ throw commandHandle . error ;
51+ }
52+
53+ await commandHandle . dispose ( 'build-finished' ) ;
54+ } finally {
55+ process . off ( 'SIGINT' , onSigint ) ;
56+ process . off ( 'SIGTERM' , onSigterm ) ;
57+ }
58+ }
59+
60+ async function runStartWithConfigRestart ( options : { rootDir : string ; [ key : string ] : unknown } ) {
61+ delete options [ '--' ] ;
62+ const { rootDir, ...commandArgs } = options ;
63+
64+ let currentPkg : Awaited < ReturnType < typeof createCore > > | null = null ;
65+ let currentCommand : StartCommandHandle | null = null ;
66+ let currentCommandClosed = false ;
67+ let configWatcher : FSWatcher | null = null ;
68+ let watchedConfigFile : string | null = null ;
69+ let shuttingDown = false ;
70+ let disposing = false ;
71+
72+ const batchHandler = createBatchChangeHandler ( restartStartRuntime ) ;
73+
74+ async function createAndRunStart ( ) {
75+ const pkg = await createCore ( {
76+ rootDir,
77+ command : 'start' ,
78+ commandArgs,
79+ } ) ;
80+ const runtime = ( await pkg . run ( ) ) as StartCommandHandle ;
81+ currentPkg = pkg ;
82+ currentCommand = runtime ;
83+ currentCommandClosed = false ;
84+ }
85+
86+ async function closeCurrentRuntime ( reason : StartCloseReason ) {
87+ if ( ! currentCommand || currentCommandClosed ) {
88+ return ;
89+ }
90+ currentCommandClosed = true ;
91+ disposing = true ;
92+ try {
93+ await currentCommand . dispose ( reason ) ;
94+ } finally {
95+ disposing = false ;
96+ }
97+ }
98+
99+ async function updateConfigWatcher ( ) {
100+ const nextConfigFile = currentPkg ?. resolvedConfigFile ?? null ;
101+ if ( nextConfigFile === watchedConfigFile ) {
102+ return ;
103+ }
104+
105+ await configWatcher ?. close ( ) ;
106+ configWatcher = null ;
107+ watchedConfigFile = nextConfigFile ;
108+
109+ if ( ! watchedConfigFile ) {
110+ return ;
111+ }
112+
113+ configWatcher = chokidar . watch ( [ watchedConfigFile ] , {
114+ ignoreInitial : true ,
115+ ignorePermissionErrors : true ,
116+ } ) ;
117+
118+ configWatcher . on ( 'add' , batchHandler . onChange ) ;
119+ configWatcher . on ( 'change' , batchHandler . onChange ) ;
120+ configWatcher . on ( 'unlink' , batchHandler . onChange ) ;
121+ configWatcher . on ( 'error' , ( error ) => consola . error ( error ) ) ;
122+ }
123+
124+ async function restartStartRuntime ( ) {
125+ if ( shuttingDown ) {
126+ return ;
127+ }
128+
129+ try {
130+ consola . info ( 'Configuration changed, restarting...' ) ;
131+ await closeCurrentRuntime ( 'config-change' ) ;
132+ await createAndRunStart ( ) ;
133+ await updateConfigWatcher ( ) ;
134+ } catch ( error ) {
135+ consola . error ( error ) ;
136+ }
137+ }
138+
139+ async function shutdown ( signal : NodeJS . Signals ) {
140+ if ( ( signal === 'SIGINT' && disposing ) || shuttingDown ) {
141+ exitWithSignal ( signal ) ;
142+ }
143+ shuttingDown = true ;
144+
145+ await configWatcher ?. close ( ) ;
146+ configWatcher = null ;
147+
148+ const shutdownHintTimer =
149+ signal === 'SIGINT'
150+ ? setTimeout ( ( ) => {
151+ consola . warn ( 'Still shutting down. Please wait, or press Ctrl+C again to force exit.' ) ;
152+ } , 1000 )
153+ : null ;
154+
155+ try {
156+ await closeCurrentRuntime ( 'signal-exit' ) ;
157+ } catch ( error ) {
158+ consola . error ( error ) ;
159+ } finally {
160+ if ( shutdownHintTimer ) {
161+ clearTimeout ( shutdownHintTimer ) ;
162+ }
163+ }
164+
165+ exitWithSignal ( signal ) ;
166+ }
167+
168+ const onSigint = ( ) => {
169+ void shutdown ( 'SIGINT' ) ;
170+ } ;
171+ const onSigterm = ( ) => {
172+ void shutdown ( 'SIGTERM' ) ;
173+ } ;
174+
175+ process . on ( 'SIGINT' , onSigint ) ;
176+ process . on ( 'SIGTERM' , onSigterm ) ;
177+
178+ await createAndRunStart ( ) ;
179+ await updateConfigWatcher ( ) ;
180+ }
181+
12182( async ( ) => {
13183 cli
14184 . command ( 'build' , 'Bundle files' , {
@@ -22,16 +192,7 @@ const cli = cac('ice-pkg');
22192 default : process . cwd ( ) ,
23193 } )
24194 . action ( async ( options ) => {
25- delete options [ '--' ] ;
26- const { rootDir, ...commandArgs } = options ;
27-
28- const pkg = await createCore ( {
29- rootDir : options . rootDir ,
30- command : 'build' ,
31- commandArgs,
32- } ) ;
33-
34- await pkg . run ( ) ;
195+ await runBuildWithSignalClose ( options ) ;
35196 } ) ;
36197
37198 cli
@@ -49,16 +210,7 @@ const cli = cac('ice-pkg');
49210 . option ( '--port <port>' , 'Override default server port' , { } )
50211 . option ( '--host <host>' , 'Override default server host' , { } )
51212 . action ( async ( options ) => {
52- delete options [ '--' ] ;
53- const { rootDir, ...commandArgs } = options ;
54-
55- const pkg = await createCore ( {
56- rootDir : options . rootDir ,
57- command : 'start' ,
58- commandArgs,
59- } ) ;
60-
61- await pkg . run ( ) ;
213+ await runStartWithConfigRestart ( options ) ;
62214 } ) ;
63215
64216 cli . help ( ) ;
0 commit comments