@moonsjs/vite 0.2.5 → 0.4.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-df6b8rqt.js} +364 -62
- package/dist/index-df6b8rqt.js.map +18 -0
- package/dist/index.js +4 -4
- package/dist/index.js.map +3 -3
- package/dist/server/bun-server.d.ts +18 -0
- package/dist/server/bun-server.d.ts.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +317 -275
- package/dist/server/index.js.map +6 -6
- package/dist/server/node-server.d.ts +2 -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 +82 -0
- package/dist/server/transport.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/plugin.ts +2 -2
- package/src/server/bun-server.ts +385 -0
- package/src/server/index.ts +2 -0
- package/src/server/node-server.ts +31 -259
- package/src/server/node.ts +24 -92
- package/src/server/server-select.ts +27 -0
- package/src/server/transport.ts +414 -0
- package/dist/index-0agjty3z.js.map +0 -15
package/dist/server/index.js
CHANGED
|
@@ -5,15 +5,31 @@ import {
|
|
|
5
5
|
injectShell2,
|
|
6
6
|
createDevShell2,
|
|
7
7
|
createFileShell2,
|
|
8
|
+
scanPayloadForSecrets,
|
|
9
|
+
MAX_TRANSPORT_BODY_BYTES,
|
|
8
10
|
InvalidRequestError,
|
|
9
11
|
BodyLimitError,
|
|
12
|
+
DEFAULT_SECURITY_HEADERS,
|
|
13
|
+
forwardedProtocol,
|
|
14
|
+
forwardedHost,
|
|
15
|
+
resolveClientIpValues,
|
|
16
|
+
MIME_TYPES,
|
|
17
|
+
COMPRESSIBLE_FILE,
|
|
18
|
+
GZIP_MIN_BYTES,
|
|
19
|
+
gzipCached,
|
|
20
|
+
acceptsGzipHeader,
|
|
21
|
+
rpcBodyTooLargeText,
|
|
22
|
+
applyStaticHeaders,
|
|
23
|
+
readBodyWithLimit,
|
|
24
|
+
resolveStaticFile2,
|
|
25
|
+
staticFileTarget,
|
|
26
|
+
scheduleRevalidation,
|
|
10
27
|
resolveRequestUrl,
|
|
11
28
|
resolveClientIp,
|
|
12
29
|
toWebRequest2,
|
|
13
|
-
DEFAULT_SECURITY_HEADERS,
|
|
14
30
|
sendNodeResponse2,
|
|
15
31
|
installDevMiddleware2
|
|
16
|
-
} from "../index-
|
|
32
|
+
} from "../index-df6b8rqt.js";
|
|
17
33
|
|
|
18
34
|
// src/server/handler.ts
|
|
19
35
|
import {
|
|
@@ -32,31 +48,6 @@ import {
|
|
|
32
48
|
import { encodeSsrPayload, reviveError, serializeError } from "@moonsjs/core/serialize";
|
|
33
49
|
import { API_METHODS, resolveApiRoute } from "@moonsjs/core";
|
|
34
50
|
import { createRpcHandler, runWithContext, runWithRequestCache, runWithResponseCollector, takeResponseMutations } from "@moonsjs/server";
|
|
35
|
-
|
|
36
|
-
// src/payload-scan.ts
|
|
37
|
-
var SECRET_KEY_PATTERN = /(SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|AUTH|API_KEY|ACCESS_KEY|SIGNING)/i;
|
|
38
|
-
var MIN_SECRET_LENGTH = 12;
|
|
39
|
-
function scanPayloadForSecrets(payload, options = {}) {
|
|
40
|
-
const findings = [];
|
|
41
|
-
const canary = options.canary;
|
|
42
|
-
if (canary !== undefined && canary.length > 0 && payload.includes(canary)) {
|
|
43
|
-
findings.push({ kind: "canary", label: "canary" });
|
|
44
|
-
}
|
|
45
|
-
for (const [key, value] of Object.entries(options.env ?? {})) {
|
|
46
|
-
if (typeof value !== "string" || value.length < MIN_SECRET_LENGTH) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (!SECRET_KEY_PATTERN.test(key) || /PUBLIC/i.test(key)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (payload.includes(value)) {
|
|
53
|
-
findings.push({ kind: "env", label: key });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return findings;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/server/handler.ts
|
|
60
51
|
function loadDefaultRenderer() {
|
|
61
52
|
return import("@moonsjs/react/ssr").then((mod) => mod.renderToMarkup).catch(() => null);
|
|
62
53
|
}
|
|
@@ -519,78 +510,21 @@ function warnPayloadFindings(payload, scan, warned) {
|
|
|
519
510
|
}
|
|
520
511
|
}
|
|
521
512
|
// src/server/node-server.ts
|
|
522
|
-
import {
|
|
523
|
-
import { gzipSync } from "node:zlib";
|
|
513
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
524
514
|
import { createServer } from "node:http";
|
|
525
|
-
import { extname as extname2, join, resolve
|
|
515
|
+
import { extname as extname2, join, resolve } from "node:path";
|
|
526
516
|
import {
|
|
527
517
|
DEFAULT_BODY_LIMIT_BYTES,
|
|
528
|
-
MoonsError as MoonsError2,
|
|
529
518
|
trailingSlashRedirect as trailingSlashRedirect2
|
|
530
519
|
} from "@moonsjs/core";
|
|
531
520
|
|
|
532
|
-
// src/server/isr.ts
|
|
533
|
-
import { renameSync, writeFileSync } from "node:fs";
|
|
534
|
-
function shouldRevalidate(input) {
|
|
535
|
-
const now = input.now ?? Date.now();
|
|
536
|
-
return now - input.mtimeMs >= input.revalidateSeconds * 1000;
|
|
537
|
-
}
|
|
538
|
-
var inflight = new Map;
|
|
539
|
-
function revalidatePrerendered(options) {
|
|
540
|
-
const existing = inflight.get(options.pathname);
|
|
541
|
-
if (existing !== undefined) {
|
|
542
|
-
return existing;
|
|
543
|
-
}
|
|
544
|
-
const task = runRevalidate(options).finally(() => {
|
|
545
|
-
inflight.delete(options.pathname);
|
|
546
|
-
});
|
|
547
|
-
inflight.set(options.pathname, task);
|
|
548
|
-
return task;
|
|
549
|
-
}
|
|
550
|
-
async function runRevalidate(options) {
|
|
551
|
-
const log = options.log ?? ((message) => console.log(message));
|
|
552
|
-
try {
|
|
553
|
-
const response = await options.handler(new Request(`http://localhost${options.pathname}`, {
|
|
554
|
-
method: "GET",
|
|
555
|
-
headers: { "x-moons-prerender": "1" }
|
|
556
|
-
}));
|
|
557
|
-
if (response.status !== 200) {
|
|
558
|
-
log(`[moons] isr: skipped ${options.pathname} (status ${response.status})`);
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
const html = await response.text();
|
|
562
|
-
const findings = scanPayloadForSecrets(html, {
|
|
563
|
-
...options.canary === undefined ? {} : { canary: options.canary },
|
|
564
|
-
env: options.env ?? process.env
|
|
565
|
-
});
|
|
566
|
-
if (findings.length > 0) {
|
|
567
|
-
const labels = findings.map((finding) => finding.kind === "canary" ? "canary" : `env(${finding.label})`).join(", ");
|
|
568
|
-
log(`[moons] isr: NOT writing ${options.pathname} — contains ${labels}`);
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
if (options.storage !== undefined) {
|
|
572
|
-
await options.storage.write(options.pathname, new TextEncoder().encode(html));
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (options.file === undefined) {
|
|
576
|
-
log(`[moons] isr: skipped ${options.pathname} — no destination file/storage`);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
const tmp = `${options.file}.tmp-${process.pid}`;
|
|
580
|
-
writeFileSync(tmp, html);
|
|
581
|
-
renameSync(tmp, options.file);
|
|
582
|
-
} catch (error) {
|
|
583
|
-
log(`[moons] isr: failed to regenerate ${options.pathname}: ${String(error)}`);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
521
|
// src/server/storage.ts
|
|
588
|
-
import { mkdirSync, readFileSync, renameSync
|
|
522
|
+
import { mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
589
523
|
import { dirname, extname } from "node:path";
|
|
590
524
|
function createFileStorage(clientDir) {
|
|
591
525
|
return {
|
|
592
526
|
async read(pathname) {
|
|
593
|
-
const file =
|
|
527
|
+
const file = resolveStaticFile2(clientDir, pathname);
|
|
594
528
|
if (file === null) {
|
|
595
529
|
return null;
|
|
596
530
|
}
|
|
@@ -604,8 +538,8 @@ function createFileStorage(clientDir) {
|
|
|
604
538
|
}
|
|
605
539
|
mkdirSync(dirname(target), { recursive: true });
|
|
606
540
|
const tmp = `${target}.tmp-${process.pid}`;
|
|
607
|
-
|
|
608
|
-
|
|
541
|
+
writeFileSync(tmp, body);
|
|
542
|
+
renameSync(tmp, target);
|
|
609
543
|
}
|
|
610
544
|
};
|
|
611
545
|
}
|
|
@@ -719,27 +653,6 @@ function installGracefulShutdown(server, options = {}) {
|
|
|
719
653
|
}
|
|
720
654
|
|
|
721
655
|
// src/server/node-server.ts
|
|
722
|
-
var MIME_TYPES = {
|
|
723
|
-
".js": "text/javascript; charset=utf-8",
|
|
724
|
-
".mjs": "text/javascript; charset=utf-8",
|
|
725
|
-
".css": "text/css; charset=utf-8",
|
|
726
|
-
".html": "text/html; charset=utf-8",
|
|
727
|
-
".json": "application/json; charset=utf-8",
|
|
728
|
-
".map": "application/json; charset=utf-8",
|
|
729
|
-
".ico": "image/x-icon",
|
|
730
|
-
".png": "image/png",
|
|
731
|
-
".jpg": "image/jpeg",
|
|
732
|
-
".jpeg": "image/jpeg",
|
|
733
|
-
".gif": "image/gif",
|
|
734
|
-
".svg": "image/svg+xml",
|
|
735
|
-
".webp": "image/webp",
|
|
736
|
-
".avif": "image/avif",
|
|
737
|
-
".woff": "font/woff",
|
|
738
|
-
".woff2": "font/woff2",
|
|
739
|
-
".ttf": "font/ttf",
|
|
740
|
-
".txt": "text/plain; charset=utf-8",
|
|
741
|
-
".wasm": "application/wasm"
|
|
742
|
-
};
|
|
743
656
|
function startNodeServer(handler, options = {}) {
|
|
744
657
|
const clientDir = resolve(options.clientDir ?? join(process.cwd(), "dist/client"));
|
|
745
658
|
const port = options.port ?? Number(process.env["PORT"] ?? 3000);
|
|
@@ -800,13 +713,13 @@ function startNodeServer(handler, options = {}) {
|
|
|
800
713
|
});
|
|
801
714
|
res.statusCode = 200;
|
|
802
715
|
res.setHeader("content-type", contentTypeForPathname(url.pathname));
|
|
803
|
-
applyStaticHeaders(res, url.pathname, options.headers);
|
|
716
|
+
applyStaticHeaders({ has: (name) => res.hasHeader(name), set: (name, value) => res.setHeader(name, value) }, url.pathname, options.headers);
|
|
804
717
|
res.end(req.method === "HEAD" ? undefined : Buffer.from(entry.body));
|
|
805
718
|
return;
|
|
806
719
|
}
|
|
807
720
|
}
|
|
808
721
|
{
|
|
809
|
-
const staticFile =
|
|
722
|
+
const staticFile = resolveStaticFile2(clientDir, url.pathname);
|
|
810
723
|
if (staticFile !== null) {
|
|
811
724
|
scheduleRevalidation({
|
|
812
725
|
pathname: url.pathname,
|
|
@@ -822,7 +735,7 @@ function startNodeServer(handler, options = {}) {
|
|
|
822
735
|
const stat = statSync2(staticFile);
|
|
823
736
|
res.statusCode = 200;
|
|
824
737
|
res.setHeader("content-type", MIME_TYPES[extname2(staticFile).toLowerCase()] ?? "application/octet-stream");
|
|
825
|
-
applyStaticHeaders(res, url.pathname, options.headers);
|
|
738
|
+
applyStaticHeaders({ has: (name) => res.hasHeader(name), set: (name, value) => res.setHeader(name, value) }, url.pathname, options.headers);
|
|
826
739
|
const etag = `W/"${stat.size}-${Math.floor(stat.mtimeMs)}"`;
|
|
827
740
|
res.setHeader("etag", etag);
|
|
828
741
|
const ifNoneMatch = req.headers["if-none-match"];
|
|
@@ -832,7 +745,9 @@ function startNodeServer(handler, options = {}) {
|
|
|
832
745
|
return;
|
|
833
746
|
}
|
|
834
747
|
let payload = body;
|
|
835
|
-
|
|
748
|
+
const encodings = req.headers["accept-encoding"];
|
|
749
|
+
const acceptsGzip = acceptsGzipHeader(Array.isArray(encodings) ? encodings[0] ?? null : encodings ?? null);
|
|
750
|
+
if (req.method === "GET" && COMPRESSIBLE_FILE.test(staticFile) && body.byteLength >= GZIP_MIN_BYTES && acceptsGzip) {
|
|
836
751
|
payload = gzipCached(staticFile, stat.mtimeMs, body);
|
|
837
752
|
res.setHeader("content-encoding", "gzip");
|
|
838
753
|
appendVary(res, "accept-encoding");
|
|
@@ -899,47 +814,6 @@ function startNodeServer(handler, options = {}) {
|
|
|
899
814
|
}
|
|
900
815
|
return server;
|
|
901
816
|
}
|
|
902
|
-
function applyStaticHeaders(res, pathname, configured) {
|
|
903
|
-
for (const [name, value] of Object.entries(configured ?? {})) {
|
|
904
|
-
if (!res.hasHeader(name)) {
|
|
905
|
-
res.setHeader(name, value);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
for (const [name, value] of Object.entries(DEFAULT_SECURITY_HEADERS)) {
|
|
909
|
-
if (!res.hasHeader(name)) {
|
|
910
|
-
res.setHeader(name, value);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
if (pathname.startsWith("/assets/") && !res.hasHeader("cache-control")) {
|
|
914
|
-
res.setHeader("cache-control", "public, max-age=31536000, immutable");
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
var COMPRESSIBLE_FILE = /\.(js|mjs|css|html|json|svg|txt|map|xml)$/i;
|
|
918
|
-
var GZIP_MIN_BYTES = 1024;
|
|
919
|
-
var GZIP_CACHE_MAX = 64;
|
|
920
|
-
var gzipCache = new Map;
|
|
921
|
-
function acceptsGzip(req) {
|
|
922
|
-
const header = req.headers["accept-encoding"];
|
|
923
|
-
if (typeof header !== "string") {
|
|
924
|
-
return false;
|
|
925
|
-
}
|
|
926
|
-
return header.toLowerCase().split(",").map((part) => part.trim()).some((part) => part === "gzip" || part === "*" || part.startsWith("gzip;"));
|
|
927
|
-
}
|
|
928
|
-
function gzipCached(file, mtimeMs, body) {
|
|
929
|
-
const cached = gzipCache.get(file);
|
|
930
|
-
if (cached !== undefined && cached.mtimeMs === mtimeMs) {
|
|
931
|
-
return cached.gzip;
|
|
932
|
-
}
|
|
933
|
-
const gzip = gzipSync(body);
|
|
934
|
-
if (gzipCache.size >= GZIP_CACHE_MAX) {
|
|
935
|
-
const oldest = gzipCache.keys().next().value;
|
|
936
|
-
if (oldest !== undefined) {
|
|
937
|
-
gzipCache.delete(oldest);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
gzipCache.set(file, { mtimeMs, gzip });
|
|
941
|
-
return gzip;
|
|
942
|
-
}
|
|
943
817
|
function appendVary(res, value) {
|
|
944
818
|
const existing = res.getHeader("vary");
|
|
945
819
|
if (typeof existing === "string" && existing.length > 0) {
|
|
@@ -955,126 +829,292 @@ function respondBadRequest(res) {
|
|
|
955
829
|
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
956
830
|
res.end("Bad Request");
|
|
957
831
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
832
|
+
// src/server/bun-server.ts
|
|
833
|
+
import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
834
|
+
import { extname as extname3, join as join2, resolve as resolve2 } from "node:path";
|
|
835
|
+
import { CLIENT_IP_HEADER, DEFAULT_BODY_LIMIT_BYTES as DEFAULT_BODY_LIMIT_BYTES2, trailingSlashRedirect as trailingSlashRedirect3 } from "@moonsjs/core";
|
|
836
|
+
var textResponse = (status, text) => new Response(text, { status, headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
837
|
+
function appendVaryHeader(headers, value) {
|
|
838
|
+
const existing = headers.get("vary");
|
|
839
|
+
if (existing !== null && existing.length > 0) {
|
|
840
|
+
if (!existing.toLowerCase().includes(value)) {
|
|
841
|
+
headers.set("vary", `${existing}, ${value}`);
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
headers.set("vary", value);
|
|
969
846
|
}
|
|
970
|
-
function
|
|
971
|
-
|
|
972
|
-
|
|
847
|
+
function withDefaultSecurityHeaders(response) {
|
|
848
|
+
let missing = false;
|
|
849
|
+
for (const name of Object.keys(DEFAULT_SECURITY_HEADERS)) {
|
|
850
|
+
if (!response.headers.has(name)) {
|
|
851
|
+
missing = true;
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (!missing) {
|
|
856
|
+
return response;
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
for (const [name, value] of Object.entries(DEFAULT_SECURITY_HEADERS)) {
|
|
860
|
+
if (!response.headers.has(name)) {
|
|
861
|
+
response.headers.set(name, value);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return response;
|
|
865
|
+
} catch {
|
|
866
|
+
const headers = new Headers(response.headers);
|
|
867
|
+
for (const [name, value] of Object.entries(DEFAULT_SECURITY_HEADERS)) {
|
|
868
|
+
if (!headers.has(name)) {
|
|
869
|
+
headers.set(name, value);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
return new Response(response.body, {
|
|
873
|
+
status: response.status,
|
|
874
|
+
statusText: response.statusText,
|
|
875
|
+
headers
|
|
876
|
+
});
|
|
877
|
+
}
|
|
973
878
|
}
|
|
974
|
-
function
|
|
975
|
-
let
|
|
879
|
+
function resolveBunUrl(request, trustProxy) {
|
|
880
|
+
let url;
|
|
976
881
|
try {
|
|
977
|
-
|
|
882
|
+
url = new URL(request.url);
|
|
978
883
|
} catch {
|
|
979
|
-
|
|
884
|
+
throw new InvalidRequestError(`invalid request URL: ${request.url}`);
|
|
980
885
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
return exact;
|
|
886
|
+
if (!trustProxy) {
|
|
887
|
+
return url;
|
|
984
888
|
}
|
|
985
|
-
const
|
|
986
|
-
const
|
|
987
|
-
if (
|
|
988
|
-
|
|
989
|
-
return safeWithin(clientDir, `./${target}`);
|
|
889
|
+
const proto = forwardedProtocol(request.headers.get("x-forwarded-proto") ?? undefined);
|
|
890
|
+
const host = forwardedHost(request.headers.get("x-forwarded-host") ?? undefined);
|
|
891
|
+
if (proto === null && host === null) {
|
|
892
|
+
return url;
|
|
990
893
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
894
|
+
const effectiveProto = proto ?? (url.protocol === "https:" ? "https" : "http");
|
|
895
|
+
const effectiveHost = host ?? url.host;
|
|
896
|
+
try {
|
|
897
|
+
return new URL(`${effectiveProto}://${effectiveHost}${url.pathname}${url.search}${url.hash}`);
|
|
898
|
+
} catch {
|
|
899
|
+
throw new InvalidRequestError(`invalid forwarded host: ${JSON.stringify(effectiveHost)}`);
|
|
997
900
|
}
|
|
998
|
-
return candidate;
|
|
999
901
|
}
|
|
1000
|
-
function
|
|
1001
|
-
|
|
902
|
+
async function toBoundedRequest(request, url, clientIp, maxBodyBytes) {
|
|
903
|
+
const headers = new Headers(request.headers);
|
|
904
|
+
headers.delete("content-length");
|
|
905
|
+
if (clientIp !== null) {
|
|
906
|
+
headers.set(CLIENT_IP_HEADER, clientIp);
|
|
907
|
+
}
|
|
908
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
909
|
+
return new Request(url, { method: request.method, headers });
|
|
910
|
+
}
|
|
911
|
+
const body = await readBodyWithLimit(request.body, maxBodyBytes);
|
|
912
|
+
return new Request(url, { method: request.method, headers, body });
|
|
1002
913
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
if (prerenderManifestCache !== null && prerenderManifestCache.file === file && prerenderManifestCache.mtimeMs === mtimeMs) {
|
|
1010
|
-
return prerenderManifestCache.info;
|
|
914
|
+
function installBunShutdown(server, options) {
|
|
915
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
916
|
+
let shuttingDown = false;
|
|
917
|
+
return (signal) => {
|
|
918
|
+
if (shuttingDown) {
|
|
919
|
+
return;
|
|
1011
920
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
921
|
+
shuttingDown = true;
|
|
922
|
+
console.log(`[moons] received ${signal} — no longer accepting connections, draining...`);
|
|
923
|
+
const timer = setTimeout(() => {
|
|
924
|
+
console.log(`[moons] drain exceeded ${timeoutMs}ms — forcing exit`);
|
|
925
|
+
server.stop(true).catch(() => {});
|
|
926
|
+
process.exit(1);
|
|
927
|
+
}, timeoutMs);
|
|
928
|
+
timer.unref?.();
|
|
929
|
+
(async () => {
|
|
930
|
+
try {
|
|
931
|
+
await server.stop(false);
|
|
932
|
+
} catch (error) {
|
|
933
|
+
clearTimeout(timer);
|
|
934
|
+
console.log(`[moons] server close failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
935
|
+
process.exit(1);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (options.plugins !== undefined) {
|
|
939
|
+
timer.ref?.();
|
|
940
|
+
try {
|
|
941
|
+
await options.plugins.shutdown();
|
|
942
|
+
} catch (error) {
|
|
943
|
+
console.log(`[moons] onShutdown failed: ${String(error)}`);
|
|
1019
944
|
}
|
|
1020
945
|
}
|
|
946
|
+
clearTimeout(timer);
|
|
947
|
+
console.log("[moons] shutdown complete (full drain)");
|
|
948
|
+
process.exit(0);
|
|
949
|
+
})();
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
function startBunServer(handler, options = {}) {
|
|
953
|
+
if (typeof Bun === "undefined") {
|
|
954
|
+
throw new Error("[moons] startBunServer requires the Bun runtime");
|
|
955
|
+
}
|
|
956
|
+
const clientDir = resolve2(options.clientDir ?? join2(process.cwd(), "dist/client"));
|
|
957
|
+
const port = options.port ?? Number(process.env["PORT"] ?? 3000);
|
|
958
|
+
const trustProxy = options.trustProxy === true;
|
|
959
|
+
const accessLogger = options.logging?.accessLog === true ? createLogger({
|
|
960
|
+
json: options.logging.json !== false,
|
|
961
|
+
redact: options.logging.redact ?? []
|
|
962
|
+
}) : null;
|
|
963
|
+
const serveStatic = async (request, url) => {
|
|
964
|
+
if (options.storage !== undefined) {
|
|
965
|
+
const entry = await options.storage.read(url.pathname);
|
|
966
|
+
if (entry !== null) {
|
|
967
|
+
scheduleRevalidation({
|
|
968
|
+
pathname: url.pathname,
|
|
969
|
+
mtimeMs: entry.mtimeMs,
|
|
970
|
+
storage: options.storage,
|
|
971
|
+
handler,
|
|
972
|
+
options,
|
|
973
|
+
clientDir,
|
|
974
|
+
logger: accessLogger
|
|
975
|
+
});
|
|
976
|
+
const headers = new Headers;
|
|
977
|
+
headers.set("content-type", contentTypeForPathname(url.pathname));
|
|
978
|
+
applyStaticHeaders(headers, url.pathname, options.headers);
|
|
979
|
+
return new Response(request.method === "HEAD" ? null : entry.body, { status: 200, headers });
|
|
980
|
+
}
|
|
1021
981
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
982
|
+
const staticFile = resolveStaticFile2(clientDir, url.pathname);
|
|
983
|
+
if (staticFile === null) {
|
|
984
|
+
return null;
|
|
985
|
+
}
|
|
986
|
+
const stat = statSync3(staticFile);
|
|
987
|
+
scheduleRevalidation({
|
|
988
|
+
pathname: url.pathname,
|
|
989
|
+
mtimeMs: stat.mtimeMs,
|
|
990
|
+
file: staticFile,
|
|
991
|
+
...options.storage === undefined ? {} : { storage: options.storage },
|
|
992
|
+
handler,
|
|
993
|
+
options,
|
|
994
|
+
clientDir,
|
|
995
|
+
logger: accessLogger
|
|
996
|
+
});
|
|
997
|
+
const headers = new Headers;
|
|
998
|
+
headers.set("content-type", MIME_TYPES[extname3(staticFile).toLowerCase()] ?? "application/octet-stream");
|
|
999
|
+
applyStaticHeaders(headers, url.pathname, options.headers);
|
|
1000
|
+
const etag = `W/"${stat.size}-${Math.floor(stat.mtimeMs)}"`;
|
|
1001
|
+
headers.set("etag", etag);
|
|
1002
|
+
const ifNoneMatch = request.headers.get("if-none-match");
|
|
1003
|
+
if (ifNoneMatch !== null && ifNoneMatch.split(",").some((value) => value.trim() === etag)) {
|
|
1004
|
+
return new Response(null, { status: 304, headers });
|
|
1005
|
+
}
|
|
1006
|
+
let payload = request.method === "HEAD" ? null : readFileSync3(staticFile);
|
|
1007
|
+
if (payload !== null && COMPRESSIBLE_FILE.test(staticFile) && payload.byteLength >= GZIP_MIN_BYTES && acceptsGzipHeader(request.headers.get("accept-encoding"))) {
|
|
1008
|
+
payload = gzipCached(staticFile, stat.mtimeMs, Buffer.from(payload));
|
|
1009
|
+
headers.set("content-encoding", "gzip");
|
|
1010
|
+
appendVaryHeader(headers, "accept-encoding");
|
|
1011
|
+
}
|
|
1012
|
+
return new Response(payload, { status: 200, headers });
|
|
1013
|
+
};
|
|
1014
|
+
const server = Bun.serve({
|
|
1015
|
+
port,
|
|
1016
|
+
fetch: async (request, bunServer) => {
|
|
1017
|
+
const requestStartedAt = performance.now();
|
|
1018
|
+
let url;
|
|
1019
|
+
try {
|
|
1020
|
+
url = resolveBunUrl(request, trustProxy);
|
|
1021
|
+
} catch (error) {
|
|
1022
|
+
if (error instanceof InvalidRequestError) {
|
|
1023
|
+
console.warn(`[moons] ${error.message} — 400`);
|
|
1024
|
+
return textResponse(400, "Bad Request");
|
|
1026
1025
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1026
|
+
throw error;
|
|
1027
|
+
}
|
|
1028
|
+
let clientIp = null;
|
|
1029
|
+
const finish = (response) => {
|
|
1030
|
+
if (accessLogger !== null) {
|
|
1031
|
+
accessLogger({
|
|
1032
|
+
msg: "request",
|
|
1033
|
+
method: request.method,
|
|
1034
|
+
path: url.pathname + url.search,
|
|
1035
|
+
status: response.status,
|
|
1036
|
+
durationMs: Math.round(performance.now() - requestStartedAt),
|
|
1037
|
+
...clientIp === null ? {} : { ip: clientIp }
|
|
1038
|
+
});
|
|
1032
1039
|
}
|
|
1040
|
+
return response;
|
|
1041
|
+
};
|
|
1042
|
+
try {
|
|
1043
|
+
if (options.trailingSlash !== undefined) {
|
|
1044
|
+
const canonical = trailingSlashRedirect3(url.pathname, options.trailingSlash);
|
|
1045
|
+
if (canonical !== null) {
|
|
1046
|
+
url.pathname = canonical;
|
|
1047
|
+
return new Response(null, { status: 308, headers: { location: url.toString() } });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (request.method === "GET" || request.method === "HEAD") {
|
|
1051
|
+
const staticResponse = await serveStatic(request, url);
|
|
1052
|
+
if (staticResponse !== null) {
|
|
1053
|
+
return finish(staticResponse);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const isRpc = url.pathname.startsWith("/_moons/fn/");
|
|
1057
|
+
const rpcLimit = options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES2;
|
|
1058
|
+
if (isRpc) {
|
|
1059
|
+
const contentLength = Number(request.headers.get("content-length") ?? "0");
|
|
1060
|
+
if (Number.isFinite(contentLength) && contentLength > rpcLimit) {
|
|
1061
|
+
return finish(textResponse(413, rpcBodyTooLargeText(rpcLimit, contentLength)));
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const remoteAddress = bunServer.requestIP(request)?.address ?? null;
|
|
1065
|
+
clientIp = resolveClientIpValues(request.headers.get("x-forwarded-for") ?? undefined, remoteAddress, trustProxy);
|
|
1066
|
+
const needsRewrite = clientIp !== null || url.toString() !== request.url;
|
|
1067
|
+
const webRequest = (request.method === "GET" || request.method === "HEAD") && !needsRewrite ? request : await toBoundedRequest(request, url, clientIp, isRpc ? rpcLimit : MAX_TRANSPORT_BODY_BYTES);
|
|
1068
|
+
const response = await handler(webRequest);
|
|
1069
|
+
return finish(withDefaultSecurityHeaders(response));
|
|
1070
|
+
} catch (error) {
|
|
1071
|
+
if (error instanceof BodyLimitError) {
|
|
1072
|
+
const isRpc = url.pathname.startsWith("/_moons/fn/");
|
|
1073
|
+
return finish(textResponse(413, isRpc ? rpcBodyTooLargeText(error.limit, null) : "Payload Too Large"));
|
|
1074
|
+
}
|
|
1075
|
+
console.error("[moons] server error:", error);
|
|
1076
|
+
return finish(textResponse(500, "Internal Server Error"));
|
|
1033
1077
|
}
|
|
1034
1078
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1079
|
+
});
|
|
1080
|
+
const listenAfterStart = () => {
|
|
1081
|
+
console.log(`[moons] server listening on http://localhost:${server.port}`);
|
|
1082
|
+
options.plugins?.ready();
|
|
1083
|
+
};
|
|
1084
|
+
if (options.plugins !== undefined) {
|
|
1085
|
+
options.plugins.start().then(listenAfterStart);
|
|
1086
|
+
} else {
|
|
1087
|
+
listenAfterStart();
|
|
1040
1088
|
}
|
|
1089
|
+
if (options.handleSignals !== false) {
|
|
1090
|
+
const shutdown = installBunShutdown(server, {
|
|
1091
|
+
...options.shutdownTimeoutMs === undefined ? {} : { timeoutMs: options.shutdownTimeoutMs },
|
|
1092
|
+
plugins: options.plugins
|
|
1093
|
+
});
|
|
1094
|
+
process.once("SIGTERM", () => shutdown("SIGTERM"));
|
|
1095
|
+
process.once("SIGINT", () => shutdown("SIGINT"));
|
|
1096
|
+
}
|
|
1097
|
+
return server;
|
|
1041
1098
|
}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1046
|
-
|
|
1099
|
+
// src/server/server-select.ts
|
|
1100
|
+
function startServer(handler, options = {}) {
|
|
1101
|
+
const forced = process.env["MOONS_SERVER"];
|
|
1102
|
+
const isBun = typeof Bun !== "undefined";
|
|
1103
|
+
if (forced === "node") {
|
|
1104
|
+
return startNodeServer(handler, options);
|
|
1047
1105
|
}
|
|
1048
|
-
if (
|
|
1049
|
-
|
|
1106
|
+
if (forced === "bun" && !isBun) {
|
|
1107
|
+
console.warn("[moons] MOONS_SERVER=bun requires the Bun runtime — falling back to node");
|
|
1108
|
+
return startNodeServer(handler, options);
|
|
1050
1109
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
...input.file === undefined ? {} : { file: input.file },
|
|
1056
|
-
...input.storage === undefined ? {} : { storage: input.storage },
|
|
1057
|
-
handler: input.handler,
|
|
1058
|
-
...input.options.canary === undefined ? {} : { canary: input.options.canary },
|
|
1059
|
-
env: process.env
|
|
1060
|
-
}).then(() => {
|
|
1061
|
-
logger?.({
|
|
1062
|
-
msg: "isr:regenerated",
|
|
1063
|
-
path: normalized,
|
|
1064
|
-
durationMs: Math.round(performance.now() - regeneratedAt)
|
|
1065
|
-
});
|
|
1066
|
-
}).catch((error) => {
|
|
1067
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1068
|
-
if (logger !== null) {
|
|
1069
|
-
logger({ msg: "isr:revalidate-failed", level: "warn", path: normalized, error: message });
|
|
1070
|
-
} else {
|
|
1071
|
-
console.error(`[moons] isr regeneration failed (${normalized}): ${message}`);
|
|
1072
|
-
}
|
|
1073
|
-
});
|
|
1110
|
+
if (forced !== undefined && forced !== "bun" && forced !== "node") {
|
|
1111
|
+
console.warn(`[moons] unknown MOONS_SERVER="${forced}" — using ${isBun ? "bun" : "node"}`);
|
|
1112
|
+
}
|
|
1113
|
+
return isBun ? startBunServer(handler, options) : startNodeServer(handler, options);
|
|
1074
1114
|
}
|
|
1075
1115
|
// src/server/test-server.ts
|
|
1076
|
-
import { existsSync
|
|
1077
|
-
import { join as
|
|
1116
|
+
import { existsSync } from "node:fs";
|
|
1117
|
+
import { join as join3, resolve as resolve3 } from "node:path";
|
|
1078
1118
|
import { pathToFileURL } from "node:url";
|
|
1079
1119
|
async function loadRenderer(candidates) {
|
|
1080
1120
|
for (const root of candidates) {
|
|
@@ -1083,8 +1123,8 @@ async function loadRenderer(candidates) {
|
|
|
1083
1123
|
"node_modules/@moonsjs/react/dist/ssr.js",
|
|
1084
1124
|
"node_modules/@moonsjs/react/dist/ssr.mjs"
|
|
1085
1125
|
]) {
|
|
1086
|
-
const file =
|
|
1087
|
-
if (!
|
|
1126
|
+
const file = join3(root, relative);
|
|
1127
|
+
if (!existsSync(file)) {
|
|
1088
1128
|
continue;
|
|
1089
1129
|
}
|
|
1090
1130
|
const mod = await import(pathToFileURL(file).href);
|
|
@@ -1107,7 +1147,7 @@ function createTestServer(options, baseUrl = "http://localhost") {
|
|
|
1107
1147
|
...options.loadApi === undefined ? {} : { loadApi: options.loadApi },
|
|
1108
1148
|
...options.apiMiddlewares === undefined ? {} : { apiMiddlewares: options.apiMiddlewares },
|
|
1109
1149
|
...options.loadApiMiddleware === undefined ? {} : { loadApiMiddleware: options.loadApiMiddleware },
|
|
1110
|
-
renderer: () => loadRenderer([process.cwd(),
|
|
1150
|
+
renderer: () => loadRenderer([process.cwd(), resolve3(options.routesDir, "..", "..")]),
|
|
1111
1151
|
shell
|
|
1112
1152
|
});
|
|
1113
1153
|
return {
|
|
@@ -1172,9 +1212,9 @@ function createPluginRuntime(options) {
|
|
|
1172
1212
|
};
|
|
1173
1213
|
}
|
|
1174
1214
|
// src/server/prerender.ts
|
|
1175
|
-
import { MoonsError as
|
|
1176
|
-
import { mkdirSync as mkdirSync2, writeFileSync as
|
|
1177
|
-
import { dirname as dirname2, join as
|
|
1215
|
+
import { MoonsError as MoonsError2 } from "@moonsjs/core";
|
|
1216
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1217
|
+
import { dirname as dirname2, join as join4, resolve as resolve4, sep } from "node:path";
|
|
1178
1218
|
var PRERENDER_MANIFEST_FILE = "_moons_prerender.json";
|
|
1179
1219
|
var NOT_FOUND_SENTINEL = "/_moons/404";
|
|
1180
1220
|
var NOT_FOUND_FILE = "404.html";
|
|
@@ -1196,7 +1236,7 @@ async function runPrerender(options) {
|
|
|
1196
1236
|
const revalidate = module.revalidate;
|
|
1197
1237
|
if (revalidate !== undefined) {
|
|
1198
1238
|
if (typeof revalidate !== "number" || !Number.isFinite(revalidate) || revalidate < 0 || prerender !== true) {
|
|
1199
|
-
throw new
|
|
1239
|
+
throw new MoonsError2({
|
|
1200
1240
|
code: "E_ISR_INVALID",
|
|
1201
1241
|
file: route.file,
|
|
1202
1242
|
message: "`revalidate` is invalid or used without `prerender = true`",
|
|
@@ -1235,7 +1275,7 @@ async function runPrerender(options) {
|
|
|
1235
1275
|
assertNoLeak(html, pathname, route.file, options, env);
|
|
1236
1276
|
const file = outputFile(options.clientDir, pathname);
|
|
1237
1277
|
mkdirSync2(dirname2(file), { recursive: true });
|
|
1238
|
-
|
|
1278
|
+
writeFileSync2(file, html);
|
|
1239
1279
|
entries.push(typeof revalidate === "number" && revalidate >= 0 ? { path: pathname, revalidate } : { path: pathname });
|
|
1240
1280
|
written += 1;
|
|
1241
1281
|
}
|
|
@@ -1243,10 +1283,10 @@ async function runPrerender(options) {
|
|
|
1243
1283
|
const notFound = await renderStaticNotFound(options, env, log);
|
|
1244
1284
|
let wrote404 = false;
|
|
1245
1285
|
if (notFound !== null) {
|
|
1246
|
-
|
|
1286
|
+
writeFileSync2(join4(options.clientDir, NOT_FOUND_FILE), notFound);
|
|
1247
1287
|
wrote404 = true;
|
|
1248
1288
|
}
|
|
1249
|
-
|
|
1289
|
+
writeFileSync2(join4(options.clientDir, PRERENDER_MANIFEST_FILE), `${JSON.stringify({ version: 2, entries }, null, 2)}
|
|
1250
1290
|
`);
|
|
1251
1291
|
const emitted = emitStaticFiles(options.clientDir, options.staticConfig, entries);
|
|
1252
1292
|
const extras = [...wrote404 ? [NOT_FOUND_FILE] : [], ...emitted];
|
|
@@ -1261,7 +1301,7 @@ function emitStaticFiles(clientDir, staticConfig, entries) {
|
|
|
1261
1301
|
const headers = Object.entries(staticConfig.headers ?? {});
|
|
1262
1302
|
if (headers.length > 0) {
|
|
1263
1303
|
const lines = ["/*", ...headers.map(([name, value]) => ` ${name}: ${value}`)];
|
|
1264
|
-
|
|
1304
|
+
writeFileSync2(join4(clientDir, "_headers"), `${lines.join(`
|
|
1265
1305
|
`)}
|
|
1266
1306
|
`);
|
|
1267
1307
|
emitted.push("_headers");
|
|
@@ -1269,7 +1309,7 @@ function emitStaticFiles(clientDir, staticConfig, entries) {
|
|
|
1269
1309
|
const redirects = staticConfig.redirects ?? [];
|
|
1270
1310
|
if (redirects.length > 0) {
|
|
1271
1311
|
const lines = redirects.map((entry) => `${entry.from} ${entry.to} ${entry.status ?? 301}`);
|
|
1272
|
-
|
|
1312
|
+
writeFileSync2(join4(clientDir, "_redirects"), `${lines.join(`
|
|
1273
1313
|
`)}
|
|
1274
1314
|
`);
|
|
1275
1315
|
emitted.push("_redirects");
|
|
@@ -1287,7 +1327,7 @@ function emitStaticFiles(clientDir, staticConfig, entries) {
|
|
|
1287
1327
|
""
|
|
1288
1328
|
].join(`
|
|
1289
1329
|
`);
|
|
1290
|
-
|
|
1330
|
+
writeFileSync2(join4(clientDir, "sitemap.xml"), xml);
|
|
1291
1331
|
emitted.push("sitemap.xml");
|
|
1292
1332
|
}
|
|
1293
1333
|
const robots = staticConfig.robots;
|
|
@@ -1299,7 +1339,7 @@ function emitStaticFiles(clientDir, staticConfig, entries) {
|
|
|
1299
1339
|
if ((robots.sitemap ?? true) && sitemap !== undefined) {
|
|
1300
1340
|
lines.push("", `Sitemap: ${sitemap.site.replace(/\/$/, "")}/sitemap.xml`);
|
|
1301
1341
|
}
|
|
1302
|
-
|
|
1342
|
+
writeFileSync2(join4(clientDir, "robots.txt"), `${lines.join(`
|
|
1303
1343
|
`)}
|
|
1304
1344
|
`);
|
|
1305
1345
|
emitted.push("robots.txt");
|
|
@@ -1331,7 +1371,7 @@ function assertNoLeak(html, pathname, file, options, env) {
|
|
|
1331
1371
|
return;
|
|
1332
1372
|
}
|
|
1333
1373
|
const labels = findings.map((finding) => finding.kind === "canary" ? "canary" : `env(${finding.label})`).join(", ");
|
|
1334
|
-
throw new
|
|
1374
|
+
throw new MoonsError2({
|
|
1335
1375
|
code: "E_PRERENDER_LEAK",
|
|
1336
1376
|
file,
|
|
1337
1377
|
message: `prerender output \`${pathname}\` contains ${labels}`,
|
|
@@ -1353,7 +1393,7 @@ async function resolvePathnames(route, module) {
|
|
|
1353
1393
|
}
|
|
1354
1394
|
const paths = module.paths;
|
|
1355
1395
|
if (typeof paths !== "function") {
|
|
1356
|
-
throw new
|
|
1396
|
+
throw new MoonsError2({
|
|
1357
1397
|
code: "E_PRERENDER_NO_PATHS",
|
|
1358
1398
|
file: route.file,
|
|
1359
1399
|
message: "dynamic route with `prerender = true` without `export const paths`",
|
|
@@ -1423,10 +1463,10 @@ function encodeSegment(route, value) {
|
|
|
1423
1463
|
}
|
|
1424
1464
|
function outputFile(clientDir, pathname) {
|
|
1425
1465
|
const parts = pathname.split("/").filter((part) => part.length > 0);
|
|
1426
|
-
const file =
|
|
1427
|
-
const base =
|
|
1428
|
-
if (file !== base && !file.startsWith(base +
|
|
1429
|
-
throw new
|
|
1466
|
+
const file = resolve4(join4(clientDir, ...parts, "index.html"));
|
|
1467
|
+
const base = resolve4(clientDir);
|
|
1468
|
+
if (file !== base && !file.startsWith(base + sep)) {
|
|
1469
|
+
throw new MoonsError2({
|
|
1430
1470
|
code: "E_PRERENDER_INVALID",
|
|
1431
1471
|
message: `prerender path escapes the client directory (\`${pathname}\`)`,
|
|
1432
1472
|
details: "The file name must stay inside `dist/client`.",
|
|
@@ -1436,7 +1476,7 @@ function outputFile(clientDir, pathname) {
|
|
|
1436
1476
|
return file;
|
|
1437
1477
|
}
|
|
1438
1478
|
function prerenderError(route, message) {
|
|
1439
|
-
return new
|
|
1479
|
+
return new MoonsError2({
|
|
1440
1480
|
code: "E_PRERENDER_INVALID",
|
|
1441
1481
|
file: route.file,
|
|
1442
1482
|
message,
|
|
@@ -1460,11 +1500,13 @@ export {
|
|
|
1460
1500
|
injectShell2 as injectShell,
|
|
1461
1501
|
installDevMiddleware2 as installDevMiddleware,
|
|
1462
1502
|
installGracefulShutdown,
|
|
1463
|
-
resolveStaticFile,
|
|
1503
|
+
resolveStaticFile2 as resolveStaticFile,
|
|
1464
1504
|
runPrerender,
|
|
1465
1505
|
sendNodeResponse2 as sendNodeResponse,
|
|
1506
|
+
startBunServer,
|
|
1466
1507
|
startNodeServer,
|
|
1508
|
+
startServer,
|
|
1467
1509
|
toWebRequest2 as toWebRequest
|
|
1468
1510
|
};
|
|
1469
1511
|
|
|
1470
|
-
//# debugId=
|
|
1512
|
+
//# debugId=BFE73455FAB6702C64756E2164756E21
|