@moonsjs/vite 0.3.0 → 0.5.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-0agjty3z.js → index-sdm38v1p.js} +445 -63
- package/dist/index-sdm38v1p.js.map +18 -0
- package/dist/index.js +8 -5
- package/dist/index.js.map +3 -3
- package/dist/plugin.d.ts.map +1 -1
- package/dist/server/bun-server.d.ts +18 -0
- package/dist/server/bun-server.d.ts.map +1 -0
- package/dist/server/dev.d.ts +2 -0
- package/dist/server/dev.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +423 -291
- package/dist/server/index.js.map +7 -7
- package/dist/server/node-server.d.ts +4 -11
- package/dist/server/node-server.d.ts.map +1 -1
- package/dist/server/node.d.ts +3 -19
- package/dist/server/node.d.ts.map +1 -1
- package/dist/server/server-select.d.ts +9 -0
- package/dist/server/server-select.d.ts.map +1 -0
- package/dist/server/transport.d.ts +98 -0
- package/dist/server/transport.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/plugin.ts +5 -2
- package/src/server/bun-server.ts +443 -0
- package/src/server/dev.ts +27 -2
- package/src/server/index.ts +2 -0
- package/src/server/node-server.ts +108 -274
- package/src/server/node.ts +24 -92
- package/src/server/server-select.ts +27 -0
- package/src/server/storage.ts +10 -0
- package/src/server/transport.ts +504 -0
- package/dist/index-0agjty3z.js.map +0 -15
|
@@ -92,10 +92,93 @@ function createFileShell2(options = {}) {
|
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
// src/server/
|
|
96
|
-
import {
|
|
97
|
-
import {
|
|
98
|
-
import {
|
|
95
|
+
// src/server/transport.ts
|
|
96
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
97
|
+
import { join as join2, resolve as resolve2, sep } from "node:path";
|
|
98
|
+
import { gzipSync } from "node:zlib";
|
|
99
|
+
import { MoonsError as MoonsError2 } from "@moonsjs/core";
|
|
100
|
+
|
|
101
|
+
// src/server/isr.ts
|
|
102
|
+
import { renameSync, writeFileSync } from "node:fs";
|
|
103
|
+
|
|
104
|
+
// src/payload-scan.ts
|
|
105
|
+
var SECRET_KEY_PATTERN = /(SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|AUTH|API_KEY|ACCESS_KEY|SIGNING)/i;
|
|
106
|
+
var MIN_SECRET_LENGTH = 12;
|
|
107
|
+
function scanPayloadForSecrets(payload, options = {}) {
|
|
108
|
+
const findings = [];
|
|
109
|
+
const canary = options.canary;
|
|
110
|
+
if (canary !== undefined && canary.length > 0 && payload.includes(canary)) {
|
|
111
|
+
findings.push({ kind: "canary", label: "canary" });
|
|
112
|
+
}
|
|
113
|
+
for (const [key, value] of Object.entries(options.env ?? {})) {
|
|
114
|
+
if (typeof value !== "string" || value.length < MIN_SECRET_LENGTH) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!SECRET_KEY_PATTERN.test(key) || /PUBLIC/i.test(key)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (payload.includes(value)) {
|
|
121
|
+
findings.push({ kind: "env", label: key });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return findings;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/server/isr.ts
|
|
128
|
+
function shouldRevalidate(input) {
|
|
129
|
+
const now = input.now ?? Date.now();
|
|
130
|
+
return now - input.mtimeMs >= input.revalidateSeconds * 1000;
|
|
131
|
+
}
|
|
132
|
+
var inflight = new Map;
|
|
133
|
+
function revalidatePrerendered(options) {
|
|
134
|
+
const existing = inflight.get(options.pathname);
|
|
135
|
+
if (existing !== undefined) {
|
|
136
|
+
return existing;
|
|
137
|
+
}
|
|
138
|
+
const task = runRevalidate(options).finally(() => {
|
|
139
|
+
inflight.delete(options.pathname);
|
|
140
|
+
});
|
|
141
|
+
inflight.set(options.pathname, task);
|
|
142
|
+
return task;
|
|
143
|
+
}
|
|
144
|
+
async function runRevalidate(options) {
|
|
145
|
+
const log = options.log ?? ((message) => console.log(message));
|
|
146
|
+
try {
|
|
147
|
+
const response = await options.handler(new Request(`http://localhost${options.pathname}`, {
|
|
148
|
+
method: "GET",
|
|
149
|
+
headers: { "x-moons-prerender": "1" }
|
|
150
|
+
}));
|
|
151
|
+
if (response.status !== 200) {
|
|
152
|
+
log(`[moons] isr: skipped ${options.pathname} (status ${response.status})`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const html = await response.text();
|
|
156
|
+
const findings = scanPayloadForSecrets(html, {
|
|
157
|
+
...options.canary === undefined ? {} : { canary: options.canary },
|
|
158
|
+
env: options.env ?? process.env
|
|
159
|
+
});
|
|
160
|
+
if (findings.length > 0) {
|
|
161
|
+
const labels = findings.map((finding) => finding.kind === "canary" ? "canary" : `env(${finding.label})`).join(", ");
|
|
162
|
+
log(`[moons] isr: NOT writing ${options.pathname} — contains ${labels}`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (options.storage !== undefined) {
|
|
166
|
+
await options.storage.write(options.pathname, new TextEncoder().encode(html));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (options.file === undefined) {
|
|
170
|
+
log(`[moons] isr: skipped ${options.pathname} — no destination file/storage`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const tmp = `${options.file}.tmp-${process.pid}`;
|
|
174
|
+
writeFileSync(tmp, html);
|
|
175
|
+
renameSync(tmp, options.file);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
log(`[moons] isr: failed to regenerate ${options.pathname}: ${String(error)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/server/transport.ts
|
|
99
182
|
var MAX_TRANSPORT_BODY_BYTES = 32 * 1024 * 1024;
|
|
100
183
|
|
|
101
184
|
class InvalidRequestError extends Error {
|
|
@@ -113,6 +196,346 @@ class BodyLimitError extends Error {
|
|
|
113
196
|
this.limit = limit;
|
|
114
197
|
}
|
|
115
198
|
}
|
|
199
|
+
var DEFAULT_SECURITY_HEADERS = {
|
|
200
|
+
"x-content-type-options": "nosniff",
|
|
201
|
+
"referrer-policy": "strict-origin-when-cross-origin"
|
|
202
|
+
};
|
|
203
|
+
function firstHeaderValue(value) {
|
|
204
|
+
if (value === undefined) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
208
|
+
if (raw === undefined) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const first = raw.split(",")[0]?.trim() ?? "";
|
|
212
|
+
return first.length === 0 ? null : first;
|
|
213
|
+
}
|
|
214
|
+
function forwardedProtocol(value) {
|
|
215
|
+
const first = firstHeaderValue(value)?.toLowerCase() ?? null;
|
|
216
|
+
if (first === "https") {
|
|
217
|
+
return "https";
|
|
218
|
+
}
|
|
219
|
+
if (first === "http") {
|
|
220
|
+
return "http";
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
function forwardedHost(value) {
|
|
225
|
+
const first = firstHeaderValue(value);
|
|
226
|
+
if (first === null || first.length === 0 || first.length > 255) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
if (!/^[A-Za-z0-9.\-[\]:]+$/.test(first)) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
return first;
|
|
233
|
+
}
|
|
234
|
+
function isPlausibleIp(value) {
|
|
235
|
+
return value.length <= 64 && /^[0-9a-fA-F:.]+$/.test(value);
|
|
236
|
+
}
|
|
237
|
+
function stripZoneId(value) {
|
|
238
|
+
const index = value.indexOf("%");
|
|
239
|
+
return index === -1 ? value : value.slice(0, index);
|
|
240
|
+
}
|
|
241
|
+
function normalizeIp(value) {
|
|
242
|
+
const withoutZone = stripZoneId(value);
|
|
243
|
+
return withoutZone.startsWith("::ffff:") ? withoutZone.slice(7) : withoutZone;
|
|
244
|
+
}
|
|
245
|
+
function resolveClientIpValues(forwardedFor, remoteAddress, trustProxy) {
|
|
246
|
+
if (trustProxy) {
|
|
247
|
+
const forwarded = firstHeaderValue(forwardedFor);
|
|
248
|
+
if (forwarded !== null && isPlausibleIp(forwarded)) {
|
|
249
|
+
return normalizeIp(forwarded);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return remoteAddress === null ? null : normalizeIp(remoteAddress);
|
|
253
|
+
}
|
|
254
|
+
var MIME_TYPES = {
|
|
255
|
+
".js": "text/javascript; charset=utf-8",
|
|
256
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
257
|
+
".css": "text/css; charset=utf-8",
|
|
258
|
+
".html": "text/html; charset=utf-8",
|
|
259
|
+
".json": "application/json; charset=utf-8",
|
|
260
|
+
".map": "application/json; charset=utf-8",
|
|
261
|
+
".ico": "image/x-icon",
|
|
262
|
+
".png": "image/png",
|
|
263
|
+
".jpg": "image/jpeg",
|
|
264
|
+
".jpeg": "image/jpeg",
|
|
265
|
+
".gif": "image/gif",
|
|
266
|
+
".svg": "image/svg+xml",
|
|
267
|
+
".webp": "image/webp",
|
|
268
|
+
".avif": "image/avif",
|
|
269
|
+
".woff": "font/woff",
|
|
270
|
+
".woff2": "font/woff2",
|
|
271
|
+
".ttf": "font/ttf",
|
|
272
|
+
".txt": "text/plain; charset=utf-8",
|
|
273
|
+
".wasm": "application/wasm",
|
|
274
|
+
".xml": "application/xml; charset=utf-8",
|
|
275
|
+
".csv": "text/csv; charset=utf-8",
|
|
276
|
+
".md": "text/markdown; charset=utf-8",
|
|
277
|
+
".pdf": "application/pdf",
|
|
278
|
+
".mp4": "video/mp4",
|
|
279
|
+
".webm": "video/webm",
|
|
280
|
+
".mp3": "audio/mpeg",
|
|
281
|
+
".wav": "audio/wav",
|
|
282
|
+
".zip": "application/zip",
|
|
283
|
+
".gz": "application/gzip"
|
|
284
|
+
};
|
|
285
|
+
var COMPRESSIBLE_FILE = /\.(js|mjs|css|html|json|svg|txt|map|xml)$/i;
|
|
286
|
+
var GZIP_MIN_BYTES = 1024;
|
|
287
|
+
var GZIP_CACHE_MAX = 64;
|
|
288
|
+
var gzipCache = new Map;
|
|
289
|
+
function gzipCached(file, mtimeMs, body) {
|
|
290
|
+
const cached = gzipCache.get(file);
|
|
291
|
+
if (cached !== undefined && cached.mtimeMs === mtimeMs) {
|
|
292
|
+
return cached.gzip;
|
|
293
|
+
}
|
|
294
|
+
const gzip = gzipSync(body);
|
|
295
|
+
if (gzipCache.size >= GZIP_CACHE_MAX) {
|
|
296
|
+
const oldest = gzipCache.keys().next().value;
|
|
297
|
+
if (oldest !== undefined) {
|
|
298
|
+
gzipCache.delete(oldest);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
gzipCache.set(file, { mtimeMs, gzip });
|
|
302
|
+
return gzip;
|
|
303
|
+
}
|
|
304
|
+
function acceptsGzipHeader(value) {
|
|
305
|
+
if (value === null) {
|
|
306
|
+
return false;
|
|
307
|
+
}
|
|
308
|
+
return value.toLowerCase().split(",").map((part) => part.trim()).some((part) => part === "gzip" || part === "*" || part.startsWith("gzip;"));
|
|
309
|
+
}
|
|
310
|
+
function rpcBodyTooLargeText(limit, size) {
|
|
311
|
+
const megabytes = (bytes) => (bytes / 1e6).toFixed(1);
|
|
312
|
+
return new MoonsError2({
|
|
313
|
+
code: "E_RPC_BODY_TOO_LARGE",
|
|
314
|
+
message: size === null ? `body exceeds the ${megabytes(limit)} MB limit` : `body ${megabytes(size)} MB exceeds the ${megabytes(limit)} MB limit`,
|
|
315
|
+
details: "Body exceeds `security.bodyLimitBytes`; reading stops at the transport before the body is fully buffered.",
|
|
316
|
+
suggestions: [
|
|
317
|
+
"Reduce the payload size, or",
|
|
318
|
+
"Increase security.bodyLimitBytes if you really need to"
|
|
319
|
+
]
|
|
320
|
+
}).format();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
class RequestTimeoutError extends Error {
|
|
324
|
+
limit;
|
|
325
|
+
constructor(limit) {
|
|
326
|
+
super(`handler did not respond within ${limit} ms`);
|
|
327
|
+
this.name = "RequestTimeoutError";
|
|
328
|
+
this.limit = limit;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function requestTimeoutText(limit) {
|
|
332
|
+
return new MoonsError2({
|
|
333
|
+
code: "E_REQUEST_TIMEOUT",
|
|
334
|
+
message: `handler did not respond within ${limit} ms`,
|
|
335
|
+
details: "The transport stopped waiting for the handler and answered 504. " + "The handler keeps running until it settles; JavaScript cannot cancel it.",
|
|
336
|
+
suggestions: [
|
|
337
|
+
"Make the handler faster, or move slow work out of the request path",
|
|
338
|
+
"Raise `requestTimeoutMs`, or set it to 0 to disable the timeout"
|
|
339
|
+
]
|
|
340
|
+
}).format();
|
|
341
|
+
}
|
|
342
|
+
async function runHandlerWithTimeout(handler, request, timeoutMs) {
|
|
343
|
+
if (timeoutMs === undefined || timeoutMs <= 0) {
|
|
344
|
+
return handler(request);
|
|
345
|
+
}
|
|
346
|
+
let timer;
|
|
347
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
348
|
+
timer = setTimeout(() => reject(new RequestTimeoutError(timeoutMs)), timeoutMs);
|
|
349
|
+
});
|
|
350
|
+
try {
|
|
351
|
+
return await Promise.race([handler(request), timeout]);
|
|
352
|
+
} finally {
|
|
353
|
+
if (timer !== undefined) {
|
|
354
|
+
clearTimeout(timer);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function isAbortLike(error) {
|
|
359
|
+
if (error instanceof DOMException) {
|
|
360
|
+
return error.name === "AbortError" || error.name === "TimeoutError";
|
|
361
|
+
}
|
|
362
|
+
if (error instanceof Error) {
|
|
363
|
+
if (error.name === "AbortError") {
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
const code = error.code;
|
|
367
|
+
if (code === "ECONNRESET" || code === "EPIPE" || code === "ERR_STREAM_PREMATURE_CLOSE") {
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
const message = error.message.toLowerCase();
|
|
371
|
+
return message === "aborted" || message.includes("aborted") || message.includes("premature close");
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
function textResponseWithHeaders(status, text, configured) {
|
|
376
|
+
const headers = new Headers({ "content-type": "text/plain; charset=utf-8" });
|
|
377
|
+
applyStaticHeaders(headers, "/", configured);
|
|
378
|
+
return new Response(text, { status, headers });
|
|
379
|
+
}
|
|
380
|
+
function applyStaticHeaders(target, pathname, configured) {
|
|
381
|
+
for (const [name, value] of Object.entries(configured ?? {})) {
|
|
382
|
+
if (!target.has(name)) {
|
|
383
|
+
target.set(name, value);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
for (const [name, value] of Object.entries(DEFAULT_SECURITY_HEADERS)) {
|
|
387
|
+
if (!target.has(name)) {
|
|
388
|
+
target.set(name, value);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (pathname.startsWith("/assets/") && !target.has("cache-control")) {
|
|
392
|
+
target.set("cache-control", "public, max-age=31536000, immutable");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function readBodyWithLimit(stream, maxBytes) {
|
|
396
|
+
if (stream === null) {
|
|
397
|
+
return new Uint8Array(0);
|
|
398
|
+
}
|
|
399
|
+
const reader = stream.getReader();
|
|
400
|
+
const chunks = [];
|
|
401
|
+
let size = 0;
|
|
402
|
+
try {
|
|
403
|
+
for (;; ) {
|
|
404
|
+
const { done, value } = await reader.read();
|
|
405
|
+
if (done) {
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
if (value !== undefined) {
|
|
409
|
+
size += value.byteLength;
|
|
410
|
+
if (size > maxBytes) {
|
|
411
|
+
throw new BodyLimitError(maxBytes);
|
|
412
|
+
}
|
|
413
|
+
chunks.push(value);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} finally {
|
|
417
|
+
try {
|
|
418
|
+
await reader.cancel();
|
|
419
|
+
} catch {}
|
|
420
|
+
}
|
|
421
|
+
const output = new Uint8Array(size);
|
|
422
|
+
let offset = 0;
|
|
423
|
+
for (const chunk of chunks) {
|
|
424
|
+
output.set(chunk, offset);
|
|
425
|
+
offset += chunk.byteLength;
|
|
426
|
+
}
|
|
427
|
+
return output;
|
|
428
|
+
}
|
|
429
|
+
function resolveStaticFile2(clientDir, pathname) {
|
|
430
|
+
const target = staticFileTarget(clientDir, pathname);
|
|
431
|
+
return target !== null && isFile(target) ? target : null;
|
|
432
|
+
}
|
|
433
|
+
function staticFileTarget(clientDir, pathname) {
|
|
434
|
+
let decoded;
|
|
435
|
+
try {
|
|
436
|
+
decoded = decodeURIComponent(pathname);
|
|
437
|
+
} catch {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const exact = safeWithin(clientDir, `.${decoded}`);
|
|
441
|
+
if (exact !== null && isFile(exact)) {
|
|
442
|
+
return exact;
|
|
443
|
+
}
|
|
444
|
+
const normalized = decoded.length > 1 ? decoded.replace(/\/+$/, "") : decoded;
|
|
445
|
+
const manifest = readPrerenderManifest(clientDir);
|
|
446
|
+
if (manifest.paths.has(normalized)) {
|
|
447
|
+
const target = normalized === "/" ? "index.html" : `${normalized.slice(1)}/index.html`;
|
|
448
|
+
return safeWithin(clientDir, `./${target}`);
|
|
449
|
+
}
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
function safeWithin(clientDir, relative) {
|
|
453
|
+
const candidate = resolve2(clientDir, relative);
|
|
454
|
+
if (candidate !== clientDir && !candidate.startsWith(clientDir + sep)) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
return candidate;
|
|
458
|
+
}
|
|
459
|
+
function isFile(path) {
|
|
460
|
+
return existsSync2(path) && statSync(path).isFile();
|
|
461
|
+
}
|
|
462
|
+
var prerenderManifestCache = null;
|
|
463
|
+
function readPrerenderManifest(clientDir) {
|
|
464
|
+
const file = join2(clientDir, "_moons_prerender.json");
|
|
465
|
+
const empty = { paths: new Set, revalidate: new Map };
|
|
466
|
+
try {
|
|
467
|
+
const mtimeMs = statSync(file).mtimeMs;
|
|
468
|
+
if (prerenderManifestCache !== null && prerenderManifestCache.file === file && prerenderManifestCache.mtimeMs === mtimeMs) {
|
|
469
|
+
return prerenderManifestCache.info;
|
|
470
|
+
}
|
|
471
|
+
const json = JSON.parse(readFileSync2(file, "utf8"));
|
|
472
|
+
const paths = new Set;
|
|
473
|
+
const revalidate = new Map;
|
|
474
|
+
if (Array.isArray(json.paths)) {
|
|
475
|
+
for (const path of json.paths) {
|
|
476
|
+
if (typeof path === "string") {
|
|
477
|
+
paths.add(path);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (Array.isArray(json.entries)) {
|
|
482
|
+
for (const entry of json.entries) {
|
|
483
|
+
if (entry === null || typeof entry !== "object" || typeof entry["path"] !== "string") {
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
const path = entry["path"];
|
|
487
|
+
paths.add(path);
|
|
488
|
+
const seconds = entry["revalidate"];
|
|
489
|
+
if (typeof seconds === "number" && Number.isFinite(seconds) && seconds >= 0) {
|
|
490
|
+
revalidate.set(path, seconds);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const info = { paths, revalidate };
|
|
495
|
+
prerenderManifestCache = { file, mtimeMs, info };
|
|
496
|
+
return info;
|
|
497
|
+
} catch {
|
|
498
|
+
return empty;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function scheduleRevalidation(input) {
|
|
502
|
+
const normalized = input.pathname.length > 1 ? input.pathname.replace(/\/+$/, "") : input.pathname;
|
|
503
|
+
const seconds = readPrerenderManifest(input.clientDir).revalidate.get(normalized);
|
|
504
|
+
if (seconds === undefined || seconds < 0) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (!shouldRevalidate({ mtimeMs: input.mtimeMs, revalidateSeconds: seconds })) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const regeneratedAt = performance.now();
|
|
511
|
+
const logger = input.logger ?? null;
|
|
512
|
+
revalidatePrerendered({
|
|
513
|
+
pathname: normalized,
|
|
514
|
+
...input.file === undefined ? {} : { file: input.file },
|
|
515
|
+
...input.storage === undefined ? {} : { storage: input.storage },
|
|
516
|
+
handler: input.handler,
|
|
517
|
+
...input.options.canary === undefined ? {} : { canary: input.options.canary },
|
|
518
|
+
env: process.env
|
|
519
|
+
}).then(() => {
|
|
520
|
+
logger?.({
|
|
521
|
+
msg: "isr:regenerated",
|
|
522
|
+
path: normalized,
|
|
523
|
+
durationMs: Math.round(performance.now() - regeneratedAt)
|
|
524
|
+
});
|
|
525
|
+
}).catch((error) => {
|
|
526
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
527
|
+
if (logger !== null) {
|
|
528
|
+
logger({ msg: "isr:revalidate-failed", level: "warn", path: normalized, error: message });
|
|
529
|
+
} else {
|
|
530
|
+
console.error(`[moons] isr regeneration failed (${normalized}): ${message}`);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/server/node.ts
|
|
536
|
+
import { Readable } from "node:stream";
|
|
537
|
+
import { pipeline } from "node:stream/promises";
|
|
538
|
+
import { CLIENT_IP_HEADER } from "@moonsjs/core";
|
|
116
539
|
function resolveRequestUrl(req, options = {}) {
|
|
117
540
|
const hostHeader = req.headers.host ?? "localhost";
|
|
118
541
|
const trustProxy = options.trustProxy === true;
|
|
@@ -126,18 +549,7 @@ function resolveRequestUrl(req, options = {}) {
|
|
|
126
549
|
}
|
|
127
550
|
}
|
|
128
551
|
function resolveClientIp(req, trustProxy) {
|
|
129
|
-
|
|
130
|
-
const forwarded = firstHeaderValue(req.headers["x-forwarded-for"]);
|
|
131
|
-
if (forwarded !== null && isPlausibleIp(forwarded)) {
|
|
132
|
-
return normalizeIp(forwarded);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const remote = req.socket?.remoteAddress ?? null;
|
|
136
|
-
return remote === null ? null : normalizeIp(remote);
|
|
137
|
-
}
|
|
138
|
-
function normalizeIp(value) {
|
|
139
|
-
const withoutZone = stripZoneId(value);
|
|
140
|
-
return withoutZone.startsWith("::ffff:") ? withoutZone.slice(7) : withoutZone;
|
|
552
|
+
return resolveClientIpValues(req.headers["x-forwarded-for"], req.socket?.remoteAddress ?? null, trustProxy);
|
|
141
553
|
}
|
|
142
554
|
async function toWebRequest2(req, options = {}) {
|
|
143
555
|
const url = resolveRequestUrl(req, options);
|
|
@@ -199,10 +611,6 @@ function readBody(req, maxBytes) {
|
|
|
199
611
|
req.on("error", (error) => fail(error));
|
|
200
612
|
});
|
|
201
613
|
}
|
|
202
|
-
var DEFAULT_SECURITY_HEADERS = {
|
|
203
|
-
"x-content-type-options": "nosniff",
|
|
204
|
-
"referrer-policy": "strict-origin-when-cross-origin"
|
|
205
|
-
};
|
|
206
614
|
async function sendNodeResponse2(res, response) {
|
|
207
615
|
res.statusCode = response.status;
|
|
208
616
|
response.headers.forEach((value, key) => {
|
|
@@ -234,44 +642,6 @@ async function sendNodeResponse2(res, response) {
|
|
|
234
642
|
}
|
|
235
643
|
await pipeline(Readable.fromWeb(response.body), res);
|
|
236
644
|
}
|
|
237
|
-
function firstHeaderValue(value) {
|
|
238
|
-
if (value === undefined) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
const raw = Array.isArray(value) ? value[0] : value;
|
|
242
|
-
if (raw === undefined) {
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
const first = raw.split(",")[0]?.trim() ?? "";
|
|
246
|
-
return first.length === 0 ? null : first;
|
|
247
|
-
}
|
|
248
|
-
function forwardedProtocol(value) {
|
|
249
|
-
const first = firstHeaderValue(value)?.toLowerCase() ?? null;
|
|
250
|
-
if (first === "https") {
|
|
251
|
-
return "https";
|
|
252
|
-
}
|
|
253
|
-
if (first === "http") {
|
|
254
|
-
return "http";
|
|
255
|
-
}
|
|
256
|
-
return null;
|
|
257
|
-
}
|
|
258
|
-
function forwardedHost(value) {
|
|
259
|
-
const first = firstHeaderValue(value);
|
|
260
|
-
if (first === null || first.length === 0 || first.length > 255) {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
if (!/^[A-Za-z0-9.\-[\]:]+$/.test(first)) {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
return first;
|
|
267
|
-
}
|
|
268
|
-
function isPlausibleIp(value) {
|
|
269
|
-
return value.length <= 64 && /^[0-9a-fA-F:.]+$/.test(value);
|
|
270
|
-
}
|
|
271
|
-
function stripZoneId(value) {
|
|
272
|
-
const index = value.indexOf("%");
|
|
273
|
-
return index === -1 ? value : value.slice(0, index);
|
|
274
|
-
}
|
|
275
645
|
|
|
276
646
|
// src/virtual-modules.ts
|
|
277
647
|
var VIRTUAL_MODULES = {
|
|
@@ -432,7 +802,19 @@ async function handleRequest(getApp, req, res, options) {
|
|
|
432
802
|
trustProxy: options.trustProxy === true,
|
|
433
803
|
...isRpc ? { maxBodyBytes: options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES } : {}
|
|
434
804
|
});
|
|
435
|
-
|
|
805
|
+
let response;
|
|
806
|
+
try {
|
|
807
|
+
response = await runHandlerWithTimeout(app.handler, request, options.requestTimeoutMs);
|
|
808
|
+
} catch (error) {
|
|
809
|
+
if (error instanceof RequestTimeoutError) {
|
|
810
|
+
res.statusCode = 504;
|
|
811
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
812
|
+
applyStaticHeaders({ has: (name) => res.hasHeader(name), set: (name, value) => res.setHeader(name, value) }, "/", undefined);
|
|
813
|
+
res.end(requestTimeoutText(error.limit));
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
436
818
|
await sendNodeResponse2(res, response);
|
|
437
819
|
}
|
|
438
820
|
function shouldHandle(req, apiPrefix) {
|
|
@@ -462,7 +844,7 @@ function shouldHandle(req, apiPrefix) {
|
|
|
462
844
|
|
|
463
845
|
// src/manifest.ts
|
|
464
846
|
import { findLayouts, routeKeyFromFile } from "@moonsjs/core";
|
|
465
|
-
import { relative, sep } from "node:path";
|
|
847
|
+
import { relative, sep as sep2 } from "node:path";
|
|
466
848
|
var NOT_FOUND_ROUTE_KEY = "__moons/404";
|
|
467
849
|
var ERROR_ROUTE_KEY = "__moons/error";
|
|
468
850
|
function buildClientManifest(routes, routesDir) {
|
|
@@ -534,10 +916,10 @@ function renderRoutesModule(routes, routesDir, projectRoot) {
|
|
|
534
916
|
}
|
|
535
917
|
function rootSpecifier(projectRoot, file) {
|
|
536
918
|
let rel = relative(projectRoot, file);
|
|
537
|
-
rel =
|
|
919
|
+
rel = sep2 === "/" ? rel : rel.split(sep2).join("/");
|
|
538
920
|
return rel.startsWith("/") ? rel : `/${rel}`;
|
|
539
921
|
}
|
|
540
922
|
|
|
541
|
-
export { NOT_FOUND_ROUTE_KEY, renderApiModule, renderRoutesModule, rootSpecifier, VIRTUAL_MODULES, fromNullPrefixed, ROOT_PLACEHOLDER2, DEFAULT_TEMPLATE2, injectShell2, createDevShell2, createFileShell2, InvalidRequestError, BodyLimitError, resolveRequestUrl, resolveClientIp, toWebRequest2,
|
|
923
|
+
export { NOT_FOUND_ROUTE_KEY, renderApiModule, renderRoutesModule, rootSpecifier, VIRTUAL_MODULES, fromNullPrefixed, ROOT_PLACEHOLDER2, DEFAULT_TEMPLATE2, injectShell2, createDevShell2, createFileShell2, scanPayloadForSecrets, MAX_TRANSPORT_BODY_BYTES, InvalidRequestError, BodyLimitError, DEFAULT_SECURITY_HEADERS, forwardedProtocol, forwardedHost, resolveClientIpValues, MIME_TYPES, COMPRESSIBLE_FILE, GZIP_MIN_BYTES, gzipCached, acceptsGzipHeader, rpcBodyTooLargeText, RequestTimeoutError, requestTimeoutText, runHandlerWithTimeout, isAbortLike, textResponseWithHeaders, applyStaticHeaders, readBodyWithLimit, resolveStaticFile2, staticFileTarget, scheduleRevalidation, resolveRequestUrl, resolveClientIp, toWebRequest2, sendNodeResponse2, installDevMiddleware2 };
|
|
542
924
|
|
|
543
|
-
//# debugId=
|
|
925
|
+
//# debugId=A0E69BBAD13811FD64756E2164756E21
|