@moonsjs/vite 0.1.4 → 0.1.5
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/api.d.ts +8 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/{index-33p1r65s.js → index-1fnt137k.js} +50 -5
- package/dist/index-1fnt137k.js.map +14 -0
- package/dist/{index-3ec9vpek.js → index-e7qz0epf.js} +232 -24
- package/dist/index-e7qz0epf.js.map +11 -0
- package/dist/index.js +213 -16
- package/dist/index.js.map +5 -4
- package/dist/manifest.d.ts +6 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/payload-scan.d.ts +18 -0
- package/dist/payload-scan.d.ts.map +1 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/server/dev.d.ts +3 -0
- package/dist/server/dev.d.ts.map +1 -1
- package/dist/server/handler.d.ts +16 -1
- package/dist/server/handler.d.ts.map +1 -1
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +453 -17
- package/dist/server/index.js.map +8 -6
- package/dist/server/node-server.d.ts +1 -1
- package/dist/server/node-server.d.ts.map +1 -1
- package/dist/server/node.d.ts.map +1 -1
- package/dist/server/prerender.d.ts +28 -0
- package/dist/server/prerender.d.ts.map +1 -0
- package/dist/server-only.d.ts +57 -0
- package/dist/server-only.d.ts.map +1 -0
- package/dist/transform.d.ts +9 -1
- package/dist/transform.d.ts.map +1 -1
- package/dist/transform.js +1 -1
- package/dist/virtual-modules.d.ts +1 -0
- package/dist/virtual-modules.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +124 -0
- package/src/manifest.ts +42 -1
- package/src/payload-scan.ts +53 -0
- package/src/plugin.ts +130 -7
- package/src/server/dev.ts +14 -5
- package/src/server/handler.ts +317 -13
- package/src/server/index.ts +4 -0
- package/src/server/node-server.ts +47 -7
- package/src/server/node.ts +17 -0
- package/src/server/prerender.ts +230 -0
- package/src/server-only.ts +220 -0
- package/src/transform.ts +138 -8
- package/src/virtual-modules.ts +1 -0
- package/dist/index-33p1r65s.js.map +0 -14
- package/dist/index-3ec9vpek.js.map +0 -10
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validasi statis export modul `src/api` (PRD Addendum D.1):
|
|
3
|
+
* hanya handler method `GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS` sebagai fungsi;
|
|
4
|
+
* `default`/export lain ditolak → `E_API_HANDLER_INVALID`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertApiModuleExports(code: string, file: string): string[];
|
|
7
|
+
export declare function assertMiddlewareExports(code: string, file: string): void;
|
|
8
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CA6E3E;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CA0BxE"}
|
|
@@ -137,8 +137,23 @@ function readBody(req) {
|
|
|
137
137
|
async function sendNodeResponse2(res, response) {
|
|
138
138
|
res.statusCode = response.status;
|
|
139
139
|
response.headers.forEach((value, key) => {
|
|
140
|
+
if (key === "set-cookie") {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
140
143
|
res.setHeader(key, value);
|
|
141
144
|
});
|
|
145
|
+
const getSetCookie = response.headers.getSetCookie;
|
|
146
|
+
if (typeof getSetCookie === "function") {
|
|
147
|
+
const cookies = getSetCookie.call(response.headers);
|
|
148
|
+
if (cookies.length > 0) {
|
|
149
|
+
res.setHeader("set-cookie", cookies);
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
const single = response.headers.get("set-cookie");
|
|
153
|
+
if (single !== null) {
|
|
154
|
+
res.setHeader("set-cookie", single);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
142
157
|
const body = Buffer.from(await response.arrayBuffer());
|
|
143
158
|
res.end(body);
|
|
144
159
|
}
|
|
@@ -146,6 +161,7 @@ async function sendNodeResponse2(res, response) {
|
|
|
146
161
|
// src/virtual-modules.ts
|
|
147
162
|
var VIRTUAL_MODULES = {
|
|
148
163
|
routes: "virtual:moons/routes",
|
|
164
|
+
api: "virtual:moons/api",
|
|
149
165
|
config: "virtual:moons/config",
|
|
150
166
|
clientEntry: "virtual:moons/client-entry",
|
|
151
167
|
serverEntry: "virtual:moons/server-entry"
|
|
@@ -166,7 +182,7 @@ function installDevMiddleware2(server, options) {
|
|
|
166
182
|
if (appPromise === null) {
|
|
167
183
|
appPromise = (async () => {
|
|
168
184
|
const entry = await server.ssrLoadModule(VIRTUAL_MODULES.serverEntry);
|
|
169
|
-
const app = entry.createDevApp(createDevShell2(server, options));
|
|
185
|
+
const app = entry.createDevApp(createDevShell2(server, options), options.payloadScan);
|
|
170
186
|
await app.plugins.start();
|
|
171
187
|
await app.plugins.ready();
|
|
172
188
|
return app;
|
|
@@ -178,7 +194,7 @@ function installDevMiddleware2(server, options) {
|
|
|
178
194
|
appPromise?.then((app) => app.plugins.shutdown());
|
|
179
195
|
});
|
|
180
196
|
server.middlewares.use((req, res, next) => {
|
|
181
|
-
if (!shouldHandle(req)) {
|
|
197
|
+
if (!shouldHandle(req, options.apiPrefix)) {
|
|
182
198
|
next();
|
|
183
199
|
return;
|
|
184
200
|
}
|
|
@@ -192,11 +208,14 @@ async function handleRequest(getApp, req, res) {
|
|
|
192
208
|
const response = await app.handler(await toWebRequest2(req));
|
|
193
209
|
await sendNodeResponse2(res, response);
|
|
194
210
|
}
|
|
195
|
-
function shouldHandle(req) {
|
|
211
|
+
function shouldHandle(req, apiPrefix) {
|
|
196
212
|
const path = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
197
213
|
if (path.startsWith("/_moons/fn/")) {
|
|
198
214
|
return true;
|
|
199
215
|
}
|
|
216
|
+
if (apiPrefix !== undefined && (path === apiPrefix || path.startsWith(`${apiPrefix}/`))) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
200
219
|
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
201
220
|
return false;
|
|
202
221
|
}
|
|
@@ -236,8 +255,34 @@ function buildClientManifest(routes, routesDir) {
|
|
|
236
255
|
if (errorRoute !== undefined) {
|
|
237
256
|
manifest[ERROR_ROUTE_KEY] = { page: routeKeyFromFile(routesDir, errorRoute.file), layouts: [] };
|
|
238
257
|
}
|
|
258
|
+
for (const route of routes) {
|
|
259
|
+
if (route.kind !== "error") {
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const key = routeKeyFromFile(routesDir, route.file);
|
|
263
|
+
manifest[key] = { page: key, layouts: [] };
|
|
264
|
+
}
|
|
239
265
|
return manifest;
|
|
240
266
|
}
|
|
267
|
+
function renderApiModule(apiRoutes, apiMiddlewares, projectRoot) {
|
|
268
|
+
const loadEntries = apiRoutes.map((route) => ` ${JSON.stringify(route.path)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, route.file))}),`).join(`
|
|
269
|
+
`);
|
|
270
|
+
const middlewareEntries = apiMiddlewares.map((middleware) => ` ${JSON.stringify(middleware.file)}: () => import(${JSON.stringify(rootSpecifier(projectRoot, middleware.file))}),`).join(`
|
|
271
|
+
`);
|
|
272
|
+
return [
|
|
273
|
+
"// Generated by @moons. Jangan diubah manual.",
|
|
274
|
+
`export const apiRoutes = ${JSON.stringify(apiRoutes, null, 2)};`,
|
|
275
|
+
`export const apiMiddlewares = ${JSON.stringify(apiMiddlewares, null, 2)};`,
|
|
276
|
+
"export const loadApi = {",
|
|
277
|
+
loadEntries,
|
|
278
|
+
"};",
|
|
279
|
+
"export const loadApiMiddleware = {",
|
|
280
|
+
middlewareEntries,
|
|
281
|
+
"};",
|
|
282
|
+
""
|
|
283
|
+
].join(`
|
|
284
|
+
`);
|
|
285
|
+
}
|
|
241
286
|
function renderRoutesModule(routes, routesDir, projectRoot) {
|
|
242
287
|
const manifest = buildClientManifest(routes, routesDir);
|
|
243
288
|
const loadEntries = routes.map((route) => {
|
|
@@ -263,6 +308,6 @@ function rootSpecifier(projectRoot, file) {
|
|
|
263
308
|
return rel.startsWith("/") ? rel : `/${rel}`;
|
|
264
309
|
}
|
|
265
310
|
|
|
266
|
-
export { NOT_FOUND_ROUTE_KEY,
|
|
311
|
+
export { NOT_FOUND_ROUTE_KEY, renderApiModule, renderRoutesModule, rootSpecifier, VIRTUAL_MODULES, fromNullPrefixed, ROOT_PLACEHOLDER2, DEFAULT_TEMPLATE2, injectShell2, createDevShell2, createFileShell2, toWebRequest2, sendNodeResponse2, installDevMiddleware2 };
|
|
267
312
|
|
|
268
|
-
//# debugId=
|
|
313
|
+
//# debugId=04E46A508CF0D84F64756E2164756E21
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/server/shell.ts", "../src/server/node.ts", "../src/virtual-modules.ts", "../src/server/dev.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=\"id\">',\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 tidak memuat \\`${ROOT_PLACEHOLDER}\\``,\n details: \"index.html aplikasi wajib memuat elemen root tersebut agar SSR bisa meng-inject markup.\",\n suggestions: [\n `Tambahkan ${ROOT_PLACEHOLDER} ke index.html`,\n \"Atau hapus index.html untuk memakai shell bawaan\",\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` tidak ditemukan\",\n details: \"Build client harus dijalankan sebelum server produksi dinyalakan (PRD §18).\",\n suggestions: [\"Jalankan `bun run build` lebih dulu\"],\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\";\n\nconst MAX_BODY_BYTES = 32 * 1024 * 1024; // batas defensif transport; limit RPC §7.3 tetap berlaku\n\n/** Konversi request Node → Web `Request` (body diteruskan untuk non-GET/HEAD, §7.3). */\nexport async function toWebRequest(req: IncomingMessage): Promise<Request> {\n const host = req.headers.host ?? \"localhost\";\n const url = `http://${host}${req.url ?? \"/\"}`;\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\") {\n // content-length tidak disalin; fetch menghitung ulang dari body.\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 if (method === \"GET\" || method === \"HEAD\") {\n return new Request(url, { method, headers });\n }\n\n const body = await readBody(req);\n return new Request(url, { method, headers, body: new Uint8Array(body) });\n}\n\nfunction readBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolvePromise, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n\n req.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_BODY_BYTES) {\n req.destroy();\n reject(new Error(\"request body melebihi batas transport\"));\n return;\n }\n chunks.push(chunk);\n });\n req.on(\"end\", () => resolvePromise(Buffer.concat(chunks)));\n req.on(\"error\", (error) => reject(error));\n });\n}\n\n/** Tulis Web `Response` ke `ServerResponse` Node. */\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 const body = Buffer.from(await response.arrayBuffer());\n res.end(body);\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 { sendNodeResponse, toWebRequest } from \"./node\";\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: { templatePath: string; payloadScan?: PayloadScanOptions; apiPrefix?: string },\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 if (!shouldHandle(req, options.apiPrefix)) {\n next();\n return;\n }\n void handleRequest(getApp, req, res).catch((error: unknown) => {\n next(error as Error);\n });\n });\n}\n\nasync function handleRequest(\n getApp: () => Promise<DevApp>,\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const app = await getApp();\n const response = await app.handler(await toWebRequest(req));\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
|
+
"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. Jangan diubah manual.\",\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. Jangan diubah manual.\",\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"
|
|
10
|
+
],
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "04E46A508CF0D84F64756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
|
@@ -1,8 +1,124 @@
|
|
|
1
1
|
// src/transform.ts
|
|
2
|
-
import { MoonsError, computeFnHash, fnRegistryKey } from "@moonsjs/core";
|
|
2
|
+
import { MoonsError as MoonsError2, computeFnHash, fnRegistryKey } from "@moonsjs/core";
|
|
3
3
|
import { parse, parseExpression } from "@babel/parser";
|
|
4
|
-
import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
5
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, realpathSync } from "node:fs";
|
|
5
|
+
import { dirname as dirname2, isAbsolute, join as join2, resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
// src/server-only.ts
|
|
8
|
+
import { MoonsError } from "@moonsjs/core";
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
var DEFAULT_SERVER_ONLY_PACKAGES = [
|
|
12
|
+
"pg",
|
|
13
|
+
"postgres",
|
|
14
|
+
"mysql",
|
|
15
|
+
"mysql2",
|
|
16
|
+
"sqlite3",
|
|
17
|
+
"better-sqlite3",
|
|
18
|
+
"drizzle-orm",
|
|
19
|
+
"@prisma/client",
|
|
20
|
+
"prisma",
|
|
21
|
+
"mongodb",
|
|
22
|
+
"mongoose",
|
|
23
|
+
"redis",
|
|
24
|
+
"ioredis",
|
|
25
|
+
"bcrypt",
|
|
26
|
+
"bcryptjs",
|
|
27
|
+
"argon2",
|
|
28
|
+
"jsonwebtoken",
|
|
29
|
+
"nodemailer",
|
|
30
|
+
"sharp",
|
|
31
|
+
"execa",
|
|
32
|
+
"cross-spawn",
|
|
33
|
+
"dotenv",
|
|
34
|
+
"knex",
|
|
35
|
+
"typeorm",
|
|
36
|
+
"sequelize",
|
|
37
|
+
"@libsql/client"
|
|
38
|
+
];
|
|
39
|
+
function tryReadPackageJson(dir) {
|
|
40
|
+
const candidate = join(dir, "package.json");
|
|
41
|
+
if (!existsSync(candidate)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const json = JSON.parse(readFileSync(candidate, "utf8"));
|
|
46
|
+
const moons = json.moons;
|
|
47
|
+
return {
|
|
48
|
+
dir,
|
|
49
|
+
name: typeof json.name === "string" ? json.name : "",
|
|
50
|
+
serverOnlyMarker: moons?.serverOnly === true
|
|
51
|
+
};
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function createServerOnlyClassifier(options = {}) {
|
|
57
|
+
const cache = options.cache ?? new Map;
|
|
58
|
+
const configured = new Set(options.configured ?? []);
|
|
59
|
+
const builtin = new Set(DEFAULT_SERVER_ONLY_PACKAGES);
|
|
60
|
+
const infoOf = (file) => {
|
|
61
|
+
let dir = dirname(file);
|
|
62
|
+
for (let depth = 0;depth < 64; depth += 1) {
|
|
63
|
+
if (cache.has(dir)) {
|
|
64
|
+
return cache.get(dir) ?? null;
|
|
65
|
+
}
|
|
66
|
+
const info = tryReadPackageJson(dir);
|
|
67
|
+
if (info !== null) {
|
|
68
|
+
cache.set(dir, info);
|
|
69
|
+
return info;
|
|
70
|
+
}
|
|
71
|
+
const parent = dirname(dir);
|
|
72
|
+
if (parent === dir) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
dir = parent;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
check({ specifier: _specifier, resolvedFile, importerFile }) {
|
|
81
|
+
if (resolvedFile === null) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const info = infoOf(resolvedFile);
|
|
85
|
+
if (info === null || info.name.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const importer = infoOf(importerFile);
|
|
89
|
+
if (importer !== null && importer.dir === info.dir) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (builtin.has(info.name)) {
|
|
93
|
+
return { packageName: info.name, source: "builtin", packageDir: info.dir };
|
|
94
|
+
}
|
|
95
|
+
if (configured.has(info.name)) {
|
|
96
|
+
return { packageName: info.name, source: "config", packageDir: info.dir };
|
|
97
|
+
}
|
|
98
|
+
if (info.serverOnlyMarker) {
|
|
99
|
+
return { packageName: info.name, source: "marker", packageDir: info.dir };
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function serverOnlyImportError(input) {
|
|
106
|
+
const sourceLabel = input.source === "builtin" ? "Sumber kecocokan: daftar bawaan paket server-only framework." : input.source === "config" ? "Sumber kecocokan: `security.serverOnlyPackages` di moons.config.ts." : `Sumber kecocokan: marker \`"moons": { "serverOnly": true }\` di ${input.markerPath ?? "package.json"}.`;
|
|
107
|
+
return new MoonsError({
|
|
108
|
+
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
109
|
+
file: input.relativeFile,
|
|
110
|
+
line: input.line,
|
|
111
|
+
message: `mengimpor${input.dynamic === true ? " (dynamic)" : ""} \`${input.specifier}\` — paket server-only \`${input.packageName}\``,
|
|
112
|
+
details: `${sourceLabel}
|
|
113
|
+
Paket server-only tidak boleh masuk bundle client (PRD Addendum C).`,
|
|
114
|
+
suggestions: [
|
|
115
|
+
"Pindahkan operasi tersebut ke `*.server.ts` dan panggil lewat serverFn",
|
|
116
|
+
"Atau ganti/hapus import tersebut di modul client"
|
|
117
|
+
]
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/transform.ts
|
|
6
122
|
var SERVER_SPECIFIER = /\.server(\.(ts|tsx|js|jsx|mts|cts|mjs|cjs))?$/i;
|
|
7
123
|
var CONTRACT_SERVER_FILE = /\.server\.(ts|tsx)$/i;
|
|
8
124
|
var NON_CONTRACT_SERVER_FILE = /\.server\.(js|jsx|mts|cts|mjs|cjs)$/i;
|
|
@@ -19,12 +135,12 @@ function resolveServerSpecifierSync2(importerFile, specifier) {
|
|
|
19
135
|
if (!isServerSpecifier2(specifier)) {
|
|
20
136
|
return null;
|
|
21
137
|
}
|
|
22
|
-
const base = specifier.startsWith(".") ? resolve(
|
|
138
|
+
const base = specifier.startsWith(".") ? resolve(dirname2(importerFile), specifier) : isAbsolute(specifier) ? specifier : null;
|
|
23
139
|
if (base === null) {
|
|
24
140
|
return null;
|
|
25
141
|
}
|
|
26
142
|
for (const candidate of [base, `${base}.ts`, `${base}.tsx`]) {
|
|
27
|
-
if (
|
|
143
|
+
if (existsSync2(candidate)) {
|
|
28
144
|
return candidate;
|
|
29
145
|
}
|
|
30
146
|
}
|
|
@@ -68,6 +184,17 @@ function topLevelStatements(ast) {
|
|
|
68
184
|
const program = ast["program"];
|
|
69
185
|
return program?.body ?? [];
|
|
70
186
|
}
|
|
187
|
+
function isTypeOnlyImportExport(statement) {
|
|
188
|
+
const declarationKind = statement["importKind"] ?? statement["exportKind"];
|
|
189
|
+
if (declarationKind === "type") {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const specifiers = statement["specifiers"] ?? [];
|
|
193
|
+
if (specifiers.length === 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return specifiers.every((item) => (item["importKind"] ?? item["exportKind"]) === "type");
|
|
197
|
+
}
|
|
71
198
|
function keyName(node) {
|
|
72
199
|
const key = node;
|
|
73
200
|
if (key?.type === "Identifier") {
|
|
@@ -359,16 +486,27 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
359
486
|
if (reference.length === 0 || !localNames.has(reference)) {
|
|
360
487
|
continue;
|
|
361
488
|
}
|
|
362
|
-
throw new
|
|
489
|
+
throw new MoonsError2({
|
|
363
490
|
code: "E_SCHEMA_LOCAL_REF",
|
|
364
491
|
file: filename,
|
|
365
492
|
line: fn.line,
|
|
366
493
|
annotation: fn.name,
|
|
367
494
|
message: `schema ${kind} serverFn \`${fn.name}\` merujuk deklarasi lokal \`${reference}\``,
|
|
368
|
-
details:
|
|
495
|
+
details: `\`${reference}\` dibuang dari stub client (PRD §12.2/§12.3), sehingga nilainya \`undefined\` di browser (crash hydration).
|
|
496
|
+
|
|
497
|
+
` + `Sebelum (ditolak):
|
|
498
|
+
` + ` const ${reference} = z.object({ … }); // deklarasi lokal di file yang sama
|
|
499
|
+
` + ` export const ${fn.name} = serverFn({ ${kind}: ${reference}, … });
|
|
500
|
+
|
|
501
|
+
` + `Sesudah (pilih salah satu):
|
|
502
|
+
` + ` // a) inline schema di argumen
|
|
503
|
+
` + ` export const ${fn.name} = serverFn({ ${kind}: z.object({ … }), … });
|
|
504
|
+
` + ` // b) pindahkan deklarasi ke modul bersama yang boleh masuk client (mis. src/shared/schemas.ts)
|
|
505
|
+
` + ` import { ${reference} } from "../shared/schemas";`,
|
|
369
506
|
suggestions: [
|
|
370
|
-
|
|
371
|
-
|
|
507
|
+
`Inline schema di argumen \`serverFn({ ${kind}: … })\`, atau`,
|
|
508
|
+
`Pindahkan \`${reference}\` ke modul bersama (mis. \`src/shared/schemas.ts\`) lalu impor dari sana`,
|
|
509
|
+
"Jangan memindahkan nilai rahasia ke shared — schema harus client-safe (zod/Standard Schema)"
|
|
372
510
|
]
|
|
373
511
|
});
|
|
374
512
|
}
|
|
@@ -378,7 +516,7 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
378
516
|
for (const reExport of analysis.reExports) {
|
|
379
517
|
const target = dependencies.analyzeSpecifier(filename, reExport.source);
|
|
380
518
|
if (target === null) {
|
|
381
|
-
throw new
|
|
519
|
+
throw new MoonsError2({
|
|
382
520
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
383
521
|
file: filename,
|
|
384
522
|
message: `tidak bisa me-resolve \`${reExport.source}\` untuk re-export`,
|
|
@@ -396,7 +534,7 @@ function renderServerFnStub2(source, filename, analysis, dependencies) {
|
|
|
396
534
|
for (const specifier of analysis.exportAllFrom) {
|
|
397
535
|
const target = dependencies.analyzeSpecifier(filename, specifier);
|
|
398
536
|
if (target === null) {
|
|
399
|
-
throw new
|
|
537
|
+
throw new MoonsError2({
|
|
400
538
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
401
539
|
file: filename,
|
|
402
540
|
message: `\`export *\` dari \`${specifier}\` tidak bisa di-resolve`,
|
|
@@ -464,7 +602,7 @@ async function serverFnExportNamesAsync(analysis, analyzeSpecifier, seen = new S
|
|
|
464
602
|
return names;
|
|
465
603
|
}
|
|
466
604
|
function nonContractServerFileError2(file, projectRoot) {
|
|
467
|
-
return new
|
|
605
|
+
return new MoonsError2({
|
|
468
606
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
469
607
|
file: toRelative(projectRoot, file),
|
|
470
608
|
message: "file server memakai ekstensi di luar kontrak §12",
|
|
@@ -479,6 +617,32 @@ async function checkClientModuleImports2(context) {
|
|
|
479
617
|
const { file, code, projectRoot, resolve, analyze } = context;
|
|
480
618
|
const ast = parseModule(code, file);
|
|
481
619
|
const relativeFile = toRelative(projectRoot, file);
|
|
620
|
+
const serverOnly = createServerOnlyClassifier({
|
|
621
|
+
configured: context.serverOnlyPackages ?? [],
|
|
622
|
+
...context.packageCache === undefined ? {} : { cache: context.packageCache }
|
|
623
|
+
});
|
|
624
|
+
let apiDirReal = null;
|
|
625
|
+
if (context.apiDir !== undefined && context.apiDir.length > 0) {
|
|
626
|
+
try {
|
|
627
|
+
apiDirReal = realpathSync(context.apiDir);
|
|
628
|
+
} catch {
|
|
629
|
+
apiDirReal = context.apiDir;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
const rejectApiImport = (line, specifier, resolvedTarget) => {
|
|
633
|
+
const relApi = toRelative(projectRoot, resolvedTarget);
|
|
634
|
+
throw new MoonsError2({
|
|
635
|
+
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
636
|
+
file: relativeFile,
|
|
637
|
+
line,
|
|
638
|
+
message: `mengimpor \`${specifier}\` — \`${relApi}\` adalah modul API server-only`,
|
|
639
|
+
details: "`src/api/**` hanya berjalan di server (endpoint HTTP) dan tidak pernah masuk bundle client (PRD Addendum D.1).",
|
|
640
|
+
suggestions: [
|
|
641
|
+
"Pindahkan operasi tersebut ke `*.server.ts` dan panggil lewat serverFn",
|
|
642
|
+
"Jangan mengimpor file `src/api` dari route/komponen client"
|
|
643
|
+
]
|
|
644
|
+
});
|
|
645
|
+
};
|
|
482
646
|
const resolveSafe = async (specifier, importer) => {
|
|
483
647
|
try {
|
|
484
648
|
const result = await resolve(specifier, importer);
|
|
@@ -508,7 +672,7 @@ async function checkClientModuleImports2(context) {
|
|
|
508
672
|
return names.has(name);
|
|
509
673
|
};
|
|
510
674
|
const reject = (line, message, details) => {
|
|
511
|
-
throw new
|
|
675
|
+
throw new MoonsError2({
|
|
512
676
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
513
677
|
file: relativeFile,
|
|
514
678
|
line,
|
|
@@ -521,7 +685,7 @@ async function checkClientModuleImports2(context) {
|
|
|
521
685
|
});
|
|
522
686
|
};
|
|
523
687
|
const rejectForbidden = (line, specifier) => {
|
|
524
|
-
throw new
|
|
688
|
+
throw new MoonsError2({
|
|
525
689
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
526
690
|
file: relativeFile,
|
|
527
691
|
line,
|
|
@@ -542,10 +706,31 @@ async function checkClientModuleImports2(context) {
|
|
|
542
706
|
const rawSpecifier = sourceNode["value"];
|
|
543
707
|
const specifier = stripQuery2(rawSpecifier);
|
|
544
708
|
const line = statement.loc?.start.line ?? 1;
|
|
709
|
+
const typeOnly = isTypeOnlyImportExport(statement);
|
|
545
710
|
if (specifier.startsWith("node:") || specifier.startsWith("bun:")) {
|
|
546
711
|
rejectForbidden(line, specifier);
|
|
547
712
|
}
|
|
548
713
|
const resolvedTarget = await resolveSafe(specifier, file);
|
|
714
|
+
if (apiDirReal !== null && resolvedTarget !== null && isInsideDir(resolvedTarget, apiDirReal)) {
|
|
715
|
+
rejectApiImport(line, rawSpecifier, resolvedTarget);
|
|
716
|
+
}
|
|
717
|
+
if (!typeOnly) {
|
|
718
|
+
const match = serverOnly.check({
|
|
719
|
+
specifier,
|
|
720
|
+
resolvedFile: resolvedTarget,
|
|
721
|
+
importerFile: file
|
|
722
|
+
});
|
|
723
|
+
if (match !== null) {
|
|
724
|
+
throw serverOnlyImportError({
|
|
725
|
+
relativeFile,
|
|
726
|
+
line,
|
|
727
|
+
specifier: rawSpecifier,
|
|
728
|
+
packageName: match.packageName,
|
|
729
|
+
source: match.source,
|
|
730
|
+
...match.source === "marker" ? { markerPath: toRelative(projectRoot, join2(match.packageDir, "package.json")) } : {}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
}
|
|
549
734
|
if (resolvedTarget !== null && isNonContractServerFile2(resolvedTarget)) {
|
|
550
735
|
throw nonContractServerFileError2(resolvedTarget, projectRoot);
|
|
551
736
|
}
|
|
@@ -611,7 +796,7 @@ async function checkClientModuleImports2(context) {
|
|
|
611
796
|
return;
|
|
612
797
|
}
|
|
613
798
|
const callText = code.slice(node.start ?? 0, node.end ?? 0).replace(/\s+/g, " ").slice(0, 80);
|
|
614
|
-
throw new
|
|
799
|
+
throw new MoonsError2({
|
|
615
800
|
code: "E_ENV_IN_CLIENT",
|
|
616
801
|
file: relativeFile,
|
|
617
802
|
line: node.loc?.start.line ?? 1,
|
|
@@ -640,6 +825,25 @@ async function checkClientModuleImports2(context) {
|
|
|
640
825
|
}
|
|
641
826
|
const specifier = stripQuery2(request.text);
|
|
642
827
|
const resolvedTarget = await resolveSafe(specifier, file);
|
|
828
|
+
if (apiDirReal !== null && resolvedTarget !== null && isInsideDir(resolvedTarget, apiDirReal)) {
|
|
829
|
+
rejectApiImport(request.node.loc?.start.line ?? 1, request.text, resolvedTarget);
|
|
830
|
+
}
|
|
831
|
+
const match = serverOnly.check({
|
|
832
|
+
specifier,
|
|
833
|
+
resolvedFile: resolvedTarget,
|
|
834
|
+
importerFile: file
|
|
835
|
+
});
|
|
836
|
+
if (match !== null) {
|
|
837
|
+
throw serverOnlyImportError({
|
|
838
|
+
relativeFile,
|
|
839
|
+
line: request.node.loc?.start.line ?? 1,
|
|
840
|
+
specifier: request.text,
|
|
841
|
+
packageName: match.packageName,
|
|
842
|
+
source: match.source,
|
|
843
|
+
dynamic: true,
|
|
844
|
+
...match.source === "marker" ? { markerPath: toRelative(projectRoot, join2(match.packageDir, "package.json")) } : {}
|
|
845
|
+
});
|
|
846
|
+
}
|
|
643
847
|
if (resolvedTarget !== null && isNonContractServerFile2(resolvedTarget)) {
|
|
644
848
|
throw nonContractServerFileError2(resolvedTarget, projectRoot);
|
|
645
849
|
}
|
|
@@ -668,7 +872,7 @@ async function checkClientModuleImports2(context) {
|
|
|
668
872
|
if (!isServerSpecifier2(pattern)) {
|
|
669
873
|
continue;
|
|
670
874
|
}
|
|
671
|
-
throw new
|
|
875
|
+
throw new MoonsError2({
|
|
672
876
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
673
877
|
file: relativeFile,
|
|
674
878
|
line: node.loc?.start.line ?? 1,
|
|
@@ -754,6 +958,10 @@ function toRelative(projectRoot, file) {
|
|
|
754
958
|
const relative = file.startsWith(projectRoot) ? file.slice(projectRoot.length).replace(/^[/\\]/, "") : file;
|
|
755
959
|
return relative.split("\\").join("/");
|
|
756
960
|
}
|
|
961
|
+
function isInsideDir(file, dir) {
|
|
962
|
+
const normalized = dir.endsWith("/") ? dir : `${dir}/`;
|
|
963
|
+
return file === dir || file.startsWith(normalized);
|
|
964
|
+
}
|
|
757
965
|
function checkRouteModuleExports2(context) {
|
|
758
966
|
const { file, code, routesDir, projectRoot } = context;
|
|
759
967
|
if (!file.startsWith(routesDir) || !file.endsWith(".tsx")) {
|
|
@@ -763,7 +971,7 @@ function checkRouteModuleExports2(context) {
|
|
|
763
971
|
if (base.startsWith("_") && base !== "_layout.tsx" && base !== "_error.tsx" && base !== "_404.tsx") {
|
|
764
972
|
return;
|
|
765
973
|
}
|
|
766
|
-
const allowed = base === "_layout.tsx" ? ["default", "loader"] : base === "_error.tsx" || base === "_404.tsx" ? ["default"] : ["default", "loader", "meta"];
|
|
974
|
+
const allowed = base === "_layout.tsx" ? ["default", "loader"] : base === "_error.tsx" || base === "_404.tsx" ? ["default"] : ["default", "loader", "meta", "prerender", "paths"];
|
|
767
975
|
const allowedText = `Hanya ${allowed.map((name) => `\`${name}\``).join(", ")} yang boleh di-export.`;
|
|
768
976
|
const relativeFile = toRelative(projectRoot, file);
|
|
769
977
|
const ast = parseModule(code, file);
|
|
@@ -775,7 +983,7 @@ function checkRouteModuleExports2(context) {
|
|
|
775
983
|
continue;
|
|
776
984
|
}
|
|
777
985
|
if (statement.type === "ExportAllDeclaration") {
|
|
778
|
-
throw new
|
|
986
|
+
throw new MoonsError2({
|
|
779
987
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
780
988
|
file: relativeFile,
|
|
781
989
|
line: statement.loc?.start.line ?? 1,
|
|
@@ -806,7 +1014,7 @@ function checkRouteModuleExports2(context) {
|
|
|
806
1014
|
}
|
|
807
1015
|
for (const entry of declarations) {
|
|
808
1016
|
if (!allowed.includes(entry.name)) {
|
|
809
|
-
throw new
|
|
1017
|
+
throw new MoonsError2({
|
|
810
1018
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
811
1019
|
file: relativeFile,
|
|
812
1020
|
line: entry.line,
|
|
@@ -820,7 +1028,7 @@ function checkRouteModuleExports2(context) {
|
|
|
820
1028
|
}
|
|
821
1029
|
}
|
|
822
1030
|
if (!hasDefault) {
|
|
823
|
-
throw new
|
|
1031
|
+
throw new MoonsError2({
|
|
824
1032
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
825
1033
|
file: relativeFile,
|
|
826
1034
|
message: "modul route wajib mengekspor `default`",
|
|
@@ -852,9 +1060,9 @@ function scanServerFiles2(projectRoot) {
|
|
|
852
1060
|
if (excluded.has(entry.name)) {
|
|
853
1061
|
continue;
|
|
854
1062
|
}
|
|
855
|
-
walkDir(
|
|
1063
|
+
walkDir(join2(dir, entry.name));
|
|
856
1064
|
} else if (entry.isFile() && isServerFilePath2(entry.name)) {
|
|
857
|
-
result.push(
|
|
1065
|
+
result.push(join2(dir, entry.name));
|
|
858
1066
|
}
|
|
859
1067
|
}
|
|
860
1068
|
};
|
|
@@ -866,7 +1074,7 @@ function buildFnRegistry2(projectRoot, files) {
|
|
|
866
1074
|
for (const file of files) {
|
|
867
1075
|
let source;
|
|
868
1076
|
try {
|
|
869
|
-
source =
|
|
1077
|
+
source = readFileSync2(file, "utf8");
|
|
870
1078
|
} catch {
|
|
871
1079
|
continue;
|
|
872
1080
|
}
|
|
@@ -880,4 +1088,4 @@ function buildFnRegistry2(projectRoot, files) {
|
|
|
880
1088
|
|
|
881
1089
|
export { isServerFilePath2, isNonContractServerFile2, isServerSpecifier2, resolveServerSpecifierSync2, stripQuery2, analyzeServerFile2, serverFnExportNames2, renderServerFnStub2, injectServerFnMeta2, nonContractServerFileError2, checkClientModuleImports2, checkRouteModuleExports2, scanServerFiles2, buildFnRegistry2 };
|
|
882
1090
|
|
|
883
|
-
//# debugId=
|
|
1091
|
+
//# debugId=72CC55CAEBB11FDE64756E2164756E21
|