@moonsjs/vite 0.1.16 → 0.1.18
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-1ksjfvnz.js} +18 -18
- package/dist/index-1ksjfvnz.js.map +15 -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 +34 -34
- package/dist/index.js.map +4 -4
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/index.js +111 -88
- package/dist/server/index.js.map +9 -9
- package/dist/server/node-server.d.ts +2 -0
- package/dist/server/node-server.d.ts.map +1 -1
- package/dist/transform.js +1 -1
- package/package.json +1 -1
- package/src/api.ts +14 -14
- package/src/devtools.ts +5 -5
- package/src/manifest.ts +2 -2
- package/src/plugin.ts +22 -22
- package/src/server/dev.ts +1 -1
- package/src/server/handler.ts +53 -39
- package/src/server/isr.ts +5 -5
- package/src/server/node-server.ts +27 -7
- 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-only.ts +7 -7
- 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-1ksjfvnz.js";
|
|
17
17
|
|
|
18
18
|
// src/server/handler.ts
|
|
19
19
|
import {
|
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
isRedirectSignal,
|
|
27
27
|
resolveRoute,
|
|
28
28
|
routeKeyFromFile,
|
|
29
|
-
runLoaders
|
|
29
|
+
runLoaders,
|
|
30
|
+
trailingSlashRedirect
|
|
30
31
|
} from "@moonsjs/core";
|
|
31
32
|
import { encodeSsrPayload, reviveError, serializeError } from "@moonsjs/core/serialize";
|
|
32
33
|
import { API_METHODS, resolveApiRoute } from "@moonsjs/core";
|
|
@@ -92,9 +93,9 @@ function createRequestHandler(options) {
|
|
|
92
93
|
return apiErrorResponse(new MoonsError({
|
|
93
94
|
code: "E_API_HANDLER_INVALID",
|
|
94
95
|
file: route.file,
|
|
95
|
-
message: `
|
|
96
|
-
details: "
|
|
97
|
-
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"]
|
|
98
99
|
}), route);
|
|
99
100
|
}
|
|
100
101
|
let mod;
|
|
@@ -107,9 +108,9 @@ function createRequestHandler(options) {
|
|
|
107
108
|
return apiErrorResponse(new MoonsError({
|
|
108
109
|
code: "E_API_HANDLER_INVALID",
|
|
109
110
|
file: route.file,
|
|
110
|
-
message: "
|
|
111
|
-
details: `
|
|
112
|
-
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"]
|
|
113
114
|
}), route);
|
|
114
115
|
}
|
|
115
116
|
const record = mod;
|
|
@@ -136,9 +137,9 @@ function createRequestHandler(options) {
|
|
|
136
137
|
return apiErrorResponse(new MoonsError({
|
|
137
138
|
code: "E_API_HANDLER_INVALID",
|
|
138
139
|
file: route.file,
|
|
139
|
-
message: `handler \`${requestMethod}\`
|
|
140
|
-
details: "
|
|
141
|
-
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 })`"]
|
|
142
143
|
}), route);
|
|
143
144
|
}
|
|
144
145
|
if (stripBody) {
|
|
@@ -158,11 +159,11 @@ function createRequestHandler(options) {
|
|
|
158
159
|
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {});
|
|
159
160
|
return htmlResponse(await shell(markup, payload, request.url), 404);
|
|
160
161
|
} catch (error) {
|
|
161
|
-
console.error("[moons]
|
|
162
|
+
console.error("[moons] failed to render custom _404:", error);
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
const payload = encodeSsrPayload({ key: "", params: {}, data: {} });
|
|
165
|
-
const html = await shell("<h1>404</h1><p>
|
|
166
|
+
const html = await shell("<h1>404</h1><p>Page not found.</p>", payload, request.url);
|
|
166
167
|
return htmlResponse(html, 404);
|
|
167
168
|
}
|
|
168
169
|
async function routeErrorResponse(error, request, resolved) {
|
|
@@ -183,7 +184,7 @@ function createRequestHandler(options) {
|
|
|
183
184
|
const markup = renderToMarkup({ page: key, layouts: [] }, { page: module, layouts: [] }, {}, {}, {}, serialized === undefined ? undefined : reviveError(serialized));
|
|
184
185
|
return htmlResponse(await shell(markup, payload, request.url), 500);
|
|
185
186
|
} catch (renderError) {
|
|
186
|
-
console.error("[moons]
|
|
187
|
+
console.error("[moons] failed to render custom _error:", renderError);
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
return internalErrorResponse(error, { log: false });
|
|
@@ -197,9 +198,9 @@ function createRequestHandler(options) {
|
|
|
197
198
|
throw new MoonsError({
|
|
198
199
|
code: "E_MIDDLEWARE_INVALID",
|
|
199
200
|
file: definition.file,
|
|
200
|
-
message: "middleware
|
|
201
|
-
details: "
|
|
202
|
-
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"]
|
|
203
204
|
});
|
|
204
205
|
}
|
|
205
206
|
const module = await load();
|
|
@@ -208,8 +209,8 @@ function createRequestHandler(options) {
|
|
|
208
209
|
throw new MoonsError({
|
|
209
210
|
code: "E_MIDDLEWARE_INVALID",
|
|
210
211
|
file: definition.file,
|
|
211
|
-
message: "`_middleware.ts`
|
|
212
|
-
details: "
|
|
212
|
+
message: "`_middleware.ts` does not default-export a function",
|
|
213
|
+
details: "Directory middleware follows the `(args) => Promise<Response>` shape.",
|
|
213
214
|
suggestions: ["Export default `async ({ req, ctx, next }) => await next()`"]
|
|
214
215
|
});
|
|
215
216
|
}
|
|
@@ -225,9 +226,9 @@ function createRequestHandler(options) {
|
|
|
225
226
|
throw new MoonsError({
|
|
226
227
|
code: "E_MIDDLEWARE_INVALID",
|
|
227
228
|
file: definition.file,
|
|
228
|
-
message: "middleware
|
|
229
|
-
details: "
|
|
230
|
-
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"]
|
|
231
232
|
});
|
|
232
233
|
}
|
|
233
234
|
const module = await load();
|
|
@@ -235,8 +236,8 @@ function createRequestHandler(options) {
|
|
|
235
236
|
throw new MoonsError({
|
|
236
237
|
code: "E_MIDDLEWARE_INVALID",
|
|
237
238
|
file: definition.file,
|
|
238
|
-
message: "`_middleware.ts`
|
|
239
|
-
details: "
|
|
239
|
+
message: "`_middleware.ts` does not default-export a function",
|
|
240
|
+
details: "Directory middleware follows the `(args) => Promise<Response>` shape.",
|
|
240
241
|
suggestions: ["Export default `async ({ req, ctx, next }) => await next()`"]
|
|
241
242
|
});
|
|
242
243
|
}
|
|
@@ -267,7 +268,7 @@ function createRequestHandler(options) {
|
|
|
267
268
|
return await handleApiRequest(apiMatch.route, apiMatch.params, request, ctx);
|
|
268
269
|
}
|
|
269
270
|
const middlewares = await loadApiDirMiddlewares(apiChain);
|
|
270
|
-
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);
|
|
271
272
|
}
|
|
272
273
|
}
|
|
273
274
|
const resolved = resolveRoute(url.pathname, routes);
|
|
@@ -280,7 +281,7 @@ function createRequestHandler(options) {
|
|
|
280
281
|
return await renderRoute(resolved, request, ctx);
|
|
281
282
|
}
|
|
282
283
|
const middlewares = await loadDirMiddlewares(dirChain);
|
|
283
|
-
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);
|
|
284
285
|
} catch (error) {
|
|
285
286
|
if (isRedirectSignal(error)) {
|
|
286
287
|
return new Response(null, {
|
|
@@ -323,6 +324,14 @@ function createRequestHandler(options) {
|
|
|
323
324
|
return response;
|
|
324
325
|
};
|
|
325
326
|
try {
|
|
327
|
+
if (!isPrerender && config.trailingSlash !== undefined) {
|
|
328
|
+
const policyUrl = new URL(request.url);
|
|
329
|
+
const canonical = trailingSlashRedirect(policyUrl.pathname, config.trailingSlash);
|
|
330
|
+
if (canonical !== null) {
|
|
331
|
+
policyUrl.pathname = canonical;
|
|
332
|
+
return applyHeaders(new Response(null, { status: 308, headers: { location: policyUrl.toString() } }));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
326
335
|
if (!isPrerender && redirects.length > 0) {
|
|
327
336
|
const url = new URL(request.url);
|
|
328
337
|
const path = url.pathname.replace(/\/$/, "");
|
|
@@ -377,9 +386,9 @@ async function loadModules(keys, loadRoute) {
|
|
|
377
386
|
if (load === undefined) {
|
|
378
387
|
throw new MoonsError({
|
|
379
388
|
code: "E_INTERNAL",
|
|
380
|
-
message: `
|
|
381
|
-
details: "
|
|
382
|
-
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"]
|
|
383
392
|
});
|
|
384
393
|
}
|
|
385
394
|
return await load();
|
|
@@ -394,11 +403,11 @@ async function runRouteMeta(pageModule, pageData, file) {
|
|
|
394
403
|
throw new MoonsError({
|
|
395
404
|
code: "E_INTERNAL",
|
|
396
405
|
file,
|
|
397
|
-
message: "export `meta`
|
|
398
|
-
details: "
|
|
406
|
+
message: "export `meta` must be a function `({ data }) => RouteMeta`",
|
|
407
|
+
details: "The `meta` shape follows `RouteMeta`.",
|
|
399
408
|
suggestions: [
|
|
400
|
-
'
|
|
401
|
-
"
|
|
409
|
+
'Change it to `export const meta = ({ data }) => ({ title: "..." })`',
|
|
410
|
+
"Or remove the `meta` export"
|
|
402
411
|
]
|
|
403
412
|
});
|
|
404
413
|
}
|
|
@@ -407,9 +416,9 @@ async function runRouteMeta(pageModule, pageData, file) {
|
|
|
407
416
|
throw new MoonsError({
|
|
408
417
|
code: "E_INTERNAL",
|
|
409
418
|
file,
|
|
410
|
-
message: "`meta()`
|
|
411
|
-
details: "
|
|
412
|
-
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" })`']
|
|
413
422
|
});
|
|
414
423
|
}
|
|
415
424
|
return result;
|
|
@@ -418,13 +427,13 @@ function apiErrorResponse(error, route) {
|
|
|
418
427
|
const isProduction = process.env["NODE_ENV"] === "production";
|
|
419
428
|
if (error instanceof MoonsError) {
|
|
420
429
|
console.error(`[moons] API ${route.path}: ${error.format()}`);
|
|
421
|
-
return new Response(isProduction ? "
|
|
430
|
+
return new Response(isProduction ? "Internal server error." : error.format(), {
|
|
422
431
|
status: 500,
|
|
423
432
|
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
424
433
|
});
|
|
425
434
|
}
|
|
426
435
|
console.error(`[moons] API ${route.path}:`, error);
|
|
427
|
-
return new Response(isProduction ? "
|
|
436
|
+
return new Response(isProduction ? "Internal server error." : String(error), {
|
|
428
437
|
status: 500,
|
|
429
438
|
headers: { "content-type": "text/plain; charset=utf-8" }
|
|
430
439
|
});
|
|
@@ -433,8 +442,8 @@ function internalErrorResponse(error, options = {}) {
|
|
|
433
442
|
if (options.log !== false) {
|
|
434
443
|
console.error("[moons] request error:", error);
|
|
435
444
|
}
|
|
436
|
-
const detail = process.env["NODE_ENV"] !== "production" && error instanceof MoonsError ? `<pre>${escapeHtml(error.format())}</pre>` : "<p>
|
|
437
|
-
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);
|
|
438
447
|
}
|
|
439
448
|
function htmlResponse(html, status) {
|
|
440
449
|
return new Response(html, {
|
|
@@ -468,7 +477,7 @@ function warnPayloadFindings(payload, scan, warned) {
|
|
|
468
477
|
continue;
|
|
469
478
|
}
|
|
470
479
|
warned.add(label);
|
|
471
|
-
console.warn(`[moons]
|
|
480
|
+
console.warn(`[moons] dev warning: SSR payload contains ${label} — do not return secrets from loaders.`);
|
|
472
481
|
}
|
|
473
482
|
}
|
|
474
483
|
// src/server/node-server.ts
|
|
@@ -476,7 +485,11 @@ import { existsSync, readFileSync as readFileSync2, statSync as statSync2 } from
|
|
|
476
485
|
import { gzipSync } from "node:zlib";
|
|
477
486
|
import { createServer } from "node:http";
|
|
478
487
|
import { extname as extname2, join, resolve, sep } from "node:path";
|
|
479
|
-
import {
|
|
488
|
+
import {
|
|
489
|
+
DEFAULT_BODY_LIMIT_BYTES,
|
|
490
|
+
MoonsError as MoonsError2,
|
|
491
|
+
trailingSlashRedirect as trailingSlashRedirect2
|
|
492
|
+
} from "@moonsjs/core";
|
|
480
493
|
|
|
481
494
|
// src/server/isr.ts
|
|
482
495
|
import { renameSync, writeFileSync } from "node:fs";
|
|
@@ -504,7 +517,7 @@ async function runRevalidate(options) {
|
|
|
504
517
|
headers: { "x-moons-prerender": "1" }
|
|
505
518
|
}));
|
|
506
519
|
if (response.status !== 200) {
|
|
507
|
-
log(`[moons] isr:
|
|
520
|
+
log(`[moons] isr: skipped ${options.pathname} (status ${response.status})`);
|
|
508
521
|
return;
|
|
509
522
|
}
|
|
510
523
|
const html = await response.text();
|
|
@@ -514,7 +527,7 @@ async function runRevalidate(options) {
|
|
|
514
527
|
});
|
|
515
528
|
if (findings.length > 0) {
|
|
516
529
|
const labels = findings.map((finding) => finding.kind === "canary" ? "canary" : `env(${finding.label})`).join(", ");
|
|
517
|
-
log(`[moons] isr:
|
|
530
|
+
log(`[moons] isr: NOT writing ${options.pathname} — contains ${labels}`);
|
|
518
531
|
return;
|
|
519
532
|
}
|
|
520
533
|
if (options.storage !== undefined) {
|
|
@@ -522,14 +535,14 @@ async function runRevalidate(options) {
|
|
|
522
535
|
return;
|
|
523
536
|
}
|
|
524
537
|
if (options.file === undefined) {
|
|
525
|
-
log(`[moons] isr:
|
|
538
|
+
log(`[moons] isr: skipped ${options.pathname} — no destination file/storage`);
|
|
526
539
|
return;
|
|
527
540
|
}
|
|
528
541
|
const tmp = `${options.file}.tmp-${process.pid}`;
|
|
529
542
|
writeFileSync(tmp, html);
|
|
530
543
|
renameSync(tmp, options.file);
|
|
531
544
|
} catch (error) {
|
|
532
|
-
log(`[moons] isr:
|
|
545
|
+
log(`[moons] isr: failed to regenerate ${options.pathname}: ${String(error)}`);
|
|
533
546
|
}
|
|
534
547
|
}
|
|
535
548
|
|
|
@@ -549,7 +562,7 @@ function createFileStorage(clientDir) {
|
|
|
549
562
|
async write(pathname, body) {
|
|
550
563
|
const target = staticFileTarget(clientDir, pathname);
|
|
551
564
|
if (target === null) {
|
|
552
|
-
throw new Error(`[moons] isr: path
|
|
565
|
+
throw new Error(`[moons] isr: invalid path to write: ${pathname}`);
|
|
553
566
|
}
|
|
554
567
|
mkdirSync(dirname(target), { recursive: true });
|
|
555
568
|
const tmp = `${target}.tmp-${process.pid}`;
|
|
@@ -632,9 +645,9 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
632
645
|
return;
|
|
633
646
|
}
|
|
634
647
|
shuttingDown = true;
|
|
635
|
-
log(`[moons]
|
|
648
|
+
log(`[moons] received ${signal} — no longer accepting connections, draining...`);
|
|
636
649
|
const timer = setTimeout(() => {
|
|
637
|
-
log(`[moons] drain
|
|
650
|
+
log(`[moons] drain exceeded ${timeoutMs}ms — forcing exit`);
|
|
638
651
|
const closeAll = server.closeAllConnections;
|
|
639
652
|
try {
|
|
640
653
|
closeAll?.call(server);
|
|
@@ -648,7 +661,7 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
648
661
|
server.close(async (error) => {
|
|
649
662
|
if (error !== undefined) {
|
|
650
663
|
clearTimeout(timer);
|
|
651
|
-
log(`[moons]
|
|
664
|
+
log(`[moons] server close failed: ${error.message}`);
|
|
652
665
|
exit(1);
|
|
653
666
|
return;
|
|
654
667
|
}
|
|
@@ -657,11 +670,11 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
657
670
|
try {
|
|
658
671
|
await options.onShutdown();
|
|
659
672
|
} catch (hookError) {
|
|
660
|
-
log(`[moons] onShutdown
|
|
673
|
+
log(`[moons] onShutdown failed: ${String(hookError)}`);
|
|
661
674
|
}
|
|
662
675
|
}
|
|
663
676
|
clearTimeout(timer);
|
|
664
|
-
log("[moons] shutdown
|
|
677
|
+
log("[moons] shutdown complete (full drain)");
|
|
665
678
|
exit(0);
|
|
666
679
|
});
|
|
667
680
|
};
|
|
@@ -724,6 +737,16 @@ function startNodeServer(handler, options = {}) {
|
|
|
724
737
|
throw error;
|
|
725
738
|
}
|
|
726
739
|
try {
|
|
740
|
+
if (options.trailingSlash !== undefined) {
|
|
741
|
+
const canonical = trailingSlashRedirect2(url.pathname, options.trailingSlash);
|
|
742
|
+
if (canonical !== null) {
|
|
743
|
+
url.pathname = canonical;
|
|
744
|
+
res.statusCode = 308;
|
|
745
|
+
res.setHeader("location", url.toString());
|
|
746
|
+
res.end();
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
727
750
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
728
751
|
if (options.storage !== undefined) {
|
|
729
752
|
const entry = await options.storage.read(url.pathname);
|
|
@@ -898,11 +921,11 @@ function rpcBodyTooLargeText(limit, size) {
|
|
|
898
921
|
const megabytes = (bytes) => (bytes / 1e6).toFixed(1);
|
|
899
922
|
return new MoonsError2({
|
|
900
923
|
code: "E_RPC_BODY_TOO_LARGE",
|
|
901
|
-
message: size === null ? `body
|
|
902
|
-
details: "Body
|
|
924
|
+
message: size === null ? `body exceeds the ${megabytes(limit)} MB limit` : `body ${megabytes(size)} MB exceeds the ${megabytes(limit)} MB limit`,
|
|
925
|
+
details: "Body exceeds `security.bodyLimitBytes`; reading stops at the transport before the body is fully buffered.",
|
|
903
926
|
suggestions: [
|
|
904
|
-
"
|
|
905
|
-
"
|
|
927
|
+
"Reduce the payload size, or",
|
|
928
|
+
"Increase security.bodyLimitBytes if you really need to"
|
|
906
929
|
]
|
|
907
930
|
}).format();
|
|
908
931
|
}
|
|
@@ -1007,7 +1030,7 @@ function scheduleRevalidation(input) {
|
|
|
1007
1030
|
if (logger !== null) {
|
|
1008
1031
|
logger({ msg: "isr:revalidate-failed", level: "warn", path: normalized, error: message });
|
|
1009
1032
|
} else {
|
|
1010
|
-
console.error(`[moons] isr
|
|
1033
|
+
console.error(`[moons] isr regeneration failed (${normalized}): ${message}`);
|
|
1011
1034
|
}
|
|
1012
1035
|
});
|
|
1013
1036
|
}
|
|
@@ -1068,7 +1091,7 @@ function createPluginRuntime(options) {
|
|
|
1068
1091
|
try {
|
|
1069
1092
|
await plugin.onShutdown?.(app);
|
|
1070
1093
|
} catch (error) {
|
|
1071
|
-
log(`[moons] plugin \`${plugin.name}\`
|
|
1094
|
+
log(`[moons] plugin \`${plugin.name}\` failed during onShutdown: ${String(error)}`);
|
|
1072
1095
|
}
|
|
1073
1096
|
}
|
|
1074
1097
|
},
|
|
@@ -1110,18 +1133,18 @@ async function runPrerender(options) {
|
|
|
1110
1133
|
throw new MoonsError3({
|
|
1111
1134
|
code: "E_ISR_INVALID",
|
|
1112
1135
|
file: route.file,
|
|
1113
|
-
message: "`revalidate`
|
|
1114
|
-
details: "ISR
|
|
1136
|
+
message: "`revalidate` is invalid or used without `prerender = true`",
|
|
1137
|
+
details: "File-based ISR: `revalidate` (seconds, number >= 0) + `prerender`.",
|
|
1115
1138
|
suggestions: [
|
|
1116
|
-
"
|
|
1117
|
-
"
|
|
1139
|
+
"Use a number of seconds, e.g. `export const revalidate = 60`",
|
|
1140
|
+
"Add `export const prerender = true` to the same page"
|
|
1118
1141
|
]
|
|
1119
1142
|
});
|
|
1120
1143
|
}
|
|
1121
1144
|
}
|
|
1122
1145
|
if (prerender !== true) {
|
|
1123
1146
|
if (prerender !== undefined && prerender !== false) {
|
|
1124
|
-
throw prerenderError(route, "`prerender`
|
|
1147
|
+
throw prerenderError(route, "`prerender` must be a boolean (`true`/`false`)");
|
|
1125
1148
|
}
|
|
1126
1149
|
continue;
|
|
1127
1150
|
}
|
|
@@ -1132,15 +1155,15 @@ async function runPrerender(options) {
|
|
|
1132
1155
|
headers: { "x-moons-prerender": "1" }
|
|
1133
1156
|
}));
|
|
1134
1157
|
if (response.status === 404) {
|
|
1135
|
-
log(`[moons] prerender:
|
|
1158
|
+
log(`[moons] prerender: skipped ${pathname} (notFound)`);
|
|
1136
1159
|
skipped += 1;
|
|
1137
1160
|
continue;
|
|
1138
1161
|
}
|
|
1139
1162
|
if (response.status >= 300 && response.status < 400) {
|
|
1140
|
-
throw prerenderError(route, `\`redirect()\`
|
|
1163
|
+
throw prerenderError(route, `\`redirect()\` is not supported during prerender (${pathname})`);
|
|
1141
1164
|
}
|
|
1142
1165
|
if (response.status !== 200) {
|
|
1143
|
-
throw prerenderError(route, `status ${response.status}
|
|
1166
|
+
throw prerenderError(route, `status ${response.status} while rendering ${pathname}`);
|
|
1144
1167
|
}
|
|
1145
1168
|
const html = await response.text();
|
|
1146
1169
|
assertNoLeak(html, pathname, route.file, options, env);
|
|
@@ -1161,7 +1184,7 @@ async function runPrerender(options) {
|
|
|
1161
1184
|
`);
|
|
1162
1185
|
const emitted = emitStaticFiles(options.clientDir, options.staticConfig, entries);
|
|
1163
1186
|
const extras = [...wrote404 ? [NOT_FOUND_FILE] : [], ...emitted];
|
|
1164
|
-
log(`[moons] prerender
|
|
1187
|
+
log(`[moons] prerender done: ${written} file${extras.length > 0 ? ` + ${extras.join(" + ")}` : ""} (${skipped} skipped)`);
|
|
1165
1188
|
return { written, skipped };
|
|
1166
1189
|
}
|
|
1167
1190
|
function emitStaticFiles(clientDir, staticConfig, entries) {
|
|
@@ -1226,7 +1249,7 @@ async function renderStaticNotFound(options, env, log) {
|
|
|
1226
1249
|
headers: { "x-moons-prerender": "1" }
|
|
1227
1250
|
}));
|
|
1228
1251
|
if (response.status !== 404) {
|
|
1229
|
-
log(`[moons] prerender: ${NOT_FOUND_FILE}
|
|
1252
|
+
log(`[moons] prerender: ${NOT_FOUND_FILE} skipped (sentinel \`${NOT_FOUND_SENTINEL}\` → ${response.status})`);
|
|
1230
1253
|
return null;
|
|
1231
1254
|
}
|
|
1232
1255
|
const html = await response.text();
|
|
@@ -1245,11 +1268,11 @@ function assertNoLeak(html, pathname, file, options, env) {
|
|
|
1245
1268
|
throw new MoonsError3({
|
|
1246
1269
|
code: "E_PRERENDER_LEAK",
|
|
1247
1270
|
file,
|
|
1248
|
-
message: `
|
|
1249
|
-
details: "HTML
|
|
1271
|
+
message: `prerender output \`${pathname}\` contains ${labels}`,
|
|
1272
|
+
details: "Static HTML is permanent — a prerendered secret cannot be retracted.",
|
|
1250
1273
|
suggestions: [
|
|
1251
|
-
"
|
|
1252
|
-
"
|
|
1274
|
+
"Do not return secret env values from a loader that runs at prerender time",
|
|
1275
|
+
"Turn off `prerender` for this route or move the data to a request-time serverFn"
|
|
1253
1276
|
]
|
|
1254
1277
|
});
|
|
1255
1278
|
}
|
|
@@ -1267,35 +1290,35 @@ async function resolvePathnames(route, module) {
|
|
|
1267
1290
|
throw new MoonsError3({
|
|
1268
1291
|
code: "E_PRERENDER_NO_PATHS",
|
|
1269
1292
|
file: route.file,
|
|
1270
|
-
message: "route
|
|
1271
|
-
details: "
|
|
1293
|
+
message: "dynamic route with `prerender = true` without `export const paths`",
|
|
1294
|
+
details: "Prerendering needs a param list to render each path.",
|
|
1272
1295
|
suggestions: [
|
|
1273
|
-
'
|
|
1274
|
-
"
|
|
1296
|
+
'Add `export const paths = () => [{ id: "…" }]`',
|
|
1297
|
+
"Or remove `prerender` so the route renders at request time (SSR)"
|
|
1275
1298
|
]
|
|
1276
1299
|
});
|
|
1277
1300
|
}
|
|
1278
1301
|
const raw = await paths();
|
|
1279
1302
|
if (!Array.isArray(raw)) {
|
|
1280
|
-
throw prerenderError(route, "`paths()`
|
|
1303
|
+
throw prerenderError(route, "`paths()` must return an array");
|
|
1281
1304
|
}
|
|
1282
1305
|
const expected = new Set(route.params);
|
|
1283
1306
|
const result = [];
|
|
1284
1307
|
for (const item of raw) {
|
|
1285
1308
|
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
1286
|
-
throw prerenderError(route, "
|
|
1309
|
+
throw prerenderError(route, "`paths()` items must be objects with string params");
|
|
1287
1310
|
}
|
|
1288
1311
|
const record = item;
|
|
1289
1312
|
const keys = Object.keys(record);
|
|
1290
1313
|
for (const name of expected) {
|
|
1291
1314
|
const value = record[name];
|
|
1292
1315
|
if (typeof value !== "string" || value.length === 0) {
|
|
1293
|
-
throw prerenderError(route, `param \`${name}\`
|
|
1316
|
+
throw prerenderError(route, `param \`${name}\` must be a non-empty string`);
|
|
1294
1317
|
}
|
|
1295
1318
|
}
|
|
1296
1319
|
for (const key of keys) {
|
|
1297
1320
|
if (!expected.has(key)) {
|
|
1298
|
-
throw prerenderError(route, `param \`${key}\`
|
|
1321
|
+
throw prerenderError(route, `param \`${key}\` is not in the route pattern`);
|
|
1299
1322
|
}
|
|
1300
1323
|
}
|
|
1301
1324
|
result.push(buildPathname(route, record));
|
|
@@ -1328,7 +1351,7 @@ function buildPathname(route, params) {
|
|
|
1328
1351
|
}
|
|
1329
1352
|
function encodeSegment(route, value) {
|
|
1330
1353
|
if (value.length === 0 || /^\.+$/.test(value)) {
|
|
1331
|
-
throw prerenderError(route, `
|
|
1354
|
+
throw prerenderError(route, `param value \`${value}\` is not valid for a file path`);
|
|
1332
1355
|
}
|
|
1333
1356
|
return encodeURIComponent(value);
|
|
1334
1357
|
}
|
|
@@ -1339,9 +1362,9 @@ function outputFile(clientDir, pathname) {
|
|
|
1339
1362
|
if (file !== base && !file.startsWith(base + sep2)) {
|
|
1340
1363
|
throw new MoonsError3({
|
|
1341
1364
|
code: "E_PRERENDER_INVALID",
|
|
1342
|
-
message: `
|
|
1343
|
-
details: "
|
|
1344
|
-
suggestions: ["
|
|
1365
|
+
message: `prerender path escapes the client directory (\`${pathname}\`)`,
|
|
1366
|
+
details: "The file name must stay inside `dist/client`.",
|
|
1367
|
+
suggestions: ["Check the `paths` param values — do not use `..`"]
|
|
1345
1368
|
});
|
|
1346
1369
|
}
|
|
1347
1370
|
return file;
|
|
@@ -1351,10 +1374,10 @@ function prerenderError(route, message) {
|
|
|
1351
1374
|
code: "E_PRERENDER_INVALID",
|
|
1352
1375
|
file: route.file,
|
|
1353
1376
|
message,
|
|
1354
|
-
details: "
|
|
1377
|
+
details: "The prerender contract is enforced.",
|
|
1355
1378
|
suggestions: [
|
|
1356
|
-
"
|
|
1357
|
-
"
|
|
1379
|
+
"Fix the `paths`/params shape so it matches the route pattern",
|
|
1380
|
+
"Do not use `redirect()` on a prerendered route"
|
|
1358
1381
|
]
|
|
1359
1382
|
});
|
|
1360
1383
|
}
|
|
@@ -1378,4 +1401,4 @@ export {
|
|
|
1378
1401
|
toWebRequest2 as toWebRequest
|
|
1379
1402
|
};
|
|
1380
1403
|
|
|
1381
|
-
//# debugId=
|
|
1404
|
+
//# debugId=01666BCB63B6268E64756E2164756E21
|