@zenithbuild/cli 0.5.0-beta.2.19 → 0.5.0-beta.2.20
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.
- package/dist/dev-server.js +115 -55
- package/package.json +2 -2
package/dist/dev-server.js
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
//
|
|
6
6
|
// - Compiles pages on demand
|
|
7
7
|
// - Rebuilds on file change
|
|
8
|
-
// -
|
|
8
|
+
// - Exposes V1 HMR endpoints consumed by runtime dev client
|
|
9
9
|
// - Server route resolution uses manifest matching
|
|
10
10
|
//
|
|
11
11
|
// V0: Uses Node.js http module + fs.watch. No external deps.
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
|
|
14
14
|
import { createServer } from 'node:http';
|
|
15
|
-
import { watch } from 'node:fs';
|
|
15
|
+
import { existsSync, watch } from 'node:fs';
|
|
16
16
|
import { readFile } from 'node:fs/promises';
|
|
17
|
-
import {
|
|
17
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
18
18
|
import { build } from './build.js';
|
|
19
19
|
import {
|
|
20
20
|
executeServerRoute,
|
|
@@ -52,9 +52,19 @@ export async function createDevServer(options) {
|
|
|
52
52
|
config = {}
|
|
53
53
|
} = options;
|
|
54
54
|
|
|
55
|
+
const resolvedPagesDir = resolve(pagesDir);
|
|
56
|
+
const resolvedOutDir = resolve(outDir);
|
|
57
|
+
const resolvedOutDirTmp = resolve(dirname(resolvedOutDir), `${basename(resolvedOutDir)}.tmp`);
|
|
58
|
+
const pagesParentDir = dirname(resolvedPagesDir);
|
|
59
|
+
const projectRoot = basename(pagesParentDir) === 'src'
|
|
60
|
+
? dirname(pagesParentDir)
|
|
61
|
+
: pagesParentDir;
|
|
62
|
+
const watchRoots = new Set([pagesParentDir]);
|
|
63
|
+
|
|
55
64
|
/** @type {import('http').ServerResponse[]} */
|
|
56
65
|
const hmrClients = [];
|
|
57
|
-
|
|
66
|
+
/** @type {import('fs').FSWatcher[]} */
|
|
67
|
+
let _watchers = [];
|
|
58
68
|
|
|
59
69
|
let buildId = 0;
|
|
60
70
|
let buildStatus = 'ok'; // 'ok' | 'error' | 'building'
|
|
@@ -332,62 +342,108 @@ export async function createDevServer(options) {
|
|
|
332
342
|
let _buildDebounce = null;
|
|
333
343
|
let _queuedFiles = new Set();
|
|
334
344
|
|
|
345
|
+
function _isWithin(parent, child) {
|
|
346
|
+
const rel = relative(parent, child);
|
|
347
|
+
return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function _toDisplayPath(absPath) {
|
|
351
|
+
const rel = relative(projectRoot, absPath);
|
|
352
|
+
if (rel === '') return '.';
|
|
353
|
+
if (!rel.startsWith('..') && !isAbsolute(rel)) {
|
|
354
|
+
return rel;
|
|
355
|
+
}
|
|
356
|
+
return absPath;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function _shouldIgnoreChange(absPath) {
|
|
360
|
+
if (_isWithin(resolvedOutDir, absPath)) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
if (_isWithin(resolvedOutDirTmp, absPath)) {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
const rel = relative(projectRoot, absPath);
|
|
367
|
+
if (rel.startsWith('..') || isAbsolute(rel)) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const segments = rel.split(/[\\/]+/g);
|
|
371
|
+
return segments.includes('node_modules')
|
|
372
|
+
|| segments.includes('.git')
|
|
373
|
+
|| segments.includes('.zenith')
|
|
374
|
+
|| segments.includes('target')
|
|
375
|
+
|| segments.includes('.turbo');
|
|
376
|
+
}
|
|
377
|
+
|
|
335
378
|
/**
|
|
336
|
-
* Start watching
|
|
379
|
+
* Start watching source roots for changes.
|
|
337
380
|
*/
|
|
338
381
|
function _startWatcher() {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
382
|
+
const queueRebuild = () => {
|
|
383
|
+
if (_buildDebounce !== null) {
|
|
384
|
+
clearTimeout(_buildDebounce);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
_buildDebounce = setTimeout(async () => {
|
|
388
|
+
_buildDebounce = null;
|
|
389
|
+
const changed = Array.from(_queuedFiles).map(_toDisplayPath).sort();
|
|
390
|
+
_queuedFiles.clear();
|
|
342
391
|
|
|
343
|
-
|
|
392
|
+
buildId++;
|
|
393
|
+
buildStatus = 'building';
|
|
394
|
+
_broadcastEvent('build_start', { changedFiles: changed });
|
|
344
395
|
|
|
345
|
-
|
|
346
|
-
|
|
396
|
+
const startTime = Date.now();
|
|
397
|
+
try {
|
|
398
|
+
await build({ pagesDir, outDir, config });
|
|
399
|
+
buildStatus = 'ok';
|
|
400
|
+
buildError = null;
|
|
401
|
+
lastBuildMs = Date.now();
|
|
402
|
+
durationMs = lastBuildMs - startTime;
|
|
403
|
+
|
|
404
|
+
_broadcastEvent('build_complete', {
|
|
405
|
+
durationMs,
|
|
406
|
+
status: buildStatus
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const onlyCss = changed.length > 0 && changed.every((f) => f.endsWith('.css'));
|
|
410
|
+
if (onlyCss) {
|
|
411
|
+
// Let the client fetch the updated CSS automatically
|
|
412
|
+
_broadcastEvent('css_update', {});
|
|
413
|
+
} else {
|
|
414
|
+
_broadcastEvent('reload', {});
|
|
415
|
+
}
|
|
416
|
+
} catch (err) {
|
|
417
|
+
const fullError = err instanceof Error ? err.message : String(err);
|
|
418
|
+
buildStatus = 'error';
|
|
419
|
+
buildError = { message: fullError.length > 10000 ? fullError.slice(0, 10000) + '... (truncated)' : fullError };
|
|
420
|
+
lastBuildMs = Date.now();
|
|
421
|
+
durationMs = lastBuildMs - startTime;
|
|
422
|
+
|
|
423
|
+
_broadcastEvent('build_error', buildError);
|
|
347
424
|
}
|
|
425
|
+
}, 50);
|
|
426
|
+
};
|
|
348
427
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
_broadcastEvent('build_start', { changedFiles: changed });
|
|
357
|
-
|
|
358
|
-
const startTime = Date.now();
|
|
359
|
-
try {
|
|
360
|
-
await build({ pagesDir, outDir, config });
|
|
361
|
-
buildStatus = 'ok';
|
|
362
|
-
buildError = null;
|
|
363
|
-
lastBuildMs = Date.now();
|
|
364
|
-
durationMs = lastBuildMs - startTime;
|
|
365
|
-
|
|
366
|
-
_broadcastEvent('build_complete', {
|
|
367
|
-
durationMs,
|
|
368
|
-
status: buildStatus
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
const onlyCss = changed.every(f => f.endsWith('.css'));
|
|
372
|
-
if (onlyCss) {
|
|
373
|
-
// Let the client fetch the updated CSS automatically
|
|
374
|
-
_broadcastEvent('css_update', {});
|
|
375
|
-
} else {
|
|
376
|
-
_broadcastEvent('reload', {});
|
|
377
|
-
}
|
|
378
|
-
} catch (err) {
|
|
379
|
-
const fullError = err instanceof Error ? err.message : String(err);
|
|
380
|
-
buildStatus = 'error';
|
|
381
|
-
buildError = { message: fullError.length > 10000 ? fullError.slice(0, 10000) + '... (truncated)' : fullError };
|
|
382
|
-
lastBuildMs = Date.now();
|
|
383
|
-
durationMs = lastBuildMs - startTime;
|
|
384
|
-
|
|
385
|
-
_broadcastEvent('build_error', buildError);
|
|
428
|
+
const roots = Array.from(watchRoots);
|
|
429
|
+
for (const root of roots) {
|
|
430
|
+
if (!existsSync(root)) continue;
|
|
431
|
+
try {
|
|
432
|
+
const watcher = watch(root, { recursive: true }, (_eventType, filename) => {
|
|
433
|
+
if (!filename) {
|
|
434
|
+
return;
|
|
386
435
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
436
|
+
const changedPath = resolve(root, String(filename));
|
|
437
|
+
if (_shouldIgnoreChange(changedPath)) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
_queuedFiles.add(changedPath);
|
|
441
|
+
queueRebuild();
|
|
442
|
+
});
|
|
443
|
+
_watchers.push(watcher);
|
|
444
|
+
} catch {
|
|
445
|
+
// fs.watch recursive may not be supported on this platform/root
|
|
446
|
+
}
|
|
391
447
|
}
|
|
392
448
|
}
|
|
393
449
|
|
|
@@ -400,10 +456,14 @@ export async function createDevServer(options) {
|
|
|
400
456
|
server,
|
|
401
457
|
port: actualPort,
|
|
402
458
|
close: () => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
459
|
+
for (const watcher of _watchers) {
|
|
460
|
+
try {
|
|
461
|
+
watcher.close();
|
|
462
|
+
} catch {
|
|
463
|
+
// ignore close errors
|
|
464
|
+
}
|
|
406
465
|
}
|
|
466
|
+
_watchers = [];
|
|
407
467
|
for (const client of hmrClients) {
|
|
408
468
|
try { client.end(); } catch { }
|
|
409
469
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenithbuild/cli",
|
|
3
|
-
"version": "0.5.0-beta.2.
|
|
3
|
+
"version": "0.5.0-beta.2.20",
|
|
4
4
|
"description": "Deterministic project orchestrator for Zenith framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@zenithbuild/compiler": "0.5.0-beta.2.
|
|
27
|
+
"@zenithbuild/compiler": "0.5.0-beta.2.20"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@jest/globals": "^30.2.0",
|