@moonsjs/vite 0.1.17 → 0.2.0
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/{index-v5sbd3az.js → index-0agjty3z.js} +21 -18
- package/dist/index-0agjty3z.js.map +15 -0
- package/dist/index-f8w073cm.js +30 -0
- package/dist/index-f8w073cm.js.map +10 -0
- package/dist/{index-snbxks72.js → index-tbph0859.js} +80 -80
- package/dist/{index-snbxks72.js.map → index-tbph0859.js.map} +4 -4
- package/dist/index.js +33 -33
- package/dist/index.js.map +4 -4
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/index.js +104 -93
- package/dist/server/index.js.map +10 -10
- package/dist/server/test-server.d.ts +6 -0
- package/dist/server/test-server.d.ts.map +1 -1
- package/dist/testing-preload.d.ts +2 -0
- package/dist/testing-preload.d.ts.map +1 -0
- package/dist/testing-preload.js +10 -0
- package/dist/testing-preload.js.map +10 -0
- package/dist/testing.d.ts +11 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +13 -0
- package/dist/testing.js.map +9 -0
- package/dist/transform.js +1 -1
- package/package.json +15 -5
- package/src/api.ts +14 -14
- package/src/devtools.ts +5 -5
- package/src/manifest.ts +2 -2
- package/src/plugin.ts +21 -21
- package/src/server/dev.ts +6 -1
- package/src/server/handler.ts +69 -47
- package/src/server/isr.ts +5 -5
- package/src/server/node-server.ts +6 -6
- package/src/server/node.ts +2 -2
- package/src/server/plugins.ts +1 -1
- package/src/server/prerender.ts +29 -29
- package/src/server/shell.ts +8 -8
- package/src/server/shutdown.ts +5 -5
- package/src/server/storage.ts +1 -1
- package/src/server/test-server.ts +12 -1
- package/src/server-only.ts +7 -7
- package/src/testing-preload.ts +5 -0
- package/src/testing.ts +34 -0
- package/src/transform.ts +76 -76
- package/dist/index-v5sbd3az.js.map +0 -15
package/dist/server/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
DEFAULT_SECURITY_HEADERS,
|
|
14
14
|
sendNodeResponse2,
|
|
15
15
|
installDevMiddleware2
|
|
16
|
-
} from "../index-
|
|
16
|
+
} from "../index-0agjty3z.js";
|
|
17
17
|
|
|
18
18
|
// src/server/handler.ts
|
|
19
19
|
import {
|
|
@@ -76,7 +76,7 @@ function createRequestHandler(options) {
|
|
|
76
76
|
const entry = { page: pageKey, layouts: keys.slice(0, -1) };
|
|
77
77
|
const searchParams = Object.fromEntries(new URL(request.url).searchParams);
|
|
78
78
|
const meta = await runRouteMeta(pageModule, data[pageKey], resolved.route.file);
|
|
79
|
-
const markup = renderToMarkup(entry, { page: pageModule, layouts: modules.slice(0, -1) }, resolved.params, data, searchParams);
|
|
79
|
+
const markup = renderToMarkup(entry, { page: pageModule, layouts: modules.slice(0, -1) }, resolved.params, data, searchParams, undefined, config.trailingSlash);
|
|
80
80
|
const payload = encodeSsrPayload({
|
|
81
81
|
key: pageKey,
|
|
82
82
|
params: resolved.params,
|
|
@@ -85,7 +85,7 @@ function createRequestHandler(options) {
|
|
|
85
85
|
...meta === undefined ? {} : { meta }
|
|
86
86
|
});
|
|
87
87
|
warnPayloadFindings(payload, options.payloadScan, warnedPayloadLabels);
|
|
88
|
-
return htmlResponse(await shell(markup, payload, request.url, meta), 200);
|
|
88
|
+
return htmlResponse(withTrailingSlashMeta(await shell(markup, payload, request.url, meta), config.trailingSlash), 200);
|
|
89
89
|
}
|
|
90
90
|
async function handleApiRequest(route, params, request, ctx) {
|
|
91
91
|
const load = options.loadApi?.[route.path];
|
|
@@ -93,9 +93,9 @@ function createRequestHandler(options) {
|
|
|
93
93
|
return apiErrorResponse(new MoonsError({
|
|
94
94
|
code: "E_API_HANDLER_INVALID",
|
|
95
95
|
file: route.file,
|
|
96
|
-
message: `
|
|
97
|
-
details: "
|
|
98
|
-
suggestions: ["
|
|
96
|
+
message: `API module \`${route.path}\` could not be loaded`,
|
|
97
|
+
details: "The `loadApi` map does not contain this route (build/dev out of sync).",
|
|
98
|
+
suggestions: ["Rebuild / restart the dev server"]
|
|
99
99
|
}), route);
|
|
100
100
|
}
|
|
101
101
|
let mod;
|
|
@@ -108,9 +108,9 @@ function createRequestHandler(options) {
|
|
|
108
108
|
return apiErrorResponse(new MoonsError({
|
|
109
109
|
code: "E_API_HANDLER_INVALID",
|
|
110
110
|
file: route.file,
|
|
111
|
-
message: "
|
|
112
|
-
details: `
|
|
113
|
-
suggestions: ["Export `GET`/`POST`/…
|
|
111
|
+
message: "API module does not export a handler object",
|
|
112
|
+
details: `Module \`${route.path}\` must export method handlers.`,
|
|
113
|
+
suggestions: ["Export `GET`/`POST`/… as `(args) => Response` functions"]
|
|
114
114
|
}), route);
|
|
115
115
|
}
|
|
116
116
|
const record = mod;
|
|
@@ -137,9 +137,9 @@ function createRequestHandler(options) {
|
|
|
137
137
|
return apiErrorResponse(new MoonsError({
|
|
138
138
|
code: "E_API_HANDLER_INVALID",
|
|
139
139
|
file: route.file,
|
|
140
|
-
message: `handler \`${requestMethod}\`
|
|
141
|
-
details: "
|
|
142
|
-
suggestions: ["
|
|
140
|
+
message: `handler \`${requestMethod}\` did not return a \`Response\``,
|
|
141
|
+
details: "API handlers must return `new Response(...)`.",
|
|
142
|
+
suggestions: ["Return `new Response(body, { status, headers })`"]
|
|
143
143
|
}), route);
|
|
144
144
|
}
|
|
145
145
|
if (stripBody) {
|
|
@@ -156,15 +156,15 @@ function createRequestHandler(options) {
|
|
|
156
156
|
const key = routeKeyFromFile(routesDir, notFoundRoute.file);
|
|
157
157
|
const [module] = await loadModules([key], loadRoute);
|
|
158
158
|
const payload = encodeSsrPayload({ key: NOT_FOUND_ROUTE_KEY, params: {}, data: {} });
|
|
159
|
-
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {});
|
|
160
|
-
return htmlResponse(await shell(markup, payload, request.url), 404);
|
|
159
|
+
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {}, {}, undefined, config.trailingSlash);
|
|
160
|
+
return htmlResponse(withTrailingSlashMeta(await shell(markup, payload, request.url), config.trailingSlash), 404);
|
|
161
161
|
} catch (error) {
|
|
162
|
-
console.error("[moons]
|
|
162
|
+
console.error("[moons] failed to render custom _404:", error);
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
165
|
const payload = encodeSsrPayload({ key: "", params: {}, data: {} });
|
|
166
|
-
const html = await shell("<h1>404</h1><p>
|
|
167
|
-
return htmlResponse(html, 404);
|
|
166
|
+
const html = await shell("<h1>404</h1><p>Page not found.</p>", payload, request.url);
|
|
167
|
+
return htmlResponse(withTrailingSlashMeta(html, config.trailingSlash), 404);
|
|
168
168
|
}
|
|
169
169
|
async function routeErrorResponse(error, request, resolved) {
|
|
170
170
|
console.error("[moons] route error:", error);
|
|
@@ -181,10 +181,10 @@ function createRequestHandler(options) {
|
|
|
181
181
|
data: {},
|
|
182
182
|
...serialized === undefined ? {} : { error: serialized }
|
|
183
183
|
});
|
|
184
|
-
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {}, {}, serialized === undefined ? undefined : reviveError(serialized));
|
|
185
|
-
return htmlResponse(await shell(markup, payload, request.url), 500);
|
|
184
|
+
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {}, {}, serialized === undefined ? undefined : reviveError(serialized), config.trailingSlash);
|
|
185
|
+
return htmlResponse(withTrailingSlashMeta(await shell(markup, payload, request.url), config.trailingSlash), 500);
|
|
186
186
|
} catch (renderError) {
|
|
187
|
-
console.error("[moons]
|
|
187
|
+
console.error("[moons] failed to render custom _error:", renderError);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
return internalErrorResponse(error, { log: false });
|
|
@@ -198,9 +198,9 @@ function createRequestHandler(options) {
|
|
|
198
198
|
throw new MoonsError({
|
|
199
199
|
code: "E_MIDDLEWARE_INVALID",
|
|
200
200
|
file: definition.file,
|
|
201
|
-
message: "middleware
|
|
202
|
-
details: "
|
|
203
|
-
suggestions: ["Restart dev server /
|
|
201
|
+
message: "directory middleware could not be loaded",
|
|
202
|
+
details: "The `loadRoute` map does not contain this `_middleware.ts`.",
|
|
203
|
+
suggestions: ["Restart the dev server / rebuild"]
|
|
204
204
|
});
|
|
205
205
|
}
|
|
206
206
|
const module = await load();
|
|
@@ -209,8 +209,8 @@ function createRequestHandler(options) {
|
|
|
209
209
|
throw new MoonsError({
|
|
210
210
|
code: "E_MIDDLEWARE_INVALID",
|
|
211
211
|
file: definition.file,
|
|
212
|
-
message: "`_middleware.ts`
|
|
213
|
-
details: "
|
|
212
|
+
message: "`_middleware.ts` does not default-export a function",
|
|
213
|
+
details: "Directory middleware follows the `(args) => Promise<Response>` shape.",
|
|
214
214
|
suggestions: ["Export default `async ({ req, ctx, next }) => await next()`"]
|
|
215
215
|
});
|
|
216
216
|
}
|
|
@@ -226,9 +226,9 @@ function createRequestHandler(options) {
|
|
|
226
226
|
throw new MoonsError({
|
|
227
227
|
code: "E_MIDDLEWARE_INVALID",
|
|
228
228
|
file: definition.file,
|
|
229
|
-
message: "middleware
|
|
230
|
-
details: "
|
|
231
|
-
suggestions: ["Restart dev server /
|
|
229
|
+
message: "API middleware could not be loaded",
|
|
230
|
+
details: "The `loadApiMiddleware` map does not contain this `_middleware.ts`.",
|
|
231
|
+
suggestions: ["Restart the dev server / rebuild"]
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
const module = await load();
|
|
@@ -236,8 +236,8 @@ function createRequestHandler(options) {
|
|
|
236
236
|
throw new MoonsError({
|
|
237
237
|
code: "E_MIDDLEWARE_INVALID",
|
|
238
238
|
file: definition.file,
|
|
239
|
-
message: "`_middleware.ts`
|
|
240
|
-
details: "
|
|
239
|
+
message: "`_middleware.ts` does not default-export a function",
|
|
240
|
+
details: "Directory middleware follows the `(args) => Promise<Response>` shape.",
|
|
241
241
|
suggestions: ["Export default `async ({ req, ctx, next }) => await next()`"]
|
|
242
242
|
});
|
|
243
243
|
}
|
|
@@ -268,7 +268,7 @@ function createRequestHandler(options) {
|
|
|
268
268
|
return await handleApiRequest(apiMatch.route, apiMatch.params, request, ctx);
|
|
269
269
|
}
|
|
270
270
|
const middlewares = await loadApiDirMiddlewares(apiChain);
|
|
271
|
-
return await composeMiddleware(middlewares, ctx, () => handleApiRequest(apiMatch.route, apiMatch.params, request, ctx), { code: "E_MIDDLEWARE_INVALID", label: "middleware
|
|
271
|
+
return await composeMiddleware(middlewares, ctx, () => handleApiRequest(apiMatch.route, apiMatch.params, request, ctx), { code: "E_MIDDLEWARE_INVALID", label: "API middleware" })(request);
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
274
|
const resolved = resolveRoute(url.pathname, routes);
|
|
@@ -281,7 +281,7 @@ function createRequestHandler(options) {
|
|
|
281
281
|
return await renderRoute(resolved, request, ctx);
|
|
282
282
|
}
|
|
283
283
|
const middlewares = await loadDirMiddlewares(dirChain);
|
|
284
|
-
return await composeMiddleware(middlewares, ctx, () => renderRoute(resolved, request, ctx), { code: "E_MIDDLEWARE_INVALID", label: "middleware
|
|
284
|
+
return await composeMiddleware(middlewares, ctx, () => renderRoute(resolved, request, ctx), { code: "E_MIDDLEWARE_INVALID", label: "directory middleware" })(request);
|
|
285
285
|
} catch (error) {
|
|
286
286
|
if (isRedirectSignal(error)) {
|
|
287
287
|
return new Response(null, {
|
|
@@ -386,9 +386,9 @@ async function loadModules(keys, loadRoute) {
|
|
|
386
386
|
if (load === undefined) {
|
|
387
387
|
throw new MoonsError({
|
|
388
388
|
code: "E_INTERNAL",
|
|
389
|
-
message: `
|
|
390
|
-
details: "
|
|
391
|
-
suggestions: ["
|
|
389
|
+
message: `route module \`${key}\` is not registered`,
|
|
390
|
+
details: "The route registry is out of sync with the scan result.",
|
|
391
|
+
suggestions: ["Run `bun run build` again, or restart the dev server"]
|
|
392
392
|
});
|
|
393
393
|
}
|
|
394
394
|
return await load();
|
|
@@ -403,11 +403,11 @@ async function runRouteMeta(pageModule, pageData, file) {
|
|
|
403
403
|
throw new MoonsError({
|
|
404
404
|
code: "E_INTERNAL",
|
|
405
405
|
file,
|
|
406
|
-
message: "export `meta`
|
|
407
|
-
details: "
|
|
406
|
+
message: "export `meta` must be a function `({ data }) => RouteMeta`",
|
|
407
|
+
details: "The `meta` shape follows `RouteMeta`.",
|
|
408
408
|
suggestions: [
|
|
409
|
-
'
|
|
410
|
-
"
|
|
409
|
+
'Change it to `export const meta = ({ data }) => ({ title: "..." })`',
|
|
410
|
+
"Or remove the `meta` export"
|
|
411
411
|
]
|
|
412
412
|
});
|
|
413
413
|
}
|
|
@@ -416,9 +416,9 @@ async function runRouteMeta(pageModule, pageData, file) {
|
|
|
416
416
|
throw new MoonsError({
|
|
417
417
|
code: "E_INTERNAL",
|
|
418
418
|
file,
|
|
419
|
-
message: "`meta()`
|
|
420
|
-
details: "
|
|
421
|
-
suggestions: ['
|
|
419
|
+
message: "`meta()` must return a RouteMeta object",
|
|
420
|
+
details: "The returned value is used for <title>/description/og.",
|
|
421
|
+
suggestions: ['Return an object, e.g. `({ title: "Home" })`']
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
return result;
|
|
@@ -427,13 +427,13 @@ function apiErrorResponse(error, route) {
|
|
|
427
427
|
const isProduction = process.env["NODE_ENV"] === "production";
|
|
428
428
|
if (error instanceof MoonsError) {
|
|
429
429
|
console.error(`[moons] API ${route.path}: ${error.format()}`);
|
|
430
|
-
return new Response(isProduction ? "
|
|
430
|
+
return new Response(isProduction ? "Internal server error." : error.format(), {
|
|
431
431
|
status: 500,
|
|
432
432
|
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
433
433
|
});
|
|
434
434
|
}
|
|
435
435
|
console.error(`[moons] API ${route.path}:`, error);
|
|
436
|
-
return new Response(isProduction ? "
|
|
436
|
+
return new Response(isProduction ? "Internal server error." : String(error), {
|
|
437
437
|
status: 500,
|
|
438
438
|
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
439
439
|
});
|
|
@@ -442,8 +442,15 @@ function internalErrorResponse(error, options = {}) {
|
|
|
442
442
|
if (options.log !== false) {
|
|
443
443
|
console.error("[moons] request error:", error);
|
|
444
444
|
}
|
|
445
|
-
const detail = process.env["NODE_ENV"] !== "production" && error instanceof MoonsError ? `<pre>${escapeHtml(error.format())}</pre>` : "<p>
|
|
446
|
-
return htmlResponse(`<!doctype html><html lang="
|
|
445
|
+
const detail = process.env["NODE_ENV"] !== "production" && error instanceof MoonsError ? `<pre>${escapeHtml(error.format())}</pre>` : "<p>Internal server error.</p>";
|
|
446
|
+
return htmlResponse(`<!doctype html><html lang="en"><head><meta charset="utf-8"><title>500</title></head><body><h1>500</h1>${detail}</body></html>`, 500);
|
|
447
|
+
}
|
|
448
|
+
function withTrailingSlashMeta(html, policy) {
|
|
449
|
+
if (policy === undefined) {
|
|
450
|
+
return html;
|
|
451
|
+
}
|
|
452
|
+
return html.replace("</head>", ` <meta name="moons:trailing-slash" content="${policy}" />
|
|
453
|
+
</head>`);
|
|
447
454
|
}
|
|
448
455
|
function htmlResponse(html, status) {
|
|
449
456
|
return new Response(html, {
|
|
@@ -477,7 +484,7 @@ function warnPayloadFindings(payload, scan, warned) {
|
|
|
477
484
|
continue;
|
|
478
485
|
}
|
|
479
486
|
warned.add(label);
|
|
480
|
-
console.warn(`[moons]
|
|
487
|
+
console.warn(`[moons] dev warning: SSR payload contains ${label} — do not return secrets from loaders.`);
|
|
481
488
|
}
|
|
482
489
|
}
|
|
483
490
|
// src/server/node-server.ts
|
|
@@ -517,7 +524,7 @@ async function runRevalidate(options) {
|
|
|
517
524
|
headers: { "x-moons-prerender": "1" }
|
|
518
525
|
}));
|
|
519
526
|
if (response.status !== 200) {
|
|
520
|
-
log(`[moons] isr:
|
|
527
|
+
log(`[moons] isr: skipped ${options.pathname} (status ${response.status})`);
|
|
521
528
|
return;
|
|
522
529
|
}
|
|
523
530
|
const html = await response.text();
|
|
@@ -527,7 +534,7 @@ async function runRevalidate(options) {
|
|
|
527
534
|
});
|
|
528
535
|
if (findings.length > 0) {
|
|
529
536
|
const labels = findings.map((finding) => finding.kind === "canary" ? "canary" : `env(${finding.label})`).join(", ");
|
|
530
|
-
log(`[moons] isr:
|
|
537
|
+
log(`[moons] isr: NOT writing ${options.pathname} — contains ${labels}`);
|
|
531
538
|
return;
|
|
532
539
|
}
|
|
533
540
|
if (options.storage !== undefined) {
|
|
@@ -535,14 +542,14 @@ async function runRevalidate(options) {
|
|
|
535
542
|
return;
|
|
536
543
|
}
|
|
537
544
|
if (options.file === undefined) {
|
|
538
|
-
log(`[moons] isr:
|
|
545
|
+
log(`[moons] isr: skipped ${options.pathname} — no destination file/storage`);
|
|
539
546
|
return;
|
|
540
547
|
}
|
|
541
548
|
const tmp = `${options.file}.tmp-${process.pid}`;
|
|
542
549
|
writeFileSync(tmp, html);
|
|
543
550
|
renameSync(tmp, options.file);
|
|
544
551
|
} catch (error) {
|
|
545
|
-
log(`[moons] isr:
|
|
552
|
+
log(`[moons] isr: failed to regenerate ${options.pathname}: ${String(error)}`);
|
|
546
553
|
}
|
|
547
554
|
}
|
|
548
555
|
|
|
@@ -562,7 +569,7 @@ function createFileStorage(clientDir) {
|
|
|
562
569
|
async write(pathname, body) {
|
|
563
570
|
const target = staticFileTarget(clientDir, pathname);
|
|
564
571
|
if (target === null) {
|
|
565
|
-
throw new Error(`[moons] isr: path
|
|
572
|
+
throw new Error(`[moons] isr: invalid path to write: ${pathname}`);
|
|
566
573
|
}
|
|
567
574
|
mkdirSync(dirname(target), { recursive: true });
|
|
568
575
|
const tmp = `${target}.tmp-${process.pid}`;
|
|
@@ -645,9 +652,9 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
645
652
|
return;
|
|
646
653
|
}
|
|
647
654
|
shuttingDown = true;
|
|
648
|
-
log(`[moons]
|
|
655
|
+
log(`[moons] received ${signal} — no longer accepting connections, draining...`);
|
|
649
656
|
const timer = setTimeout(() => {
|
|
650
|
-
log(`[moons] drain
|
|
657
|
+
log(`[moons] drain exceeded ${timeoutMs}ms — forcing exit`);
|
|
651
658
|
const closeAll = server.closeAllConnections;
|
|
652
659
|
try {
|
|
653
660
|
closeAll?.call(server);
|
|
@@ -661,7 +668,7 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
661
668
|
server.close(async (error) => {
|
|
662
669
|
if (error !== undefined) {
|
|
663
670
|
clearTimeout(timer);
|
|
664
|
-
log(`[moons]
|
|
671
|
+
log(`[moons] server close failed: ${error.message}`);
|
|
665
672
|
exit(1);
|
|
666
673
|
return;
|
|
667
674
|
}
|
|
@@ -670,11 +677,11 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
670
677
|
try {
|
|
671
678
|
await options.onShutdown();
|
|
672
679
|
} catch (hookError) {
|
|
673
|
-
log(`[moons] onShutdown
|
|
680
|
+
log(`[moons] onShutdown failed: ${String(hookError)}`);
|
|
674
681
|
}
|
|
675
682
|
}
|
|
676
683
|
clearTimeout(timer);
|
|
677
|
-
log("[moons] shutdown
|
|
684
|
+
log("[moons] shutdown complete (full drain)");
|
|
678
685
|
exit(0);
|
|
679
686
|
});
|
|
680
687
|
};
|
|
@@ -921,11 +928,11 @@ function rpcBodyTooLargeText(limit, size) {
|
|
|
921
928
|
const megabytes = (bytes) => (bytes / 1e6).toFixed(1);
|
|
922
929
|
return new MoonsError2({
|
|
923
930
|
code: "E_RPC_BODY_TOO_LARGE",
|
|
924
|
-
message: size === null ? `body
|
|
925
|
-
details: "Body
|
|
931
|
+
message: size === null ? `body exceeds the ${megabytes(limit)} MB limit` : `body ${megabytes(size)} MB exceeds the ${megabytes(limit)} MB limit`,
|
|
932
|
+
details: "Body exceeds `security.bodyLimitBytes`; reading stops at the transport before the body is fully buffered.",
|
|
926
933
|
suggestions: [
|
|
927
|
-
"
|
|
928
|
-
"
|
|
934
|
+
"Reduce the payload size, or",
|
|
935
|
+
"Increase security.bodyLimitBytes if you really need to"
|
|
929
936
|
]
|
|
930
937
|
}).format();
|
|
931
938
|
}
|
|
@@ -1030,7 +1037,7 @@ function scheduleRevalidation(input) {
|
|
|
1030
1037
|
if (logger !== null) {
|
|
1031
1038
|
logger({ msg: "isr:revalidate-failed", level: "warn", path: normalized, error: message });
|
|
1032
1039
|
} else {
|
|
1033
|
-
console.error(`[moons] isr
|
|
1040
|
+
console.error(`[moons] isr regeneration failed (${normalized}): ${message}`);
|
|
1034
1041
|
}
|
|
1035
1042
|
});
|
|
1036
1043
|
}
|
|
@@ -1042,6 +1049,10 @@ function createTestServer(options, baseUrl = "http://localhost") {
|
|
|
1042
1049
|
config: options.config,
|
|
1043
1050
|
routesDir: options.routesDir,
|
|
1044
1051
|
loadRoute: options.loadRoute,
|
|
1052
|
+
...options.apiRoutes === undefined ? {} : { apiRoutes: options.apiRoutes },
|
|
1053
|
+
...options.loadApi === undefined ? {} : { loadApi: options.loadApi },
|
|
1054
|
+
...options.apiMiddlewares === undefined ? {} : { apiMiddlewares: options.apiMiddlewares },
|
|
1055
|
+
...options.loadApiMiddleware === undefined ? {} : { loadApiMiddleware: options.loadApiMiddleware },
|
|
1045
1056
|
shell
|
|
1046
1057
|
});
|
|
1047
1058
|
return {
|
|
@@ -1091,7 +1102,7 @@ function createPluginRuntime(options) {
|
|
|
1091
1102
|
try {
|
|
1092
1103
|
await plugin.onShutdown?.(app);
|
|
1093
1104
|
} catch (error) {
|
|
1094
|
-
log(`[moons] plugin \`${plugin.name}\`
|
|
1105
|
+
log(`[moons] plugin \`${plugin.name}\` failed during onShutdown: ${String(error)}`);
|
|
1095
1106
|
}
|
|
1096
1107
|
}
|
|
1097
1108
|
},
|
|
@@ -1133,18 +1144,18 @@ async function runPrerender(options) {
|
|
|
1133
1144
|
throw new MoonsError3({
|
|
1134
1145
|
code: "E_ISR_INVALID",
|
|
1135
1146
|
file: route.file,
|
|
1136
|
-
message: "`revalidate`
|
|
1137
|
-
details: "ISR
|
|
1147
|
+
message: "`revalidate` is invalid or used without `prerender = true`",
|
|
1148
|
+
details: "File-based ISR: `revalidate` (seconds, number >= 0) + `prerender`.",
|
|
1138
1149
|
suggestions: [
|
|
1139
|
-
"
|
|
1140
|
-
"
|
|
1150
|
+
"Use a number of seconds, e.g. `export const revalidate = 60`",
|
|
1151
|
+
"Add `export const prerender = true` to the same page"
|
|
1141
1152
|
]
|
|
1142
1153
|
});
|
|
1143
1154
|
}
|
|
1144
1155
|
}
|
|
1145
1156
|
if (prerender !== true) {
|
|
1146
1157
|
if (prerender !== undefined && prerender !== false) {
|
|
1147
|
-
throw prerenderError(route, "`prerender`
|
|
1158
|
+
throw prerenderError(route, "`prerender` must be a boolean (`true`/`false`)");
|
|
1148
1159
|
}
|
|
1149
1160
|
continue;
|
|
1150
1161
|
}
|
|
@@ -1155,15 +1166,15 @@ async function runPrerender(options) {
|
|
|
1155
1166
|
headers: { "x-moons-prerender": "1" }
|
|
1156
1167
|
}));
|
|
1157
1168
|
if (response.status === 404) {
|
|
1158
|
-
log(`[moons] prerender:
|
|
1169
|
+
log(`[moons] prerender: skipped ${pathname} (notFound)`);
|
|
1159
1170
|
skipped += 1;
|
|
1160
1171
|
continue;
|
|
1161
1172
|
}
|
|
1162
1173
|
if (response.status >= 300 && response.status < 400) {
|
|
1163
|
-
throw prerenderError(route, `\`redirect()\`
|
|
1174
|
+
throw prerenderError(route, `\`redirect()\` is not supported during prerender (${pathname})`);
|
|
1164
1175
|
}
|
|
1165
1176
|
if (response.status !== 200) {
|
|
1166
|
-
throw prerenderError(route, `status ${response.status}
|
|
1177
|
+
throw prerenderError(route, `status ${response.status} while rendering ${pathname}`);
|
|
1167
1178
|
}
|
|
1168
1179
|
const html = await response.text();
|
|
1169
1180
|
assertNoLeak(html, pathname, route.file, options, env);
|
|
@@ -1184,7 +1195,7 @@ async function runPrerender(options) {
|
|
|
1184
1195
|
`);
|
|
1185
1196
|
const emitted = emitStaticFiles(options.clientDir, options.staticConfig, entries);
|
|
1186
1197
|
const extras = [...wrote404 ? [NOT_FOUND_FILE] : [], ...emitted];
|
|
1187
|
-
log(`[moons] prerender
|
|
1198
|
+
log(`[moons] prerender done: ${written} file${extras.length > 0 ? ` + ${extras.join(" + ")}` : ""} (${skipped} skipped)`);
|
|
1188
1199
|
return { written, skipped };
|
|
1189
1200
|
}
|
|
1190
1201
|
function emitStaticFiles(clientDir, staticConfig, entries) {
|
|
@@ -1249,7 +1260,7 @@ async function renderStaticNotFound(options, env, log) {
|
|
|
1249
1260
|
headers: { "x-moons-prerender": "1" }
|
|
1250
1261
|
}));
|
|
1251
1262
|
if (response.status !== 404) {
|
|
1252
|
-
log(`[moons] prerender: ${NOT_FOUND_FILE}
|
|
1263
|
+
log(`[moons] prerender: ${NOT_FOUND_FILE} skipped (sentinel \`${NOT_FOUND_SENTINEL}\` → ${response.status})`);
|
|
1253
1264
|
return null;
|
|
1254
1265
|
}
|
|
1255
1266
|
const html = await response.text();
|
|
@@ -1268,11 +1279,11 @@ function assertNoLeak(html, pathname, file, options, env) {
|
|
|
1268
1279
|
throw new MoonsError3({
|
|
1269
1280
|
code: "E_PRERENDER_LEAK",
|
|
1270
1281
|
file,
|
|
1271
|
-
message: `
|
|
1272
|
-
details: "HTML
|
|
1282
|
+
message: `prerender output \`${pathname}\` contains ${labels}`,
|
|
1283
|
+
details: "Static HTML is permanent — a prerendered secret cannot be retracted.",
|
|
1273
1284
|
suggestions: [
|
|
1274
|
-
"
|
|
1275
|
-
"
|
|
1285
|
+
"Do not return secret env values from a loader that runs at prerender time",
|
|
1286
|
+
"Turn off `prerender` for this route or move the data to a request-time serverFn"
|
|
1276
1287
|
]
|
|
1277
1288
|
});
|
|
1278
1289
|
}
|
|
@@ -1290,35 +1301,35 @@ async function resolvePathnames(route, module) {
|
|
|
1290
1301
|
throw new MoonsError3({
|
|
1291
1302
|
code: "E_PRERENDER_NO_PATHS",
|
|
1292
1303
|
file: route.file,
|
|
1293
|
-
message: "route
|
|
1294
|
-
details: "
|
|
1304
|
+
message: "dynamic route with `prerender = true` without `export const paths`",
|
|
1305
|
+
details: "Prerendering needs a param list to render each path.",
|
|
1295
1306
|
suggestions: [
|
|
1296
|
-
'
|
|
1297
|
-
"
|
|
1307
|
+
'Add `export const paths = () => [{ id: "…" }]`',
|
|
1308
|
+
"Or remove `prerender` so the route renders at request time (SSR)"
|
|
1298
1309
|
]
|
|
1299
1310
|
});
|
|
1300
1311
|
}
|
|
1301
1312
|
const raw = await paths();
|
|
1302
1313
|
if (!Array.isArray(raw)) {
|
|
1303
|
-
throw prerenderError(route, "`paths()`
|
|
1314
|
+
throw prerenderError(route, "`paths()` must return an array");
|
|
1304
1315
|
}
|
|
1305
1316
|
const expected = new Set(route.params);
|
|
1306
1317
|
const result = [];
|
|
1307
1318
|
for (const item of raw) {
|
|
1308
1319
|
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
1309
|
-
throw prerenderError(route, "
|
|
1320
|
+
throw prerenderError(route, "`paths()` items must be objects with string params");
|
|
1310
1321
|
}
|
|
1311
1322
|
const record = item;
|
|
1312
1323
|
const keys = Object.keys(record);
|
|
1313
1324
|
for (const name of expected) {
|
|
1314
1325
|
const value = record[name];
|
|
1315
1326
|
if (typeof value !== "string" || value.length === 0) {
|
|
1316
|
-
throw prerenderError(route, `param \`${name}\`
|
|
1327
|
+
throw prerenderError(route, `param \`${name}\` must be a non-empty string`);
|
|
1317
1328
|
}
|
|
1318
1329
|
}
|
|
1319
1330
|
for (const key of keys) {
|
|
1320
1331
|
if (!expected.has(key)) {
|
|
1321
|
-
throw prerenderError(route, `param \`${key}\`
|
|
1332
|
+
throw prerenderError(route, `param \`${key}\` is not in the route pattern`);
|
|
1322
1333
|
}
|
|
1323
1334
|
}
|
|
1324
1335
|
result.push(buildPathname(route, record));
|
|
@@ -1351,7 +1362,7 @@ function buildPathname(route, params) {
|
|
|
1351
1362
|
}
|
|
1352
1363
|
function encodeSegment(route, value) {
|
|
1353
1364
|
if (value.length === 0 || /^\.+$/.test(value)) {
|
|
1354
|
-
throw prerenderError(route, `
|
|
1365
|
+
throw prerenderError(route, `param value \`${value}\` is not valid for a file path`);
|
|
1355
1366
|
}
|
|
1356
1367
|
return encodeURIComponent(value);
|
|
1357
1368
|
}
|
|
@@ -1362,9 +1373,9 @@ function outputFile(clientDir, pathname) {
|
|
|
1362
1373
|
if (file !== base && !file.startsWith(base + sep2)) {
|
|
1363
1374
|
throw new MoonsError3({
|
|
1364
1375
|
code: "E_PRERENDER_INVALID",
|
|
1365
|
-
message: `
|
|
1366
|
-
details: "
|
|
1367
|
-
suggestions: ["
|
|
1376
|
+
message: `prerender path escapes the client directory (\`${pathname}\`)`,
|
|
1377
|
+
details: "The file name must stay inside `dist/client`.",
|
|
1378
|
+
suggestions: ["Check the `paths` param values — do not use `..`"]
|
|
1368
1379
|
});
|
|
1369
1380
|
}
|
|
1370
1381
|
return file;
|
|
@@ -1374,10 +1385,10 @@ function prerenderError(route, message) {
|
|
|
1374
1385
|
code: "E_PRERENDER_INVALID",
|
|
1375
1386
|
file: route.file,
|
|
1376
1387
|
message,
|
|
1377
|
-
details: "
|
|
1388
|
+
details: "The prerender contract is enforced.",
|
|
1378
1389
|
suggestions: [
|
|
1379
|
-
"
|
|
1380
|
-
"
|
|
1390
|
+
"Fix the `paths`/params shape so it matches the route pattern",
|
|
1391
|
+
"Do not use `redirect()` on a prerendered route"
|
|
1381
1392
|
]
|
|
1382
1393
|
});
|
|
1383
1394
|
}
|
|
@@ -1401,4 +1412,4 @@ export {
|
|
|
1401
1412
|
toWebRequest2 as toWebRequest
|
|
1402
1413
|
};
|
|
1403
1414
|
|
|
1404
|
-
//# debugId=
|
|
1415
|
+
//# debugId=ACB67C3B410A13D264756E2164756E21
|