@zenithbuild/cli 0.6.6 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/build.d.ts +32 -0
  2. package/dist/build.js +193 -548
  3. package/dist/compiler-bridge-runner.d.ts +5 -0
  4. package/dist/compiler-bridge-runner.js +70 -0
  5. package/dist/component-instance-ir.d.ts +6 -0
  6. package/dist/component-instance-ir.js +0 -20
  7. package/dist/component-occurrences.d.ts +6 -0
  8. package/dist/component-occurrences.js +6 -28
  9. package/dist/dev-server.d.ts +18 -0
  10. package/dist/dev-server.js +65 -114
  11. package/dist/dev-watch.d.ts +1 -0
  12. package/dist/dev-watch.js +2 -2
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +6 -28
  15. package/dist/manifest.d.ts +23 -0
  16. package/dist/manifest.js +22 -48
  17. package/dist/preview.d.ts +100 -0
  18. package/dist/preview.js +418 -488
  19. package/dist/resolve-components.d.ts +39 -0
  20. package/dist/resolve-components.js +30 -104
  21. package/dist/server/resolve-request-route.d.ts +39 -0
  22. package/dist/server/resolve-request-route.js +104 -113
  23. package/dist/server-contract.d.ts +39 -0
  24. package/dist/server-contract.js +15 -67
  25. package/dist/toolchain-paths.d.ts +23 -0
  26. package/dist/toolchain-paths.js +111 -39
  27. package/dist/toolchain-runner.d.ts +33 -0
  28. package/dist/toolchain-runner.js +170 -0
  29. package/dist/types/generate-env-dts.d.ts +5 -0
  30. package/dist/types/generate-env-dts.js +4 -2
  31. package/dist/types/generate-routes-dts.d.ts +8 -0
  32. package/dist/types/generate-routes-dts.js +7 -5
  33. package/dist/types/index.d.ts +14 -0
  34. package/dist/types/index.js +16 -7
  35. package/dist/ui/env.d.ts +18 -0
  36. package/dist/ui/env.js +0 -12
  37. package/dist/ui/format.d.ts +33 -0
  38. package/dist/ui/format.js +7 -45
  39. package/dist/ui/logger.d.ts +59 -0
  40. package/dist/ui/logger.js +3 -32
  41. package/dist/version-check.d.ts +54 -0
  42. package/dist/version-check.js +41 -98
  43. package/package.json +6 -4
@@ -10,7 +10,6 @@
10
10
  //
11
11
  // V0: Uses Node.js http module + fs.watch. No external deps.
12
12
  // ---------------------------------------------------------------------------
13
-
14
13
  import { createServer } from 'node:http';
15
14
  import { existsSync, watch } from 'node:fs';
16
15
  import { readFile, stat } from 'node:fs/promises';
@@ -18,15 +17,8 @@ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from
18
17
  import { build } from './build.js';
19
18
  import { createSilentLogger } from './ui/logger.js';
20
19
  import { readChangeFingerprint } from './dev-watch.js';
21
- import {
22
- executeServerRoute,
23
- injectSsrPayload,
24
- loadRouteManifest,
25
- resolveWithinDist,
26
- toStaticFilePath
27
- } from './preview.js';
20
+ import { executeServerRoute, injectSsrPayload, loadRouteManifest, resolveWithinDist, toStaticFilePath } from './preview.js';
28
21
  import { resolveRequestRoute } from './server/resolve-request-route.js';
29
-
30
22
  const MIME_TYPES = {
31
23
  '.html': 'text/html',
32
24
  '.js': 'application/javascript',
@@ -36,10 +28,8 @@ const MIME_TYPES = {
36
28
  '.jpg': 'image/jpeg',
37
29
  '.svg': 'image/svg+xml'
38
30
  };
39
-
40
31
  // Note: V0 HMR script injection has been moved to the runtime client.
41
32
  // This server purely hosts the V1 HMR contract endpoints.
42
-
43
33
  /**
44
34
  * Create and start a development server.
45
35
  *
@@ -47,16 +37,8 @@ const MIME_TYPES = {
47
37
  * @returns {Promise<{ server: import('http').Server, port: number, close: () => void }>}
48
38
  */
49
39
  export async function createDevServer(options) {
50
- const {
51
- pagesDir,
52
- outDir,
53
- port = 3000,
54
- host = '127.0.0.1',
55
- config = {},
56
- logger: providedLogger = null
57
- } = options;
40
+ const { pagesDir, outDir, port = 3000, host = '127.0.0.1', config = {}, logger: providedLogger = null } = options;
58
41
  const logger = providedLogger || createSilentLogger();
59
-
60
42
  const resolvedPagesDir = resolve(pagesDir);
61
43
  const resolvedOutDir = resolve(outDir);
62
44
  const resolvedOutDirTmp = resolve(dirname(resolvedOutDir), `${basename(resolvedOutDir)}.tmp`);
@@ -65,7 +47,6 @@ export async function createDevServer(options) {
65
47
  ? dirname(pagesParentDir)
66
48
  : pagesParentDir;
67
49
  const watchRoots = new Set([pagesParentDir]);
68
-
69
50
  /** @type {import('http').ServerResponse[]} */
70
51
  const hmrClients = [];
71
52
  /** @type {import('fs').FSWatcher[]} */
@@ -74,12 +55,12 @@ export async function createDevServer(options) {
74
55
  for (const client of hmrClients) {
75
56
  try {
76
57
  client.write(': ping\n\n');
77
- } catch {
58
+ }
59
+ catch {
78
60
  // client disconnected
79
61
  }
80
62
  }
81
63
  }, 15000);
82
-
83
64
  let buildId = 0;
84
65
  let pendingBuildId = 0;
85
66
  let buildStatus = 'ok'; // 'ok' | 'error' | 'building'
@@ -88,44 +69,44 @@ export async function createDevServer(options) {
88
69
  let buildError = null;
89
70
  const traceEnabled = config.devTrace === true || process.env.ZENITH_DEV_TRACE === '1';
90
71
  const verboseLogging = traceEnabled || logger.mode?.logLevel === 'verbose';
91
-
92
72
  // Stable dev CSS endpoint points to this backing asset.
93
73
  let currentCssAssetPath = '';
94
74
  let currentCssHref = '';
95
75
  let currentCssContent = '';
96
76
  let actualPort = port;
97
-
98
77
  function _publicHost() {
99
78
  if (host === '0.0.0.0' || host === '::') {
100
79
  return '127.0.0.1';
101
80
  }
102
81
  return host;
103
82
  }
104
-
105
83
  function _serverOrigin() {
106
84
  return `http://${_publicHost()}:${actualPort}`;
107
85
  }
108
-
109
86
  function _trace(event, payload = {}) {
110
- if (!traceEnabled) return;
87
+ if (!traceEnabled)
88
+ return;
111
89
  try {
112
90
  const detail = Object.keys(payload).length > 0
113
91
  ? `${event} ${JSON.stringify(payload)}`
114
92
  : event;
115
93
  logger.verbose('BUILD', detail);
116
- } catch {
94
+ }
95
+ catch {
117
96
  // tracing must never break the dev server
118
97
  }
119
98
  }
120
-
121
99
  function _classifyPath(pathname) {
122
- if (pathname.startsWith('/__zenith_dev/events')) return 'dev_events';
123
- if (pathname.startsWith('/__zenith_dev/state')) return 'dev_state';
124
- if (pathname.startsWith('/__zenith_dev/styles.css')) return 'dev_styles';
125
- if (pathname.startsWith('/assets/')) return 'asset';
100
+ if (pathname.startsWith('/__zenith_dev/events'))
101
+ return 'dev_events';
102
+ if (pathname.startsWith('/__zenith_dev/state'))
103
+ return 'dev_state';
104
+ if (pathname.startsWith('/__zenith_dev/styles.css'))
105
+ return 'dev_styles';
106
+ if (pathname.startsWith('/assets/'))
107
+ return 'asset';
126
108
  return 'other';
127
109
  }
128
-
129
110
  function _trace404(req, url, details = {}) {
130
111
  _trace('http_404', {
131
112
  method: req.method || 'GET',
@@ -134,7 +115,6 @@ export async function createDevServer(options) {
134
115
  ...details
135
116
  });
136
117
  }
137
-
138
118
  function _pickCssAsset(assets) {
139
119
  if (!Array.isArray(assets) || assets.length === 0) {
140
120
  return '';
@@ -148,11 +128,9 @@ export async function createDevServer(options) {
148
128
  const preferred = cssAssets.find((entry) => /\/styles(\.|\/|$)/.test(entry));
149
129
  return preferred || cssAssets[0];
150
130
  }
151
-
152
131
  function _delay(ms) {
153
132
  return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
154
133
  }
155
-
156
134
  async function _waitForCssFile(absolutePath, retries = 16, delayMs = 40) {
157
135
  for (let i = 0; i <= retries; i++) {
158
136
  try {
@@ -160,7 +138,8 @@ export async function createDevServer(options) {
160
138
  if (info.isFile()) {
161
139
  return true;
162
140
  }
163
- } catch {
141
+ }
142
+ catch {
164
143
  // keep retrying
165
144
  }
166
145
  if (i < retries) {
@@ -169,7 +148,6 @@ export async function createDevServer(options) {
169
148
  }
170
149
  return false;
171
150
  }
172
-
173
151
  async function _syncCssStateFromBuild(buildResult, nextBuildId) {
174
152
  currentCssHref = `/__zenith_dev/styles.css?buildId=${nextBuildId}`;
175
153
  const candidate = _pickCssAsset(buildResult?.assets);
@@ -177,7 +155,6 @@ export async function createDevServer(options) {
177
155
  _trace('css_sync_skipped', { reason: 'no_css_asset', buildId: nextBuildId });
178
156
  return false;
179
157
  }
180
-
181
158
  const absoluteCssPath = join(outDir, candidate);
182
159
  const ready = await _waitForCssFile(absoluteCssPath);
183
160
  if (!ready) {
@@ -189,11 +166,11 @@ export async function createDevServer(options) {
189
166
  });
190
167
  return false;
191
168
  }
192
-
193
169
  let cssContent = '';
194
170
  try {
195
171
  cssContent = await readFile(absoluteCssPath, 'utf8');
196
- } catch {
172
+ }
173
+ catch {
197
174
  _trace('css_sync_skipped', {
198
175
  reason: 'css_read_failed',
199
176
  buildId: nextBuildId,
@@ -220,12 +197,10 @@ export async function createDevServer(options) {
220
197
  });
221
198
  cssContent = '/* zenith-dev: empty css */';
222
199
  }
223
-
224
200
  currentCssAssetPath = candidate;
225
201
  currentCssContent = cssContent;
226
202
  return true;
227
203
  }
228
-
229
204
  function _broadcastEvent(type, payload = {}) {
230
205
  const eventBuildId = Number.isInteger(payload.buildId) ? payload.buildId : buildId;
231
206
  const data = JSON.stringify({
@@ -242,12 +217,12 @@ export async function createDevServer(options) {
242
217
  for (const client of hmrClients) {
243
218
  try {
244
219
  client.write(`event: ${type}\ndata: ${data}\n\n`);
245
- } catch {
220
+ }
221
+ catch {
246
222
  // client disconnected
247
223
  }
248
224
  }
249
225
  }
250
-
251
226
  // Initial build
252
227
  try {
253
228
  logger.build('Initial build (id=0)', { onceKey: 'dev-initial-build' });
@@ -256,7 +231,8 @@ export async function createDevServer(options) {
256
231
  if (currentCssHref.length > 0) {
257
232
  logger.css(`ready (${currentCssHref})`, { onceKey: `css-ready:${buildId}:${currentCssHref}` });
258
233
  }
259
- } catch (err) {
234
+ }
235
+ catch (err) {
260
236
  buildStatus = 'error';
261
237
  buildError = { message: err instanceof Error ? err.message : String(err) };
262
238
  logger.error('initial build failed', {
@@ -264,14 +240,12 @@ export async function createDevServer(options) {
264
240
  error: err
265
241
  });
266
242
  }
267
-
268
243
  const server = createServer(async (req, res) => {
269
244
  const requestBase = typeof req.headers.host === 'string' && req.headers.host.length > 0
270
245
  ? `http://${req.headers.host}`
271
246
  : _serverOrigin();
272
247
  const url = new URL(req.url, requestBase);
273
248
  let pathname = url.pathname;
274
-
275
249
  // Legacy HMR endpoint (deprecated but kept alive to avoid breaking old caches instantly)
276
250
  if (pathname === '/__zenith_hmr') {
277
251
  res.writeHead(200, {
@@ -288,11 +262,11 @@ export async function createDevServer(options) {
288
262
  hmrClients.push(res);
289
263
  req.on('close', () => {
290
264
  const idx = hmrClients.indexOf(res);
291
- if (idx !== -1) hmrClients.splice(idx, 1);
265
+ if (idx !== -1)
266
+ hmrClients.splice(idx, 1);
292
267
  });
293
268
  return;
294
269
  }
295
-
296
270
  // V1 Dev State Endpoint
297
271
  if (pathname === '/__zenith_dev/state') {
298
272
  res.writeHead(200, {
@@ -310,7 +284,6 @@ export async function createDevServer(options) {
310
284
  }));
311
285
  return;
312
286
  }
313
-
314
287
  // V1 Dev Events Endpoint (SSE)
315
288
  if (pathname === '/__zenith_dev/events') {
316
289
  res.writeHead(200, {
@@ -324,11 +297,11 @@ export async function createDevServer(options) {
324
297
  hmrClients.push(res);
325
298
  req.on('close', () => {
326
299
  const idx = hmrClients.indexOf(res);
327
- if (idx !== -1) hmrClients.splice(idx, 1);
300
+ if (idx !== -1)
301
+ hmrClients.splice(idx, 1);
328
302
  });
329
303
  return;
330
304
  }
331
-
332
305
  if (pathname === '/__zenith_dev/styles.css') {
333
306
  if (typeof currentCssContent === 'string' && currentCssContent.length > 0) {
334
307
  res.writeHead(200, {
@@ -346,7 +319,8 @@ export async function createDevServer(options) {
346
319
  if (typeof css === 'string' && css.length > 0) {
347
320
  currentCssContent = css;
348
321
  }
349
- } catch {
322
+ }
323
+ catch {
350
324
  // keep serving last known CSS body below
351
325
  }
352
326
  }
@@ -365,7 +339,6 @@ export async function createDevServer(options) {
365
339
  res.end(currentCssContent);
366
340
  return;
367
341
  }
368
-
369
342
  if (pathname === '/__zenith/route-check') {
370
343
  try {
371
344
  // Security: Require explicitly designated header to prevent public oracle probing
@@ -374,23 +347,19 @@ export async function createDevServer(options) {
374
347
  res.end(JSON.stringify({ error: 'forbidden', message: 'invalid request context' }));
375
348
  return;
376
349
  }
377
-
378
350
  const targetPath = String(url.searchParams.get('path') || '/');
379
-
380
351
  // Security: Prevent protocol/domain injection in path
381
352
  if (targetPath.includes('://') || targetPath.startsWith('//') || /[\r\n]/.test(targetPath)) {
382
353
  res.writeHead(400, { 'Content-Type': 'application/json' });
383
354
  res.end(JSON.stringify({ error: 'invalid_path_format' }));
384
355
  return;
385
356
  }
386
-
387
357
  const targetUrl = new URL(targetPath, url.origin);
388
358
  if (targetUrl.origin !== url.origin) {
389
359
  res.writeHead(400, { 'Content-Type': 'application/json' });
390
360
  res.end(JSON.stringify({ error: 'external_route_evaluation_forbidden' }));
391
361
  return;
392
362
  }
393
-
394
363
  const routes = await loadRouteManifest(outDir);
395
364
  const resolvedCheck = resolveRequestRoute(targetUrl, routes);
396
365
  if (!resolvedCheck.matched || !resolvedCheck.route) {
@@ -398,7 +367,6 @@ export async function createDevServer(options) {
398
367
  res.end(JSON.stringify({ error: 'route_not_found' }));
399
368
  return;
400
369
  }
401
-
402
370
  const checkResult = await executeServerRoute({
403
371
  source: resolvedCheck.route.server_script || '',
404
372
  sourcePath: resolvedCheck.route.server_script_path || '',
@@ -420,12 +388,12 @@ export async function createDevServer(options) {
420
388
  if (parsedLoc.origin !== targetUrl.origin) {
421
389
  checkResult.result.location = '/'; // Fallback to root for open redirect attempt
422
390
  }
423
- } catch {
391
+ }
392
+ catch {
424
393
  checkResult.result.location = '/';
425
394
  }
426
395
  }
427
396
  }
428
-
429
397
  res.writeHead(200, {
430
398
  'Content-Type': 'application/json',
431
399
  'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
@@ -439,13 +407,13 @@ export async function createDevServer(options) {
439
407
  to: targetUrl.toString()
440
408
  }));
441
409
  return;
442
- } catch {
410
+ }
411
+ catch {
443
412
  res.writeHead(500, { 'Content-Type': 'application/json' });
444
413
  res.end(JSON.stringify({ error: 'route_check_failed' }));
445
414
  return;
446
415
  }
447
416
  }
448
-
449
417
  let resolvedPathFor404 = null;
450
418
  let staticRootFor404 = null;
451
419
  try {
@@ -460,32 +428,26 @@ export async function createDevServer(options) {
460
428
  res.end(asset);
461
429
  return;
462
430
  }
463
-
464
431
  const routes = await loadRouteManifest(outDir);
465
432
  const resolved = resolveRequestRoute(url, routes);
466
433
  let filePath = null;
467
-
468
434
  if (resolved.matched && resolved.route) {
469
435
  if (verboseLogging) {
470
- logger.router(
471
- `${req.method || 'GET'} ${pathname} -> ${resolved.route.path} params=${JSON.stringify(resolved.params)}`
472
- );
436
+ logger.router(`${req.method || 'GET'} ${pathname} -> ${resolved.route.path} params=${JSON.stringify(resolved.params)}`);
473
437
  }
474
438
  const output = resolved.route.output.startsWith('/')
475
439
  ? resolved.route.output.slice(1)
476
440
  : resolved.route.output;
477
441
  filePath = resolveWithinDist(outDir, output);
478
- } else {
442
+ }
443
+ else {
479
444
  filePath = toStaticFilePath(outDir, pathname);
480
445
  }
481
-
482
446
  resolvedPathFor404 = filePath;
483
447
  staticRootFor404 = outDir;
484
-
485
448
  if (!filePath) {
486
449
  throw new Error('not found');
487
450
  }
488
-
489
451
  let ssrPayload = null;
490
452
  if (resolved.matched && resolved.route?.server_script && resolved.route.prerender !== true) {
491
453
  let routeExecution = null;
@@ -501,7 +463,8 @@ export async function createDevServer(options) {
501
463
  routeFile: resolved.route.server_script_path || '',
502
464
  routeId: resolved.route.route_id || ''
503
465
  });
504
- } catch (error) {
466
+ }
467
+ catch (error) {
505
468
  ssrPayload = {
506
469
  __zenith_error: {
507
470
  code: 'LOAD_FAILED',
@@ -509,15 +472,11 @@ export async function createDevServer(options) {
509
472
  }
510
473
  };
511
474
  }
512
-
513
475
  const trace = routeExecution?.trace || { guard: 'none', load: 'none' };
514
476
  const routeId = resolved.route.route_id || '';
515
477
  if (verboseLogging) {
516
- logger.router(
517
- `${routeId || resolved.route.path} guard=${trace.guard} load=${trace.load}`
518
- );
478
+ logger.router(`${routeId || resolved.route.path} guard=${trace.guard} load=${trace.load}`);
519
479
  }
520
-
521
480
  const result = routeExecution?.result;
522
481
  if (result && result.kind === 'redirect') {
523
482
  const status = Number.isInteger(result.status) ? result.status : 302;
@@ -538,14 +497,14 @@ export async function createDevServer(options) {
538
497
  ssrPayload = result.data;
539
498
  }
540
499
  }
541
-
542
500
  let content = await readFile(filePath, 'utf8');
543
501
  if (ssrPayload) {
544
502
  content = injectSsrPayload(content, ssrPayload);
545
503
  }
546
504
  res.writeHead(200, { 'Content-Type': 'text/html' });
547
505
  res.end(content);
548
- } catch {
506
+ }
507
+ catch {
549
508
  _trace404(req, url, {
550
509
  reason: 'not_found',
551
510
  staticRoot: staticRootFor404,
@@ -555,7 +514,6 @@ export async function createDevServer(options) {
555
514
  res.end('404 Not Found');
556
515
  }
557
516
  });
558
-
559
517
  /**
560
518
  * Broadcast HMR reload to all connected clients.
561
519
  */
@@ -563,31 +521,29 @@ export async function createDevServer(options) {
563
521
  for (const client of hmrClients) {
564
522
  try {
565
523
  client.write('data: reload\n\n');
566
- } catch {
524
+ }
525
+ catch {
567
526
  // client disconnected
568
527
  }
569
528
  }
570
529
  }
571
-
572
530
  let _buildDebounce = null;
573
531
  let _queuedFiles = new Set();
574
532
  const _lastQueuedFingerprints = new Map();
575
533
  let _buildInFlight = false;
576
-
577
534
  function _isWithin(parent, child) {
578
535
  const rel = relative(parent, child);
579
536
  return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
580
537
  }
581
-
582
538
  function _toDisplayPath(absPath) {
583
539
  const rel = relative(projectRoot, absPath);
584
- if (rel === '') return '.';
540
+ if (rel === '')
541
+ return '.';
585
542
  if (!rel.startsWith('..') && !isAbsolute(rel)) {
586
543
  return rel;
587
544
  }
588
545
  return absPath;
589
546
  }
590
-
591
547
  function _shouldIgnoreChange(absPath) {
592
548
  if (_isWithin(resolvedOutDir, absPath)) {
593
549
  return true;
@@ -606,7 +562,6 @@ export async function createDevServer(options) {
606
562
  || segments.includes('target')
607
563
  || segments.includes('.turbo');
608
564
  }
609
-
610
565
  /**
611
566
  * Start watching source roots for changes.
612
567
  */
@@ -620,7 +575,6 @@ export async function createDevServer(options) {
620
575
  void drainBuildQueue();
621
576
  }, delayMs);
622
577
  };
623
-
624
578
  const drainBuildQueue = async () => {
625
579
  if (_buildInFlight) {
626
580
  return;
@@ -630,39 +584,33 @@ export async function createDevServer(options) {
630
584
  return;
631
585
  }
632
586
  _queuedFiles.clear();
633
-
634
587
  _buildInFlight = true;
635
588
  const cycleBuildId = pendingBuildId + 1;
636
589
  pendingBuildId = cycleBuildId;
637
590
  buildStatus = 'building';
638
591
  logger.build(`Rebuild (id=${cycleBuildId})`);
639
592
  _broadcastEvent('build_start', { buildId: cycleBuildId, changedFiles: changed });
640
-
641
593
  const startTime = Date.now();
642
594
  const previousCssAssetPath = currentCssAssetPath;
643
595
  const previousCssContent = currentCssContent;
644
596
  try {
645
597
  const buildResult = await build({ pagesDir, outDir, config, logger });
646
598
  const cssReady = await _syncCssStateFromBuild(buildResult, cycleBuildId);
647
- const cssChanged = cssReady && (
648
- currentCssAssetPath !== previousCssAssetPath ||
649
- currentCssContent !== previousCssContent
650
- );
599
+ const cssChanged = cssReady && (currentCssAssetPath !== previousCssAssetPath ||
600
+ currentCssContent !== previousCssContent);
651
601
  buildId = cycleBuildId;
652
602
  buildStatus = 'ok';
653
603
  buildError = null;
654
604
  lastBuildMs = Date.now();
655
605
  durationMs = lastBuildMs - startTime;
656
606
  logger.build(`Complete (id=${cycleBuildId}, ${durationMs}ms)`);
657
-
658
607
  _broadcastEvent('build_complete', {
659
608
  buildId: cycleBuildId,
660
609
  durationMs,
661
610
  status: buildStatus,
662
611
  cssHref: currentCssHref,
663
612
  changedFiles: changed
664
- }
665
- );
613
+ });
666
614
  _trace('state_snapshot', {
667
615
  status: buildStatus,
668
616
  buildId: cycleBuildId,
@@ -670,18 +618,17 @@ export async function createDevServer(options) {
670
618
  durationMs,
671
619
  changedFiles: changed
672
620
  });
673
-
674
621
  if (cssChanged && currentCssHref.length > 0) {
675
622
  logger.css(`ready (${currentCssHref})`);
676
623
  logger.hmr(`css_update (buildId=${cycleBuildId})`);
677
624
  _broadcastEvent('css_update', { href: currentCssHref, changedFiles: changed });
678
625
  }
679
-
680
626
  const onlyCss = changed.length > 0 && changed.every((f) => f.endsWith('.css'));
681
627
  if (!onlyCss) {
682
628
  logger.hmr(`reload (buildId=${cycleBuildId})`);
683
629
  _broadcastEvent('reload', { changedFiles: changed });
684
- } else {
630
+ }
631
+ else {
685
632
  _trace('css_only_update', {
686
633
  buildId: cycleBuildId,
687
634
  cssHref: currentCssHref,
@@ -689,7 +636,8 @@ export async function createDevServer(options) {
689
636
  changedFiles: changed
690
637
  });
691
638
  }
692
- } catch (err) {
639
+ }
640
+ catch (err) {
693
641
  const fullError = err instanceof Error ? err.message : String(err);
694
642
  buildStatus = 'error';
695
643
  buildError = { message: fullError.length > 10000 ? fullError.slice(0, 10000) + '... (truncated)' : fullError };
@@ -699,7 +647,6 @@ export async function createDevServer(options) {
699
647
  hint: 'fix the error and save again',
700
648
  error: err
701
649
  });
702
-
703
650
  _broadcastEvent('build_error', { buildId: cycleBuildId, ...buildError, changedFiles: changed });
704
651
  _trace('state_snapshot', {
705
652
  status: buildStatus,
@@ -708,17 +655,18 @@ export async function createDevServer(options) {
708
655
  durationMs,
709
656
  error: buildError
710
657
  });
711
- } finally {
658
+ }
659
+ finally {
712
660
  _buildInFlight = false;
713
661
  if (_queuedFiles.size > 0) {
714
662
  triggerBuildDrain(20);
715
663
  }
716
664
  }
717
665
  };
718
-
719
666
  const roots = Array.from(watchRoots);
720
667
  for (const root of roots) {
721
- if (!existsSync(root)) continue;
668
+ if (!existsSync(root))
669
+ continue;
722
670
  try {
723
671
  const watcher = watch(root, { recursive: true }, (_eventType, filename) => {
724
672
  if (!filename) {
@@ -739,17 +687,16 @@ export async function createDevServer(options) {
739
687
  })();
740
688
  });
741
689
  _watchers.push(watcher);
742
- } catch {
690
+ }
691
+ catch {
743
692
  // fs.watch recursive may not be supported on this platform/root
744
693
  }
745
694
  }
746
695
  }
747
-
748
696
  return new Promise((resolve) => {
749
697
  server.listen(port, host, () => {
750
698
  actualPort = server.address().port;
751
699
  _startWatcher();
752
-
753
700
  resolve({
754
701
  server,
755
702
  port: actualPort,
@@ -758,13 +705,17 @@ export async function createDevServer(options) {
758
705
  for (const watcher of _watchers) {
759
706
  try {
760
707
  watcher.close();
761
- } catch {
708
+ }
709
+ catch {
762
710
  // ignore close errors
763
711
  }
764
712
  }
765
713
  _watchers = [];
766
714
  for (const client of hmrClients) {
767
- try { client.end(); } catch { }
715
+ try {
716
+ client.end();
717
+ }
718
+ catch { }
768
719
  }
769
720
  hmrClients.length = 0;
770
721
  server.close();
@@ -0,0 +1 @@
1
+ export function readChangeFingerprint(absPath: any): Promise<string>;
package/dist/dev-watch.js CHANGED
@@ -1,5 +1,4 @@
1
1
  import { stat } from 'node:fs/promises';
2
-
3
2
  export async function readChangeFingerprint(absPath) {
4
3
  try {
5
4
  const info = await stat(absPath);
@@ -9,7 +8,8 @@ export async function readChangeFingerprint(absPath) {
9
8
  ? 'file'
10
9
  : 'other';
11
10
  return `${kind}:${info.mtimeMs}:${info.size}`;
12
- } catch (error) {
11
+ }
12
+ catch (error) {
13
13
  const code = error && typeof error === 'object' ? error.code : '';
14
14
  if (code === 'ENOENT' || code === 'ENOTDIR') {
15
15
  return 'missing';
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI entry point.
4
+ *
5
+ * @param {string[]} args - Process arguments (without node and script paths)
6
+ * @param {string} [cwd] - Working directory override
7
+ */
8
+ export function cli(args: string[], cwd?: string): Promise<void>;