@moonsjs/vite 0.1.17 → 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 +33 -33
- package/dist/index.js.map +4 -4
- package/dist/server/index.js +86 -86
- package/dist/server/index.js.map +9 -9
- 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 +21 -21
- package/src/server/dev.ts +1 -1
- package/src/server/handler.ts +39 -39
- 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-only.ts +7 -7
- package/src/transform.ts +76 -76
- package/dist/index-v5sbd3az.js.map +0 -15
|
@@ -5,7 +5,7 @@ import { join, resolve } from "node:path";
|
|
|
5
5
|
var ROOT_PLACEHOLDER2 = '<div id="root"></div>';
|
|
6
6
|
var DEFAULT_TEMPLATE2 = [
|
|
7
7
|
"<!doctype html>",
|
|
8
|
-
'<html lang="
|
|
8
|
+
'<html lang="en">',
|
|
9
9
|
"<head>",
|
|
10
10
|
' <meta charset="utf-8" />',
|
|
11
11
|
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
@@ -23,11 +23,11 @@ function injectShell2(template, markup, payload, meta) {
|
|
|
23
23
|
if (!template.includes(ROOT_PLACEHOLDER2)) {
|
|
24
24
|
throw new MoonsError({
|
|
25
25
|
code: "E_INTERNAL",
|
|
26
|
-
message: `shell HTML
|
|
27
|
-
details: "index.html
|
|
26
|
+
message: `shell HTML does not contain \`${ROOT_PLACEHOLDER2}\``,
|
|
27
|
+
details: "The app's index.html must contain that root element so SSR can inject markup.",
|
|
28
28
|
suggestions: [
|
|
29
|
-
`
|
|
30
|
-
"
|
|
29
|
+
`Add ${ROOT_PLACEHOLDER2} to index.html`,
|
|
30
|
+
"Or remove index.html to use the built-in shell"
|
|
31
31
|
]
|
|
32
32
|
});
|
|
33
33
|
}
|
|
@@ -81,9 +81,9 @@ function createFileShell2(options = {}) {
|
|
|
81
81
|
throw new MoonsError({
|
|
82
82
|
code: "E_INTERNAL",
|
|
83
83
|
file,
|
|
84
|
-
message: "`dist/client/index.html`
|
|
85
|
-
details: "
|
|
86
|
-
suggestions: ["
|
|
84
|
+
message: "`dist/client/index.html` not found",
|
|
85
|
+
details: "The client build must run before starting the production server.",
|
|
86
|
+
suggestions: ["Run `bun run build` first"]
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
cached = readFileSync(file, "utf8");
|
|
@@ -108,7 +108,7 @@ class InvalidRequestError extends Error {
|
|
|
108
108
|
class BodyLimitError extends Error {
|
|
109
109
|
limit;
|
|
110
110
|
constructor(limit) {
|
|
111
|
-
super(`request body
|
|
111
|
+
super(`request body exceeds the ${limit} byte limit`);
|
|
112
112
|
this.name = "BodyLimitError";
|
|
113
113
|
this.limit = limit;
|
|
114
114
|
}
|
|
@@ -122,7 +122,7 @@ function resolveRequestUrl(req, options = {}) {
|
|
|
122
122
|
try {
|
|
123
123
|
return new URL(`${proto ?? "http"}://${host}${req.url ?? "/"}`);
|
|
124
124
|
} catch {
|
|
125
|
-
throw new InvalidRequestError(`
|
|
125
|
+
throw new InvalidRequestError(`invalid Host header: ${JSON.stringify(hostHeader)}`);
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
function resolveClientIp(req, trustProxy) {
|
|
@@ -298,13 +298,13 @@ function emptyDevtoolsData() {
|
|
|
298
298
|
return { routes: [], apiRoutes: [], serverFns: [], canary: false, version: "" };
|
|
299
299
|
}
|
|
300
300
|
function renderDevtoolsHtml(data) {
|
|
301
|
-
const rows = (items) => items.length === 0 ? '<tr><td colspan="3" class="muted">(
|
|
301
|
+
const rows = (items) => items.length === 0 ? '<tr><td colspan="3" class="muted">(empty)</td></tr>' : items.map((item) => `<tr><td><code>${esc(item.path)}</code></td><td><span class="kind k-${esc(item.kind)}">${esc(item.kind)}</span></td><td class="muted">${esc(item.file)}</td></tr>`).join(`
|
|
302
302
|
`);
|
|
303
|
-
const fnRows = data.serverFns.length === 0 ? '<tr><td colspan="3" class="muted">(
|
|
303
|
+
const fnRows = data.serverFns.length === 0 ? '<tr><td colspan="3" class="muted">(no serverFn)</td></tr>' : data.serverFns.map((fn) => `<tr><td><code>${esc(fn.hash)}</code></td><td><code>${esc(fn.name)}</code></td><td class="muted">${esc(fn.file)}</td></tr>`).join(`
|
|
304
304
|
`);
|
|
305
|
-
const canary = data.canary ? '<span class="ok">
|
|
305
|
+
const canary = data.canary ? '<span class="ok">enabled</span> (canary installed)' : '<span class="muted">disabled</span> (no canary)';
|
|
306
306
|
return `<!doctype html>
|
|
307
|
-
<html lang="
|
|
307
|
+
<html lang="en">
|
|
308
308
|
<head>
|
|
309
309
|
<meta charset="utf-8">
|
|
310
310
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@@ -398,7 +398,7 @@ function installDevMiddleware2(server, options) {
|
|
|
398
398
|
} catch (error) {
|
|
399
399
|
res.statusCode = 500;
|
|
400
400
|
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
401
|
-
res.end(`[moons] devtools
|
|
401
|
+
res.end(`[moons] devtools failed to render: ${String(error)}`);
|
|
402
402
|
}
|
|
403
403
|
return;
|
|
404
404
|
}
|
|
@@ -497,7 +497,7 @@ function renderApiModule(apiRoutes, apiMiddlewares, projectRoot) {
|
|
|
497
497
|
const middlewareEntries = apiMiddlewares.map((middleware) => ` ${JSON.stringify(middleware.file)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, middleware.file))}),`).join(`
|
|
498
498
|
`);
|
|
499
499
|
return [
|
|
500
|
-
"// Generated by @moons.
|
|
500
|
+
"// Generated by @moons. Do not edit manually.",
|
|
501
501
|
`export const apiRoutes = ${JSON.stringify(apiRoutes, null, 2)};`,
|
|
502
502
|
`export const apiMiddlewares = ${JSON.stringify(apiMiddlewares, null, 2)};`,
|
|
503
503
|
"export const loadApi = {",
|
|
@@ -517,7 +517,7 @@ function renderRoutesModule(routes, routesDir, projectRoot) {
|
|
|
517
517
|
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`;
|
|
518
518
|
});
|
|
519
519
|
return [
|
|
520
|
-
"// Generated by @moons.
|
|
520
|
+
"// Generated by @moons. Do not edit manually.",
|
|
521
521
|
`export const routes = ${JSON.stringify(routes, null, 2)};`,
|
|
522
522
|
"",
|
|
523
523
|
`export const clientManifest = ${JSON.stringify(manifest, null, 2)};`,
|
|
@@ -537,4 +537,4 @@ function rootSpecifier(projectRoot, file) {
|
|
|
537
537
|
|
|
538
538
|
export { NOT_FOUND_ROUTE_KEY, renderApiModule, renderRoutesModule, rootSpecifier, VIRTUAL_MODULES, fromNullPrefixed, ROOT_PLACEHOLDER2, DEFAULT_TEMPLATE2, injectShell2, createDevShell2, createFileShell2, InvalidRequestError, BodyLimitError, resolveRequestUrl, resolveClientIp, toWebRequest2, DEFAULT_SECURITY_HEADERS, sendNodeResponse2, installDevMiddleware2 };
|
|
539
539
|
|
|
540
|
-
//# debugId=
|
|
540
|
+
//# debugId=E364F1DD2BD3F4F264756E2164756E21
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/server/shell.ts", "../src/server/node.ts", "../src/virtual-modules.ts", "../src/server/dev.ts", "../src/devtools.ts", "../src/manifest.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { MoonsError } from \"@moonsjs/core\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { ViteDevServer } from \"vite\";\n\nexport type Shell = (\n markup: string,\n payload: string,\n url: string,\n meta?: Record<string, unknown>,\n) => string | Promise<string>;\n\nexport const ROOT_PLACEHOLDER = '<div id=\"root\"></div>';\n\nexport const DEFAULT_TEMPLATE = [\n \"<!doctype html>\",\n '<html lang=\"en\">',\n \"<head>\",\n ' <meta charset=\"utf-8\" />',\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n \" <title>Moons</title>\",\n \"</head>\",\n \"<body>\",\n ` ${ROOT_PLACEHOLDER}`,\n ' <script type=\"module\" src=\"/src/client/main.tsx\"></script>',\n \"</body>\",\n \"</html>\",\n \"\",\n].join(\"\\n\");\n\n/** Inject markup SSR + payload devalue ke shell HTML (PRD §8.2, §17, Addendum B.1). */\nexport function injectShell(\n template: string,\n markup: string,\n payload: string,\n meta?: Record<string, unknown>,\n): string {\n if (!template.includes(ROOT_PLACEHOLDER)) {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n message: `shell HTML does not contain \\`${ROOT_PLACEHOLDER}\\``,\n details: \"The app's index.html must contain that root element so SSR can inject markup.\",\n suggestions: [\n `Add ${ROOT_PLACEHOLDER} to index.html`,\n \"Or remove index.html to use the built-in shell\",\n ],\n });\n }\n const dataScript = `<script id=\"__moons_data\" type=\"application/x-moons\">${payload}</script>`;\n const withRoot = template.replace(ROOT_PLACEHOLDER, `<div id=\"root\">${markup}</div>${dataScript}`);\n return applyMeta(withRoot, meta);\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\");\n}\n\n/**\n * Terapkan meta route ke `<head>` (Addendum B.1): `<title>` + description/og.\n * Tag yang sudah ada (mis. di index.html) ditimpa; yang belum ada disisipkan\n * sebelum `</head>`.\n */\nfunction applyMeta(html: string, meta: Record<string, unknown> | undefined): string {\n if (meta === undefined) {\n return html;\n }\n let out = html;\n if (typeof meta.title === \"string\") {\n const title = `<title>${escapeHtml(meta.title)}</title>`;\n out = /<title>[\\s\\S]*?<\\/title>/.test(out)\n ? out.replace(/<title>[\\s\\S]*?<\\/title>/, title)\n : out.replace(\"</head>\", ` ${title}\\n</head>`);\n }\n\n const tags: Array<{ attr: \"name\" | \"property\"; key: string; value: unknown }> = [\n { attr: \"name\", key: \"description\", value: meta.description },\n { attr: \"property\", key: \"og:title\", value: meta.title },\n { attr: \"property\", key: \"og:description\", value: meta.description },\n ];\n for (const tag of tags) {\n if (typeof tag.value !== \"string\") {\n continue;\n }\n const rendered = `<meta ${tag.attr}=\"${tag.key}\" content=\"${escapeHtml(tag.value)}\" />`;\n const pattern = new RegExp(`<meta[^>]*${tag.attr}=\"${tag.key}\"[^>]*>`);\n out = pattern.test(out)\n ? out.replace(pattern, rendered)\n : out.replace(\"</head>\", ` ${rendered}\\n</head>`);\n }\n return out;\n}\n\n/**\n * Shell untuk dev: baca `index.html` dari disk setiap request (selalu segar)\n * lalu lewatkan ke Vite `transformIndexHtml` (inject `/@vite/client`, dsb).\n */\nexport function createDevShell(server: ViteDevServer, options: { templatePath: string }): Shell {\n return async (markup, payload, url, meta) => {\n const template = existsSync(options.templatePath)\n ? readFileSync(options.templatePath, \"utf8\")\n : DEFAULT_TEMPLATE;\n const html = injectShell(template, markup, payload, meta);\n return await server.transformIndexHtml(url, html);\n };\n}\n\n/** Shell untuk produksi: baca `dist/client/index.html` sekali dari cwd. */\nexport function createFileShell(options: { clientDir?: string } = {}): Shell {\n let cached: string | null = null;\n return async (markup, payload, _url, meta) => {\n if (cached === null) {\n const clientDir = resolve(options.clientDir ?? process.cwd(), \"dist/client\");\n const file = join(clientDir, \"index.html\");\n if (!existsSync(file)) {\n throw new MoonsError({\n code: \"E_INTERNAL\",\n file,\n message: \"`dist/client/index.html` not found\",\n details: \"The client build must run before starting the production server.\",\n suggestions: [\"Run `bun run build` first\"],\n });\n }\n cached = readFileSync(file, \"utf8\");\n }\n return injectShell(cached, markup, payload, meta);\n };\n}\n",
|
|
6
|
+
"import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { CLIENT_IP_HEADER } from \"@moonsjs/core\";\n\n/** Batas transport default (Addendum J.2); RPC punya batas lebih kecil dari config. */\nexport const MAX_TRANSPORT_BODY_BYTES = 32 * 1024 * 1024;\n\n/** Request tidak valid (Host/URL malformed) → dipetakan ke 400 oleh server. */\nexport class InvalidRequestError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidRequestError\";\n }\n}\n\n/** Body melebihi batas → dipetakan ke 413; pembacaan dihentikan saat melewati. */\nexport class BodyLimitError extends Error {\n readonly limit: number;\n constructor(limit: number) {\n super(`request body exceeds the ${limit} byte limit`);\n this.name = \"BodyLimitError\";\n this.limit = limit;\n }\n}\n\nexport interface ToWebRequestOptions {\n /** Addendum J.1: percayai `x-forwarded-proto`/`x-forwarded-host` (default false). */\n trustProxy?: boolean;\n /** Batas pembacaan body (byte). Default: `MAX_TRANSPORT_BODY_BYTES`. */\n maxBodyBytes?: number;\n}\n\n/**\n * Rekonstruksi URL request (Addendum J.1). Tanpa `trustProxy`, origin/host\n * murni dari header `Host` (perilaku lama, kebal spoofing `x-forwarded-*`).\n * Malformed → `InvalidRequestError` (server memetakan ke 400).\n */\nexport function resolveRequestUrl(\n req: IncomingMessage,\n options: ToWebRequestOptions = {},\n): URL {\n const hostHeader = req.headers.host ?? \"localhost\";\n const trustProxy = options.trustProxy === true;\n const proto = trustProxy ? forwardedProtocol(req.headers[\"x-forwarded-proto\"]) : null;\n const forwardedHostValue = trustProxy ? forwardedHost(req.headers[\"x-forwarded-host\"]) : null;\n const host = forwardedHostValue ?? hostHeader;\n try {\n return new URL(`${proto ?? \"http\"}://${host}${req.url ?? \"/\"}`);\n } catch {\n throw new InvalidRequestError(`invalid Host header: ${JSON.stringify(hostHeader)}`);\n }\n}\n\n/** IP klien tepercaya (Addendum J.1): XFF (hanya bila trustProxy) → socket. */\nexport function resolveClientIp(req: IncomingMessage, trustProxy: boolean): string | null {\n if (trustProxy) {\n const forwarded = firstHeaderValue(req.headers[\"x-forwarded-for\"]);\n if (forwarded !== null && isPlausibleIp(forwarded)) {\n return normalizeIp(forwarded);\n }\n }\n const remote = req.socket?.remoteAddress ?? null;\n return remote === null ? null : normalizeIp(remote);\n}\n\n/** `::ffff:127.0.0.1` → `127.0.0.1`; buang zone id (`%eth0`). */\nfunction normalizeIp(value: string): string {\n const withoutZone = stripZoneId(value);\n return withoutZone.startsWith(\"::ffff:\") ? withoutZone.slice(7) : withoutZone;\n}\n\n/** Konversi request Node → Web `Request` (body diteruskan untuk non-GET/HEAD, §7.3 + J.1/J.2). */\nexport async function toWebRequest(\n req: IncomingMessage,\n options: ToWebRequestOptions = {},\n): Promise<Request> {\n const url = resolveRequestUrl(req, options);\n const method = req.method ?? \"GET\";\n\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined || key === \"content-length\" || key === CLIENT_IP_HEADER) {\n // content-length tidak disalin; fetch menghitung ulang dari body.\n // x-moons-client-ip selalu ditimpa nilai tepercaya di bawah.\n continue;\n }\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(key, item);\n }\n } else {\n headers.set(key, value);\n }\n }\n\n const clientIp = resolveClientIp(req, options.trustProxy === true);\n if (clientIp !== null) {\n headers.set(CLIENT_IP_HEADER, clientIp);\n }\n\n if (method === \"GET\" || method === \"HEAD\") {\n return new Request(url, { method, headers });\n }\n\n const body = await readBody(req, options.maxBodyBytes ?? MAX_TRANSPORT_BODY_BYTES);\n // View zero-copy (bukan salinan) — Buffer selalu di atas ArrayBuffer biasa.\n const view = new Uint8Array(body.buffer as ArrayBuffer, body.byteOffset, body.byteLength);\n return new Request(url, { method, headers, body: view });\n}\n\n/**\n * Baca body dengan batas tegas (Addendum J.2): begitu melewati `maxBytes`,\n * pembacaan dihentikan (pause) dan `BodyLimitError` dilempar — tidak pernah\n * seluruh body besar masuk memori, dan hanya satu salinan buffer dibuat.\n */\nexport function readBody(req: IncomingMessage, maxBytes: number): Promise<Buffer> {\n return new Promise((resolvePromise, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n let settled = false;\n\n const fail = (error: Error): void => {\n if (settled) {\n return;\n }\n settled = true;\n req.pause();\n reject(error);\n };\n\n req.on(\"data\", (chunk: Buffer) => {\n if (settled) {\n return;\n }\n size += chunk.length;\n if (size > maxBytes) {\n fail(new BodyLimitError(maxBytes));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => {\n if (!settled) {\n settled = true;\n resolvePromise(Buffer.concat(chunks));\n }\n });\n req.on(\"error\", (error) => fail(error));\n });\n}\n\n/** Addendum J.7: default aman runtime; `config.headers` tetap menang (cek hasHeader). */\nexport const DEFAULT_SECURITY_HEADERS: Record<string, string> = {\n \"x-content-type-options\": \"nosniff\",\n \"referrer-policy\": \"strict-origin-when-cross-origin\",\n};\n\n/** Tulis Web `Response` ke `ServerResponse` Node (streaming bila ada body). */\nexport async function sendNodeResponse(res: ServerResponse, response: Response): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n // `set-cookie` ditangani terpisah: forEach menggabungkan nilai ganda\n // menjadi satu string (cookie kedua hilang — D.2).\n if (key === \"set-cookie\") {\n return;\n }\n res.setHeader(key, value);\n });\n const getSetCookie = (response.headers as { getSetCookie?: () => string[] }).getSetCookie;\n if (typeof getSetCookie === \"function\") {\n const cookies = getSetCookie.call(response.headers);\n if (cookies.length > 0) {\n res.setHeader(\"set-cookie\", cookies);\n }\n } else {\n const single = response.headers.get(\"set-cookie\");\n if (single !== null) {\n res.setHeader(\"set-cookie\", single);\n }\n }\n\n for (const [name, value] of Object.entries(DEFAULT_SECURITY_HEADERS)) {\n if (!res.hasHeader(name)) {\n res.setHeader(name, value);\n }\n }\n\n if (response.body === null || res.req?.method === \"HEAD\") {\n res.end();\n return;\n }\n\n // Addendum J.2: stream langsung — tidak ada `arrayBuffer()` penuh di memori.\n await pipeline(\n Readable.fromWeb(response.body as unknown as import(\"node:stream/web\").ReadableStream),\n res,\n );\n}\n\nfunction firstHeaderValue(value: string | string[] | undefined): string | null {\n if (value === undefined) {\n return null;\n }\n const raw = Array.isArray(value) ? value[0] : value;\n if (raw === undefined) {\n return null;\n }\n const first = raw.split(\",\")[0]?.trim() ?? \"\";\n return first.length === 0 ? null : first;\n}\n\nfunction forwardedProtocol(value: string | string[] | undefined): \"http\" | \"https\" | null {\n const first = firstHeaderValue(value)?.toLowerCase() ?? null;\n if (first === \"https\") {\n return \"https\";\n }\n if (first === \"http\") {\n return \"http\";\n }\n return null;\n}\n\nfunction forwardedHost(value: string | string[] | undefined): string | null {\n const first = firstHeaderValue(value);\n if (first === null || first.length === 0 || first.length > 255) {\n return null;\n }\n // hostname[:port] — tolak karakter di luar himpunan aman (spasi, CRLF, /, @, ?).\n if (!/^[A-Za-z0-9.\\-[\\]:]+$/.test(first)) {\n return null;\n }\n return first;\n}\n\nfunction isPlausibleIp(value: string): boolean {\n return value.length <= 64 && /^[0-9a-fA-F:.]+$/.test(value);\n}\n\nfunction stripZoneId(value: string): string {\n const index = value.indexOf(\"%\");\n return index === -1 ? value : value.slice(0, index);\n}\n",
|
|
7
|
+
"/** Nama virtual module PRD §9.4. Semua di-resolve ke id `\\0`-prefix. */\nexport const VIRTUAL_MODULES = {\n routes: \"virtual:moons/routes\",\n api: \"virtual:moons/api\",\n config: \"virtual:moons/config\",\n clientEntry: \"virtual:moons/client-entry\",\n serverEntry: \"virtual:moons/server-entry\",\n} as const;\n\nexport type VirtualModuleName = (typeof VIRTUAL_MODULES)[keyof typeof VIRTUAL_MODULES];\n\nexport function isVirtualModuleId(id: string): id is VirtualModuleName {\n const clean = id.startsWith(\"\\0\") ? id.slice(1) : id;\n return (Object.values(VIRTUAL_MODULES) as string[]).includes(clean);\n}\n\nexport function toNullPrefixed(id: VirtualModuleName): `\\0${string}` {\n return `\\0${id}`;\n}\n\nexport function fromNullPrefixed(id: string): VirtualModuleName | null {\n const clean = id.startsWith(\"\\0\") ? id.slice(1) : id;\n return isVirtualModuleId(clean) ? (clean as VirtualModuleName) : null;\n}\n",
|
|
8
|
+
"import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { ViteDevServer } from \"vite\";\nimport { VIRTUAL_MODULES } from \"../virtual-modules\";\nimport type { PayloadScanOptions } from \"../payload-scan\";\nimport type { PluginRuntime } from \"./plugins\";\nimport type { Shell } from \"./shell\";\nimport { createDevShell } from \"./shell\";\nimport {\n BodyLimitError,\n InvalidRequestError,\n resolveRequestUrl,\n sendNodeResponse,\n toWebRequest,\n} from \"./node\";\nimport { DEFAULT_BODY_LIMIT_BYTES } from \"@moonsjs/core\";\nimport { emptyDevtoolsData, renderDevtoolsHtml, type DevtoolsData } from \"../devtools\";\n\ninterface DevApp {\n handler: (request: Request) => Promise<Response>;\n plugins: PluginRuntime;\n}\n\ntype DevServerEntry = {\n createDevApp: (\n shell: Shell,\n payloadScan?: import(\"../payload-scan\").PayloadScanOptions,\n ) => DevApp;\n};\n\n/**\n * Middleware SSR dev: dipasang sebelum middleware internal Vite, menangani\n * request HTML dan RPC serverFn (`/_moons/fn/*`) — modul/aset tetap Vite.\n * Handler + lifecycle plugin (Addendum B.4) diinisialisasi sekali.\n */\nexport function installDevMiddleware(\n server: ViteDevServer,\n options: {\n templatePath: string;\n payloadScan?: PayloadScanOptions;\n apiPrefix?: string;\n /** Addendum J.1: paritas dev dengan produksi (trustProxy/bodyLimitBytes). */\n trustProxy?: boolean;\n bodyLimitBytes?: number;\n /** Addendum I.4: datasource devtools overlay (dipanggil per request). */\n devtools?: () => DevtoolsData;\n },\n): void {\n let appPromise: Promise<DevApp> | null = null;\n\n const getApp = (): Promise<DevApp> => {\n if (appPromise === null) {\n appPromise = (async () => {\n const entry = (await server.ssrLoadModule(VIRTUAL_MODULES.serverEntry)) as DevServerEntry;\n const app = entry.createDevApp(createDevShell(server, options), options.payloadScan);\n await app.plugins.start();\n await app.plugins.ready();\n return app;\n })();\n }\n return appPromise;\n };\n\n server.httpServer?.once(\"close\", () => {\n void appPromise?.then((app) => app.plugins.shutdown());\n });\n\n server.middlewares.use((req, res, next) => {\n // Addendum I.4 (W4): overlay dev-only — selalu data segar per request.\n if (\n (req.method === \"GET\" || req.method === \"HEAD\") &&\n new URL(req.url ?? \"/\", \"http://localhost\").pathname === \"/__moons/devtools\"\n ) {\n try {\n const data = options.devtools?.() ?? emptyDevtoolsData();\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n res.end(req.method === \"HEAD\" ? undefined : renderDevtoolsHtml(data));\n } catch (error) {\n res.statusCode = 500;\n res.setHeader(\"content-type\", \"text/plain; charset=utf-8\");\n res.end(`[moons] devtools failed to render: ${String(error)}`);\n }\n return;\n }\n\n if (!shouldHandle(req, options.apiPrefix)) {\n next();\n return;\n }\n void handleRequest(getApp, req, res, options).catch((error: unknown) => {\n if (error instanceof InvalidRequestError) {\n res.statusCode = 400;\n res.setHeader(\"content-type\", \"text/plain; charset=utf-8\");\n res.end(\"Bad Request\");\n return;\n }\n if (error instanceof BodyLimitError) {\n res.statusCode = 413;\n res.setHeader(\"content-type\", \"text/plain; charset=utf-8\");\n res.end(\"Payload Too Large\");\n res.once(\"finish\", () => req.destroy());\n return;\n }\n next(error as Error);\n });\n });\n}\n\nasync function handleRequest(\n getApp: () => Promise<DevApp>,\n req: IncomingMessage,\n res: ServerResponse,\n options: { trustProxy?: boolean; bodyLimitBytes?: number },\n): Promise<void> {\n const app = await getApp();\n const url = resolveRequestUrl(req, { trustProxy: options.trustProxy === true });\n const isRpc = url.pathname.startsWith(\"/_moons/fn/\");\n const request = await toWebRequest(req, {\n trustProxy: options.trustProxy === true,\n ...(isRpc ? { maxBodyBytes: options.bodyLimitBytes ?? DEFAULT_BODY_LIMIT_BYTES } : {}),\n });\n const response = await app.handler(request);\n await sendNodeResponse(res, response);\n}\n\nfunction shouldHandle(req: IncomingMessage, apiPrefix: string | undefined): boolean {\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n\n // RPC serverFn (semua method): dulu hanya GET/HEAD HTML yang dirutekan,\n // sehingga `POST /_moons/fn/<hash>` jatuh ke Vite (\"Cannot POST\", 404) dan\n // serverFn tidak bisa dipakai di dev (temuan eval T2 ronde 2; decisions/0016).\n if (path.startsWith(\"/_moons/fn/\")) {\n return true;\n }\n\n // Addendum D.1: endpoint `src/api` (semua method) dirutekan ke runtime.\n if (apiPrefix !== undefined && (path === apiPrefix || path.startsWith(`${apiPrefix}/`))) {\n return true;\n }\n\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n return false;\n }\n if (path.startsWith(\"/@\") || path.startsWith(\"/node_modules/\") || path.startsWith(\"/__\")) {\n return false;\n }\n const lastSegment = path.split(\"/\").pop() ?? \"\";\n if (lastSegment.includes(\".\")) {\n return false;\n }\n const accept = req.headers.accept ?? \"\";\n return accept.includes(\"text/html\") || accept.includes(\"*/*\");\n}\n",
|
|
9
|
+
"/**\n * Addendum I.4 (W4): devtools overlay dev-only — tabel route, registry\n * serverFn (hash §12), endpoint `src/api`, dan status payload-scan.\n * Renderer murni (tanpa I/O) supaya bisa diuji unit; middleware dev yang\n * menyajikannya di `/__moons/devtools`.\n */\n\nexport interface DevtoolsRoute {\n /** Path URL (bentuk PRD §6.1), mis. \"/users/:id\". */\n path: string;\n /** \"page\" | \"layout\" | \"error\" | \"404\" | \"middleware\" | \"api\". */\n kind: string;\n /** Path relatif terhadap root app (posix). */\n file: string;\n}\n\nexport interface DevtoolsFn {\n file: string;\n name: string;\n hash: string;\n}\n\nexport interface DevtoolsData {\n routes: DevtoolsRoute[];\n apiRoutes: DevtoolsRoute[];\n serverFns: DevtoolsFn[];\n /** Canary `leakTest` terpasang → payload-scan aktif (A1). */\n canary: boolean;\n version: string;\n}\n\nexport function emptyDevtoolsData(): DevtoolsData {\n return { routes: [], apiRoutes: [], serverFns: [], canary: false, version: \"\" };\n}\n\nexport function renderDevtoolsHtml(data: DevtoolsData): string {\n const rows = (items: DevtoolsRoute[]): string =>\n items.length === 0\n ? '<tr><td colspan=\"3\" class=\"muted\">(empty)</td></tr>'\n : items\n .map(\n (item) =>\n `<tr><td><code>${esc(item.path)}</code></td><td><span class=\"kind k-${esc(item.kind)}\">${esc(item.kind)}</span></td><td class=\"muted\">${esc(item.file)}</td></tr>`,\n )\n .join(\"\\n\");\n\n const fnRows =\n data.serverFns.length === 0\n ? '<tr><td colspan=\"3\" class=\"muted\">(no serverFn)</td></tr>'\n : data.serverFns\n .map(\n (fn) =>\n `<tr><td><code>${esc(fn.hash)}</code></td><td><code>${esc(fn.name)}</code></td><td class=\"muted\">${esc(fn.file)}</td></tr>`,\n )\n .join(\"\\n\");\n\n const canary = data.canary\n ? '<span class=\"ok\">enabled</span> (canary installed)'\n : '<span class=\"muted\">disabled</span> (no canary)';\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>moons devtools</title>\n<style>\n :root { color-scheme: dark; }\n body { margin: 0; padding: 2rem; background: #101418; color: #e6edf3;\n font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; }\n h1 { font-size: 1.1rem; margin: 0 0 .25rem; }\n h2 { font-size: .95rem; margin: 2rem 0 .5rem; color: #9fb0c0; }\n .muted { color: #8b98a5; }\n .ok { color: #3fb950; font-weight: 600; }\n .meta { margin-bottom: 1.5rem; }\n table { border-collapse: collapse; width: 100%; max-width: 1100px; }\n th, td { text-align: left; padding: .35rem .75rem .35rem 0; border-bottom: 1px solid #1f2630; }\n th { color: #8b98a5; font-weight: 600; font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; }\n code { color: #79c0ff; }\n .kind { border: 1px solid #30363d; border-radius: 4px; padding: 0 .35rem; font-size: .8rem; }\n .k-page { color: #d2a8ff; } .k-layout { color: #7ee787; }\n .k-error { color: #ff7b72; } .k-404 { color: #ff7b72; }\n .k-middleware { color: #e3b341; } .k-api { color: #79c0ff; }\n</style>\n</head>\n<body>\n<h1>moons devtools</h1>\n<div class=\"meta muted\">@moonsjs/vite ${esc(data.version)} · dev-only · payload-scan: ${canary}</div>\n\n<h2>Routes (${data.routes.length})</h2>\n<table><thead><tr><th>path</th><th>kind</th><th>file</th></tr></thead>\n<tbody>\n${rows(data.routes)}\n</tbody></table>\n\n<h2>API (${data.apiRoutes.length})</h2>\n<table><thead><tr><th>path</th><th>kind</th><th>file</th></tr></thead>\n<tbody>\n${rows(data.apiRoutes)}\n</tbody></table>\n\n<h2>serverFn registry (${data.serverFns.length})</h2>\n<table><thead><tr><th>hash</th><th>export</th><th>file</th></tr></thead>\n<tbody>\n${fnRows}\n</tbody></table>\n</body>\n</html>\n`;\n}\n\nfunction esc(value: string): string {\n return value.replace(/[&<>\"']/g, (char) => {\n switch (char) {\n case \"&\":\n return \"&\";\n case \"<\":\n return \"<\";\n case \">\":\n return \">\";\n case '\"':\n return \""\";\n default:\n return \"'\";\n }\n });\n}\n",
|
|
10
|
+
"import { findLayouts, routeKeyFromFile, type ApiRouteDefinition, type RouteDefinition } from \"@moonsjs/core\";\nimport { relative, sep } from \"node:path\";\n\nexport interface ClientManifestEntry {\n page: string;\n layouts: string[];\n}\n\n/** Key manifest untuk halaman kustom `_404` dan `_error` (§11.2). */\nexport const NOT_FOUND_ROUTE_KEY = \"__moons/404\";\nexport const ERROR_ROUTE_KEY = \"__moons/error\";\n\n/**\n * Manifest route untuk client (PRD §8.2): key = key route file\n * (routeKeyFromFile), nilai = page + layout chain dari root ke leaf.\n */\nexport function buildClientManifest(\n routes: RouteDefinition[],\n routesDir: string,\n): Record<string, ClientManifestEntry> {\n const manifest: Record<string, ClientManifestEntry> = {};\n for (const route of routes) {\n if (route.kind !== \"page\") {\n continue;\n }\n const key = routeKeyFromFile(routesDir, route.file);\n manifest[key] = {\n page: key,\n layouts: findLayouts(route, routes).map((layout) => routeKeyFromFile(routesDir, layout.file)),\n };\n }\n\n const notFound = routes.find((route) => route.kind === \"404\");\n if (notFound !== undefined) {\n manifest[NOT_FOUND_ROUTE_KEY] = { page: routeKeyFromFile(routesDir, notFound.file), layouts: [] };\n }\n const errorRoute = routes.find((route) => route.kind === \"error\");\n if (errorRoute !== undefined) {\n manifest[ERROR_ROUTE_KEY] = { page: routeKeyFromFile(routesDir, errorRoute.file), layouts: [] };\n }\n // D.3: semua boundary `_error` (termasuk bersarang) ikut manifest — payload\n // memakai key file boundary yang dipilih runtime.\n for (const route of routes) {\n if (route.kind !== \"error\") {\n continue;\n }\n const key = routeKeyFromFile(routesDir, route.file);\n manifest[key] = { page: key, layouts: [] };\n }\n\n return manifest;\n}\n\n/** `virtual:moons/api` (Addendum D.1 + D.3): tabel API + middleware + peta import(). */\nexport function renderApiModule(\n apiRoutes: ApiRouteDefinition[],\n apiMiddlewares: ReadonlyArray<{ file: string; dir: string }>,\n projectRoot: string,\n): string {\n const loadEntries = apiRoutes\n .map(\n (route) =>\n ` ${JSON.stringify(route.path)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`,\n )\n .join(\"\\n\");\n const middlewareEntries = apiMiddlewares\n .map(\n (middleware) =>\n ` ${JSON.stringify(middleware.file)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, middleware.file))}),`,\n )\n .join(\"\\n\");\n return [\n \"// Generated by @moons. Do not edit manually.\",\n `export const apiRoutes = ${JSON.stringify(apiRoutes, null, 2)};`,\n `export const apiMiddlewares = ${JSON.stringify(apiMiddlewares, null, 2)};`,\n \"export const loadApi = {\",\n loadEntries,\n \"};\",\n \"export const loadApiMiddleware = {\",\n middlewareEntries,\n \"};\",\n \"\",\n ].join(\"\\n\");\n}\n\n/**\n * Isi `virtual:moons/routes`: daftar RouteDefinition, manifest client, dan\n * peta import() dinamis per file route (specifier root-relative).\n */\nexport function renderRoutesModule(\n routes: RouteDefinition[],\n routesDir: string,\n projectRoot: string,\n): string {\n const manifest = buildClientManifest(routes, routesDir);\n\n const loadEntries = routes.map((route) => {\n const key = routeKeyFromFile(routesDir, route.file);\n return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`;\n });\n\n return [\n \"// Generated by @moons. Do not edit manually.\",\n `export const routes = ${JSON.stringify(routes, null, 2)};`,\n \"\",\n `export const clientManifest = ${JSON.stringify(manifest, null, 2)};`,\n \"\",\n \"export const loadRoute = {\",\n ...loadEntries,\n \"};\",\n \"\",\n ].join(\"\\n\");\n}\n\n/** Path file absolut project → specifier root-relative yang bisa di-resolve Vite. */\nexport function rootSpecifier(projectRoot: string, file: string): string {\n let rel = relative(projectRoot, file);\n rel = sep === \"/\" ? rel : rel.split(sep).join(\"/\");\n return rel.startsWith(\"/\") ? rel : `/${rel}`;\n}\n"
|
|
11
|
+
],
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "E364F1DD2BD3F4F264756E2164756E21",
|
|
14
|
+
"names": []
|
|
15
|
+
}
|
|
@@ -103,17 +103,17 @@ function createServerOnlyClassifier(options = {}) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
function serverOnlyImportError(input) {
|
|
106
|
-
const sourceLabel = input.source === "builtin" ? "
|
|
106
|
+
const sourceLabel = input.source === "builtin" ? "Match source: framework built-in server-only package list." : input.source === "config" ? "Match source: `security.serverOnlyPackages` in moons.config.ts." : `Match source: marker \`"moons": { "serverOnly": true }\` in ${input.markerPath ?? "package.json"}.`;
|
|
107
107
|
return new MoonsError({
|
|
108
108
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
109
109
|
file: input.relativeFile,
|
|
110
110
|
line: input.line,
|
|
111
|
-
message: `
|
|
111
|
+
message: `importing${input.dynamic === true ? " (dynamic)" : ""} \`${input.specifier}\` — server-only package \`${input.packageName}\``,
|
|
112
112
|
details: `${sourceLabel}
|
|
113
|
-
|
|
113
|
+
Server-only packages must not enter the client bundle.`,
|
|
114
114
|
suggestions: [
|
|
115
|
-
"
|
|
116
|
-
"
|
|
115
|
+
"Move the operation into `*.server.ts` and call it via a serverFn",
|
|
116
|
+
"Or replace/remove the import in the client module"
|
|
117
117
|
]
|
|
118
118
|
});
|
|
119
119
|
}
|
|
@@ -499,17 +499,17 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
499
499
|
file: filename,
|
|
500
500
|
line: fn.line,
|
|
501
501
|
annotation: fn.name,
|
|
502
|
-
message: `schema ${kind} serverFn \`${fn.name}\`
|
|
503
|
-
details: `\`${reference}\`
|
|
502
|
+
message: `schema ${kind} of serverFn \`${fn.name}\` references server value \`${reference}\` from \`${serverImport.specifier}\``,
|
|
503
|
+
details: `\`${reference}\` is imported from another server file and is not a serverFn — the client stub does not export it (previously a raw bundler \`MISSING_EXPORT\` error).
|
|
504
504
|
|
|
505
|
-
` + `
|
|
506
|
-
` + ` // a)
|
|
505
|
+
` + `Pick one:
|
|
506
|
+
` + ` // a) move the constant/schema to a client-safe module
|
|
507
507
|
` + ` import { ${reference} } from "../shared/schemas";
|
|
508
|
-
` + ` // b) inline
|
|
509
|
-
` + " // c)
|
|
508
|
+
` + ` // b) inline the value if it is genuinely public in the schema, or
|
|
509
|
+
` + " // c) do extra validation in the server handler without referencing server values in the schema",
|
|
510
510
|
suggestions: [
|
|
511
|
-
"
|
|
512
|
-
"
|
|
511
|
+
"Move the referenced declaration to a client-safe shared module (e.g. `src/shared/schemas.ts`)",
|
|
512
|
+
"Or do the validation in the server handler instead of the schema expression"
|
|
513
513
|
]
|
|
514
514
|
});
|
|
515
515
|
}
|
|
@@ -523,22 +523,22 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
523
523
|
file: filename,
|
|
524
524
|
line: fn.line,
|
|
525
525
|
annotation: fn.name,
|
|
526
|
-
message: `schema ${kind} serverFn \`${fn.name}\`
|
|
527
|
-
details: `\`${reference}\`
|
|
526
|
+
message: `schema ${kind} of serverFn \`${fn.name}\` references local declaration \`${reference}\``,
|
|
527
|
+
details: `\`${reference}\` is dropped from the client stub, so its value is \`undefined\` in the browser (hydration crash).
|
|
528
528
|
|
|
529
|
-
` + `
|
|
530
|
-
` + ` const ${reference} = z.object({ … }); //
|
|
529
|
+
` + `Before (rejected):
|
|
530
|
+
` + ` const ${reference} = z.object({ … }); // local declaration in the same file
|
|
531
531
|
` + ` export const ${fn.name} = serverFn({ ${kind}: ${reference}, … });
|
|
532
532
|
|
|
533
|
-
` + `
|
|
534
|
-
` + ` // a) inline schema
|
|
533
|
+
` + `After (pick one):
|
|
534
|
+
` + ` // a) inline the schema in the argument
|
|
535
535
|
` + ` export const ${fn.name} = serverFn({ ${kind}: z.object({ … }), … });
|
|
536
|
-
` + ` // b)
|
|
536
|
+
` + ` // b) move the declaration to a shared module that may enter the client (e.g. src/shared/schemas.ts)
|
|
537
537
|
` + ` import { ${reference} } from "../shared/schemas";`,
|
|
538
538
|
suggestions: [
|
|
539
|
-
`Inline schema
|
|
540
|
-
`
|
|
541
|
-
"
|
|
539
|
+
`Inline the schema in the argument \`serverFn({ ${kind}: … })\`, or`,
|
|
540
|
+
`Move \`${reference}\` to a shared module (e.g. \`src/shared/schemas.ts\`) and import it from there`,
|
|
541
|
+
"Never move secret values to shared — the schema must be client-safe (zod/Standard Schema)"
|
|
542
542
|
]
|
|
543
543
|
});
|
|
544
544
|
}
|
|
@@ -551,9 +551,9 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
551
551
|
throw new MoonsError2({
|
|
552
552
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
553
553
|
file: filename,
|
|
554
|
-
message: `
|
|
555
|
-
details: "Re-
|
|
556
|
-
suggestions: ["
|
|
554
|
+
message: `cannot resolve \`${reExport.source}\` for re-export`,
|
|
555
|
+
details: "Re-exports from `*.server.ts` must be statically resolvable.",
|
|
556
|
+
suggestions: ["Fix the re-export path", "Avoid dynamic re-exports from server files"]
|
|
557
557
|
});
|
|
558
558
|
}
|
|
559
559
|
const targetNames = serverFnExportNames2(target, (specifier) => dependencies.analyzeSpecifier(filename, specifier));
|
|
@@ -569,9 +569,9 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
569
569
|
throw new MoonsError2({
|
|
570
570
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
571
571
|
file: filename,
|
|
572
|
-
message: `\`export *\`
|
|
573
|
-
details: "`export *`
|
|
574
|
-
suggestions: ["
|
|
572
|
+
message: `\`export *\` from \`${specifier}\` cannot be resolved`,
|
|
573
|
+
details: "`export *` from `*.server.ts` must be statically expandable.",
|
|
574
|
+
suggestions: ["Fix the re-export path", "Replace it with named re-exports"]
|
|
575
575
|
});
|
|
576
576
|
}
|
|
577
577
|
const targetNames = serverFnExportNames2(target, (nested) => dependencies.analyzeSpecifier(filename, nested));
|
|
@@ -580,7 +580,7 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
580
580
|
}
|
|
581
581
|
}
|
|
582
582
|
if (importLines.length === 0 && exportLines.length === 0) {
|
|
583
|
-
return { code: `// @moons:
|
|
583
|
+
return { code: `// @moons: no serverFn exported
|
|
584
584
|
export {};
|
|
585
585
|
`, referenced };
|
|
586
586
|
}
|
|
@@ -637,11 +637,11 @@ function nonContractServerFileError2(file, projectRoot) {
|
|
|
637
637
|
return new MoonsError2({
|
|
638
638
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
639
639
|
file: toRelative(projectRoot, file),
|
|
640
|
-
message: "file
|
|
641
|
-
details: "
|
|
640
|
+
message: "server file uses an extension outside the `*.server.ts` contract",
|
|
641
|
+
details: "The zero-trust transform only handles `*.server.ts`/`*.server.tsx`; server files with other extensions cannot be stubbed and must not enter the client graph.",
|
|
642
642
|
suggestions: [
|
|
643
|
-
"
|
|
644
|
-
"
|
|
643
|
+
"Rename the file to `*.server.ts`/`*.server.tsx` and put the `serverFn` inside it",
|
|
644
|
+
"Or move the value you need into a non-server file"
|
|
645
645
|
]
|
|
646
646
|
});
|
|
647
647
|
}
|
|
@@ -791,11 +791,11 @@ async function checkClientModuleImports2(context) {
|
|
|
791
791
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
792
792
|
file: relativeFile,
|
|
793
793
|
line,
|
|
794
|
-
message: `
|
|
795
|
-
details: "
|
|
794
|
+
message: `importing \`${specifier}\` — \`moons.config.ts\` is a server-only module`,
|
|
795
|
+
details: "The config contains `context`/`middleware` (server code) and never enters the client bundle (Addendum J.4).",
|
|
796
796
|
suggestions: [
|
|
797
|
-
"
|
|
798
|
-
"
|
|
797
|
+
"Move the value you need into a client-safe module (e.g. `src/shared/…`)",
|
|
798
|
+
"Get the value from the server via a loader/serverFn"
|
|
799
799
|
]
|
|
800
800
|
});
|
|
801
801
|
};
|
|
@@ -804,11 +804,11 @@ async function checkClientModuleImports2(context) {
|
|
|
804
804
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
805
805
|
file: relativeFile,
|
|
806
806
|
line,
|
|
807
|
-
message: `
|
|
808
|
-
details: "
|
|
807
|
+
message: `importing \`${specifier}\` — \`${toRelative(projectRoot, resolvedTarget)}\` is inside \`src/server/**\` (server-only)`,
|
|
808
|
+
details: "The `src/server/**` directory is for server code only (Addendum J.4); the `.server.ts` suffix is not the only guarantee.",
|
|
809
809
|
suggestions: [
|
|
810
|
-
"
|
|
811
|
-
"
|
|
810
|
+
"Move client-safe code to `src/shared/`",
|
|
811
|
+
"Access the value from the server via a loader/serverFn"
|
|
812
812
|
]
|
|
813
813
|
});
|
|
814
814
|
};
|
|
@@ -818,11 +818,11 @@ async function checkClientModuleImports2(context) {
|
|
|
818
818
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
819
819
|
file: relativeFile,
|
|
820
820
|
line,
|
|
821
|
-
message: `
|
|
822
|
-
details: "`src/api/**`
|
|
821
|
+
message: `importing \`${specifier}\` — \`${relApi}\` is a server-only API module`,
|
|
822
|
+
details: "`src/api/**` only runs on the server (HTTP endpoints) and never enters the client bundle.",
|
|
823
823
|
suggestions: [
|
|
824
|
-
"
|
|
825
|
-
"
|
|
824
|
+
"Move the operation into `*.server.ts` and call it via a serverFn",
|
|
825
|
+
"Do not import `src/api` files from client routes/components"
|
|
826
826
|
]
|
|
827
827
|
});
|
|
828
828
|
};
|
|
@@ -862,8 +862,8 @@ async function checkClientModuleImports2(context) {
|
|
|
862
862
|
message,
|
|
863
863
|
details,
|
|
864
864
|
suggestions: [
|
|
865
|
-
"
|
|
866
|
-
"
|
|
865
|
+
"Create a server function that returns the data you need, or",
|
|
866
|
+
"If the value is genuinely public, redefine the constant in a client-safe module (never move secret values)"
|
|
867
867
|
]
|
|
868
868
|
});
|
|
869
869
|
};
|
|
@@ -872,11 +872,11 @@ async function checkClientModuleImports2(context) {
|
|
|
872
872
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
873
873
|
file: relativeFile,
|
|
874
874
|
line,
|
|
875
|
-
message: `
|
|
876
|
-
details: "
|
|
875
|
+
message: `importing \`${specifier}\` in a client module`,
|
|
876
|
+
details: "Server-only modules may only be imported from *.server.ts.",
|
|
877
877
|
suggestions: [
|
|
878
|
-
"
|
|
879
|
-
"
|
|
878
|
+
"Move the operation into a serverFn, or",
|
|
879
|
+
"Remove the import"
|
|
880
880
|
]
|
|
881
881
|
});
|
|
882
882
|
};
|
|
@@ -970,11 +970,11 @@ async function checkClientModuleImports2(context) {
|
|
|
970
970
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
971
971
|
file: relativeFile,
|
|
972
972
|
line,
|
|
973
|
-
message: `
|
|
974
|
-
details: `\`${toRelative(projectRoot, resolvedTarget)}\`
|
|
973
|
+
message: `importing \`${name}\` — comes from \`${toRelative(projectRoot, serverFile)}\` (via a re-export chain)`,
|
|
974
|
+
details: `\`${toRelative(projectRoot, resolvedTarget)}\` re-exports a value from a server file; ` + "the client stub does not export it (previously a raw bundler `MISSING_EXPORT` error).",
|
|
975
975
|
suggestions: [
|
|
976
|
-
"
|
|
977
|
-
"
|
|
976
|
+
"Create a server function that returns the data you need, or",
|
|
977
|
+
"Redefine the public constant in a client-safe module (never move secret values)"
|
|
978
978
|
]
|
|
979
979
|
});
|
|
980
980
|
}
|
|
@@ -985,17 +985,17 @@ async function checkClientModuleImports2(context) {
|
|
|
985
985
|
const targetRelative = resolvedTarget === null ? rawSpecifier : toRelative(projectRoot, resolvedTarget);
|
|
986
986
|
if (statement.type === "ExportAllDeclaration") {
|
|
987
987
|
if (resolvedTarget === null) {
|
|
988
|
-
reject(line, `\`export *\`
|
|
988
|
+
reject(line, `\`export *\` from \`${targetRelative}\` cannot be resolved`, "Re-exports from `*.server.ts` must be statically resolvable.");
|
|
989
989
|
}
|
|
990
990
|
continue;
|
|
991
991
|
}
|
|
992
992
|
const specifiers = statement["specifiers"] ?? [];
|
|
993
993
|
for (const item of specifiers) {
|
|
994
994
|
if (item.type === "ImportDefaultSpecifier") {
|
|
995
|
-
reject(line, `
|
|
995
|
+
reject(line, `importing the default export from ${targetRelative}`, "Default exports from a server file must not cross to the client.");
|
|
996
996
|
}
|
|
997
997
|
if (item.type === "ImportNamespaceSpecifier") {
|
|
998
|
-
reject(line, `
|
|
998
|
+
reject(line, `importing the namespace (\`* as\`) from ${targetRelative}`, "Namespace imports from a server file must not cross to the client.");
|
|
999
999
|
}
|
|
1000
1000
|
const name = item["local"]?.["name"] ?? "";
|
|
1001
1001
|
const specifierKind = item["importKind"];
|
|
@@ -1004,7 +1004,7 @@ async function checkClientModuleImports2(context) {
|
|
|
1004
1004
|
continue;
|
|
1005
1005
|
}
|
|
1006
1006
|
if (name.length > 0 && !await isAllowedServerFnImport(resolvedTarget, specifier, name)) {
|
|
1007
|
-
reject(line, `
|
|
1007
|
+
reject(line, `importing \`${name}\` from ${targetRelative}`, "Server code must not appear in the client.\nOnly `serverFn` may cross.");
|
|
1008
1008
|
}
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
@@ -1044,11 +1044,11 @@ async function checkClientModuleImports2(context) {
|
|
|
1044
1044
|
code: "E_ENV_IN_CLIENT",
|
|
1045
1045
|
file: relativeFile,
|
|
1046
1046
|
line: node.loc?.start.line ?? 1,
|
|
1047
|
-
message: `
|
|
1048
|
-
details: "`env()`
|
|
1047
|
+
message: `calling \`${callText}\` in a client module`,
|
|
1048
|
+
details: "`env()` is only legal on the server.",
|
|
1049
1049
|
suggestions: [
|
|
1050
|
-
"
|
|
1051
|
-
"
|
|
1050
|
+
"Move the call into a serverFn, or",
|
|
1051
|
+
"Use import.meta.env for public values"
|
|
1052
1052
|
]
|
|
1053
1053
|
});
|
|
1054
1054
|
});
|
|
@@ -1097,7 +1097,7 @@ async function checkClientModuleImports2(context) {
|
|
|
1097
1097
|
continue;
|
|
1098
1098
|
}
|
|
1099
1099
|
const targetRelative = resolvedTarget === null ? request.text : toRelative(projectRoot, resolvedTarget);
|
|
1100
|
-
reject(request.node.loc?.start.line ?? 1, `
|
|
1100
|
+
reject(request.node.loc?.start.line ?? 1, `importing (dynamic) \`${request.text}\` from ${targetRelative}`, "The client must not dynamically import `*.server.ts`.");
|
|
1101
1101
|
}
|
|
1102
1102
|
walk(ast, (node) => {
|
|
1103
1103
|
if (node.type !== "CallExpression") {
|
|
@@ -1120,11 +1120,11 @@ async function checkClientModuleImports2(context) {
|
|
|
1120
1120
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
1121
1121
|
file: relativeFile,
|
|
1122
1122
|
line: node.loc?.start.line ?? 1,
|
|
1123
|
-
message: `
|
|
1124
|
-
details: "`import.meta.glob`
|
|
1123
|
+
message: `importing (glob) \`${pattern}\` in a client module`,
|
|
1124
|
+
details: "`import.meta.glob` patterns covering `*.server.ts` are forbidden in the client.",
|
|
1125
1125
|
suggestions: [
|
|
1126
|
-
"
|
|
1127
|
-
"
|
|
1126
|
+
"Statically import the `serverFn` from `*.server.ts` (that is all that may cross), or",
|
|
1127
|
+
"Move the data requirement into a server function"
|
|
1128
1128
|
]
|
|
1129
1129
|
});
|
|
1130
1130
|
}
|
|
@@ -1216,7 +1216,7 @@ function checkRouteModuleExports2(context) {
|
|
|
1216
1216
|
return;
|
|
1217
1217
|
}
|
|
1218
1218
|
const allowed = base === "_layout.tsx" ? ["default", "loader"] : base === "_error.tsx" || base === "_404.tsx" ? ["default"] : ["default", "loader", "meta", "prerender", "paths", "revalidate"];
|
|
1219
|
-
const allowedText = `
|
|
1219
|
+
const allowedText = `Only ${allowed.map((name) => `\`${name}\``).join(", ")} may be exported.`;
|
|
1220
1220
|
const relativeFile = toRelative(projectRoot, file);
|
|
1221
1221
|
const ast = parseModule(code, file);
|
|
1222
1222
|
const declarations = [];
|
|
@@ -1231,11 +1231,11 @@ function checkRouteModuleExports2(context) {
|
|
|
1231
1231
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
1232
1232
|
file: relativeFile,
|
|
1233
1233
|
line: statement.loc?.start.line ?? 1,
|
|
1234
|
-
message: "`export *`
|
|
1234
|
+
message: "`export *` is not allowed in route modules",
|
|
1235
1235
|
details: allowedText,
|
|
1236
1236
|
suggestions: [
|
|
1237
|
-
`
|
|
1238
|
-
"
|
|
1237
|
+
`Move the file contents outside ${toRelative(projectRoot, routesDir)}/, or`,
|
|
1238
|
+
"Remove the `export *`"
|
|
1239
1239
|
]
|
|
1240
1240
|
});
|
|
1241
1241
|
}
|
|
@@ -1262,11 +1262,11 @@ function checkRouteModuleExports2(context) {
|
|
|
1262
1262
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
1263
1263
|
file: relativeFile,
|
|
1264
1264
|
line: entry.line,
|
|
1265
|
-
message: `export \`${entry.name}\`
|
|
1265
|
+
message: `export \`${entry.name}\` is not allowed in route modules`,
|
|
1266
1266
|
details: allowedText,
|
|
1267
1267
|
suggestions: [
|
|
1268
|
-
`
|
|
1269
|
-
"
|
|
1268
|
+
`Move \`${entry.name}\` to a file outside ${toRelative(projectRoot, routesDir)}/, or`,
|
|
1269
|
+
"Remove the export"
|
|
1270
1270
|
]
|
|
1271
1271
|
});
|
|
1272
1272
|
}
|
|
@@ -1275,9 +1275,9 @@ function checkRouteModuleExports2(context) {
|
|
|
1275
1275
|
throw new MoonsError2({
|
|
1276
1276
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
1277
1277
|
file: relativeFile,
|
|
1278
|
-
message: "
|
|
1279
|
-
details: "
|
|
1280
|
-
suggestions: ["
|
|
1278
|
+
message: "route module must export `default`",
|
|
1279
|
+
details: "Every route file needs an `export default` React component.",
|
|
1280
|
+
suggestions: ["Add `export default function Page() { ... }`"]
|
|
1281
1281
|
});
|
|
1282
1282
|
}
|
|
1283
1283
|
}
|
|
@@ -1332,4 +1332,4 @@ function buildFnRegistry2(projectRoot, files) {
|
|
|
1332
1332
|
|
|
1333
1333
|
export { isServerFilePath2, isNonContractServerFile2, isServerSpecifier2, resolveServerSpecifierSync2, stripQuery2, analyzeServerFile2, serverFnExportNames2, renderServerFnStub2, injectServerFnMeta2, nonContractServerFileError2, checkClientModuleImports2, checkRouteModuleExports2, scanServerFiles2, buildFnRegistry2 };
|
|
1334
1334
|
|
|
1335
|
-
//# debugId=
|
|
1335
|
+
//# debugId=B9056013E70896ED64756E2164756E21
|