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