@zenithbuild/cli 0.6.13 → 0.6.17

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 (2) hide show
  1. package/dist/dev-server.js +149 -4
  2. package/package.json +2 -2
@@ -115,6 +115,112 @@ export async function createDevServer(options) {
115
115
  ...details
116
116
  });
117
117
  }
118
+ function _classifyNotFound(pathname) {
119
+ const lower = String(pathname || '').toLowerCase();
120
+ if (lower.startsWith('/__zenith_dev/'))
121
+ return 'dev_internal';
122
+ if (lower.startsWith('/__zenith/'))
123
+ return 'zenith_internal';
124
+ if (lower.startsWith('/_assets/')
125
+ || lower.startsWith('/assets/')
126
+ || lower.endsWith('.css')
127
+ || lower.endsWith('.js')
128
+ || lower.endsWith('.map')
129
+ || lower.endsWith('.json')) {
130
+ return 'asset';
131
+ }
132
+ return 'page';
133
+ }
134
+ function _routeFileHint(pathname) {
135
+ const normalized = String(pathname || '/').replace(/\/+$/, '');
136
+ if (normalized === '' || normalized === '/') {
137
+ return 'src/pages/index.zen';
138
+ }
139
+ return `src/pages${normalized}.zen`;
140
+ }
141
+ function _infer404Cause(category) {
142
+ if (category === 'dev_internal' || category === 'zenith_internal') {
143
+ if (buildStatus === 'error') {
144
+ return 'initial build failed';
145
+ }
146
+ return 'unknown Zenith dev endpoint';
147
+ }
148
+ if (category === 'asset') {
149
+ if (buildStatus === 'error') {
150
+ return 'initial build failed';
151
+ }
152
+ return 'asset not emitted by latest build';
153
+ }
154
+ return null;
155
+ }
156
+ function _looksLikeJsonRequest(req, pathname) {
157
+ const accept = String(req.headers.accept || '').toLowerCase();
158
+ const secFetchDest = String(req.headers['sec-fetch-dest'] || '').toLowerCase();
159
+ if (accept.includes('application/json') || accept.includes('application/problem+json')) {
160
+ return true;
161
+ }
162
+ if (pathname.endsWith('.json')) {
163
+ return true;
164
+ }
165
+ return secFetchDest === 'empty';
166
+ }
167
+ function _buildNotFoundPayload(pathname, category, cause) {
168
+ const payload = {
169
+ kind: 'zenith_dev_not_found',
170
+ category,
171
+ requestedPath: pathname,
172
+ buildId,
173
+ buildStatus,
174
+ cause: cause || ''
175
+ };
176
+ if (category === 'asset') {
177
+ payload.hint = buildStatus === 'error'
178
+ ? 'Dev server is running but initial build failed; fix compile errors and refresh.'
179
+ : 'Check emitted assets in dist and verify the requested path.';
180
+ if (pathname.endsWith('.css')) {
181
+ payload.expectedCssHref = currentCssHref || null;
182
+ payload.hint = buildStatus === 'error'
183
+ ? `Dev server is running but initial build failed; expected CSS at ${currentCssHref || '<none>'}.`
184
+ : `Requested CSS is missing; expected current href ${currentCssHref || '<none>'}.`;
185
+ }
186
+ return payload;
187
+ }
188
+ if (category === 'dev_internal' || category === 'zenith_internal') {
189
+ payload.hint = buildStatus === 'error'
190
+ ? 'Dev server is running but initial build failed; restart after fixing compile errors.'
191
+ : 'Check Zenith dev endpoint path and dev client version.';
192
+ payload.docsLink = '/docs/documentation/contracts/hmr-v1-contract.md';
193
+ return payload;
194
+ }
195
+ const routeFile = _routeFileHint(pathname);
196
+ payload.routeFile = routeFile;
197
+ payload.cause = `no route file found at ${routeFile}`;
198
+ payload.hint = `Create ${routeFile} or verify router manifest output.`;
199
+ return payload;
200
+ }
201
+ function _renderNotFoundHtml(payload) {
202
+ const escaped = (value) => String(value || '')
203
+ .replaceAll('&', '&amp;')
204
+ .replaceAll('<', '&lt;')
205
+ .replaceAll('>', '&gt;');
206
+ const details = [
207
+ `Requested: ${payload.requestedPath}`,
208
+ `Category: ${payload.category}`,
209
+ `Build: ${payload.buildStatus} (id=${payload.buildId})`,
210
+ `Cause: ${payload.cause}`,
211
+ payload.expectedCssHref ? `Expected CSS href: ${payload.expectedCssHref}` : '',
212
+ `Hint: ${payload.hint || 'Inspect dev server output.'}`,
213
+ payload.docsLink ? `Docs: ${payload.docsLink}` : ''
214
+ ].filter(Boolean).join('\n');
215
+ return [
216
+ '<!DOCTYPE html>',
217
+ '<html><head><meta charset="utf-8"><title>Zenith Dev 404</title></head>',
218
+ '<body style="font-family: ui-monospace, SFMono-Regular, Menlo, monospace; padding: 20px; background: #101216; color: #e6edf3;">',
219
+ '<h1 style="margin-top:0;">Zenith Dev 404</h1>',
220
+ `<pre style="white-space: pre-wrap; line-height: 1.5;">${escaped(details)}</pre>`,
221
+ '</body></html>'
222
+ ].join('');
223
+ }
118
224
  function _pickCssAsset(assets) {
119
225
  if (!Array.isArray(assets) || assets.length === 0) {
120
226
  return '';
@@ -303,6 +409,21 @@ export async function createDevServer(options) {
303
409
  return;
304
410
  }
305
411
  if (pathname === '/__zenith_dev/styles.css') {
412
+ if (buildStatus === 'error') {
413
+ const reason = typeof buildError?.message === 'string' && buildError.message.length > 0
414
+ ? buildError.message
415
+ : 'initial build failed';
416
+ const summary = reason.length > 280 ? `${reason.slice(0, 277)}...` : reason;
417
+ res.writeHead(503, {
418
+ 'Content-Type': 'text/css; charset=utf-8',
419
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
420
+ 'Pragma': 'no-cache',
421
+ 'Expires': '0',
422
+ 'X-Zenith-Dev-Error': 'build-failed'
423
+ });
424
+ res.end(`/* zenith-dev: css unavailable because build failed */\n/* cause: ${summary} */\n/* expected href: ${currentCssHref || '<none>'} */`);
425
+ return;
426
+ }
306
427
  if (typeof currentCssContent === 'string' && currentCssContent.length > 0) {
307
428
  res.writeHead(200, {
308
429
  'Content-Type': 'text/css; charset=utf-8',
@@ -504,14 +625,38 @@ export async function createDevServer(options) {
504
625
  res.writeHead(200, { 'Content-Type': 'text/html' });
505
626
  res.end(content);
506
627
  }
507
- catch {
628
+ catch (error) {
629
+ const category = _classifyNotFound(pathname);
630
+ const cause = _infer404Cause(category);
631
+ const payload = _buildNotFoundPayload(pathname, category, cause);
632
+ if (buildStatus === 'error' && typeof buildError?.message === 'string') {
633
+ payload.buildError = buildError.message.length > 600
634
+ ? `${buildError.message.slice(0, 597)}...`
635
+ : buildError.message;
636
+ }
637
+ const displayCategory = category === 'page' ? 'page' : 'asset';
638
+ logger.warn(`404 ${displayCategory}: ${pathname} (buildId=${buildId}) -> cause: ${payload.cause || cause || 'not found'}`);
508
639
  _trace404(req, url, {
509
640
  reason: 'not_found',
641
+ category,
642
+ cause: payload.cause || cause || 'not_found',
510
643
  staticRoot: staticRootFor404,
511
- resolvedPath: resolvedPathFor404
644
+ resolvedPath: resolvedPathFor404,
645
+ error: error instanceof Error ? error.message : String(error || '')
646
+ });
647
+ if (_looksLikeJsonRequest(req, pathname)) {
648
+ res.writeHead(404, {
649
+ 'Content-Type': 'application/json',
650
+ 'Cache-Control': 'no-store'
651
+ });
652
+ res.end(JSON.stringify(payload));
653
+ return;
654
+ }
655
+ res.writeHead(404, {
656
+ 'Content-Type': 'text/html; charset=utf-8',
657
+ 'Cache-Control': 'no-store'
512
658
  });
513
- res.writeHead(404, { 'Content-Type': 'text/plain' });
514
- res.end('404 Not Found');
659
+ res.end(_renderNotFoundHtml(payload));
515
660
  }
516
661
  });
517
662
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenithbuild/cli",
3
- "version": "0.6.13",
3
+ "version": "0.6.17",
4
4
  "description": "Deterministic project orchestrator for Zenith framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "prepublishOnly": "npm run build"
35
35
  },
36
36
  "dependencies": {
37
- "@zenithbuild/compiler": "0.6.13",
37
+ "@zenithbuild/compiler": "0.6.17",
38
38
  "picocolors": "^1.1.1"
39
39
  },
40
40
  "devDependencies": {