@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
package/src/server/prerender.ts
CHANGED
|
@@ -80,11 +80,11 @@ export async function runPrerender(options: PrerenderOptions): Promise<Prerender
|
|
|
80
80
|
throw new MoonsError({
|
|
81
81
|
code: "E_ISR_INVALID",
|
|
82
82
|
file: route.file,
|
|
83
|
-
message: "`revalidate`
|
|
84
|
-
details: "ISR
|
|
83
|
+
message: "`revalidate` is invalid or used without `prerender = true`",
|
|
84
|
+
details: "File-based ISR: `revalidate` (seconds, number >= 0) + `prerender`.",
|
|
85
85
|
suggestions: [
|
|
86
|
-
"
|
|
87
|
-
"
|
|
86
|
+
"Use a number of seconds, e.g. `export const revalidate = 60`",
|
|
87
|
+
"Add `export const prerender = true` to the same page",
|
|
88
88
|
],
|
|
89
89
|
});
|
|
90
90
|
}
|
|
@@ -92,7 +92,7 @@ export async function runPrerender(options: PrerenderOptions): Promise<Prerender
|
|
|
92
92
|
|
|
93
93
|
if (prerender !== true) {
|
|
94
94
|
if (prerender !== undefined && prerender !== false) {
|
|
95
|
-
throw prerenderError(route, "`prerender`
|
|
95
|
+
throw prerenderError(route, "`prerender` must be a boolean (`true`/`false`)");
|
|
96
96
|
}
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
@@ -107,15 +107,15 @@ export async function runPrerender(options: PrerenderOptions): Promise<Prerender
|
|
|
107
107
|
);
|
|
108
108
|
|
|
109
109
|
if (response.status === 404) {
|
|
110
|
-
log(`[moons] prerender:
|
|
110
|
+
log(`[moons] prerender: skipped ${pathname} (notFound)`);
|
|
111
111
|
skipped += 1;
|
|
112
112
|
continue;
|
|
113
113
|
}
|
|
114
114
|
if (response.status >= 300 && response.status < 400) {
|
|
115
|
-
throw prerenderError(route, `\`redirect()\`
|
|
115
|
+
throw prerenderError(route, `\`redirect()\` is not supported during prerender (${pathname})`);
|
|
116
116
|
}
|
|
117
117
|
if (response.status !== 200) {
|
|
118
|
-
throw prerenderError(route, `status ${response.status}
|
|
118
|
+
throw prerenderError(route, `status ${response.status} while rendering ${pathname}`);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const html = await response.text();
|
|
@@ -152,7 +152,7 @@ export async function runPrerender(options: PrerenderOptions): Promise<Prerender
|
|
|
152
152
|
|
|
153
153
|
const extras = [...(wrote404 ? [NOT_FOUND_FILE] : []), ...emitted];
|
|
154
154
|
log(
|
|
155
|
-
`[moons] prerender
|
|
155
|
+
`[moons] prerender done: ${written} file${extras.length > 0 ? ` + ${extras.join(" + ")}` : ""} (${skipped} skipped)`,
|
|
156
156
|
);
|
|
157
157
|
return { written, skipped };
|
|
158
158
|
}
|
|
@@ -237,7 +237,7 @@ async function renderStaticNotFound(
|
|
|
237
237
|
);
|
|
238
238
|
if (response.status !== 404) {
|
|
239
239
|
log(
|
|
240
|
-
`[moons] prerender: ${NOT_FOUND_FILE}
|
|
240
|
+
`[moons] prerender: ${NOT_FOUND_FILE} skipped (sentinel \`${NOT_FOUND_SENTINEL}\` → ${response.status})`,
|
|
241
241
|
);
|
|
242
242
|
return null;
|
|
243
243
|
}
|
|
@@ -267,11 +267,11 @@ function assertNoLeak(
|
|
|
267
267
|
throw new MoonsError({
|
|
268
268
|
code: "E_PRERENDER_LEAK",
|
|
269
269
|
file,
|
|
270
|
-
message: `
|
|
271
|
-
details: "HTML
|
|
270
|
+
message: `prerender output \`${pathname}\` contains ${labels}`,
|
|
271
|
+
details: "Static HTML is permanent — a prerendered secret cannot be retracted.",
|
|
272
272
|
suggestions: [
|
|
273
|
-
"
|
|
274
|
-
"
|
|
273
|
+
"Do not return secret env values from a loader that runs at prerender time",
|
|
274
|
+
"Turn off `prerender` for this route or move the data to a request-time serverFn",
|
|
275
275
|
],
|
|
276
276
|
});
|
|
277
277
|
}
|
|
@@ -291,37 +291,37 @@ async function resolvePathnames(route: RouteDefinition, module: RouteModule): Pr
|
|
|
291
291
|
throw new MoonsError({
|
|
292
292
|
code: "E_PRERENDER_NO_PATHS",
|
|
293
293
|
file: route.file,
|
|
294
|
-
message: "route
|
|
295
|
-
details: "
|
|
294
|
+
message: "dynamic route with `prerender = true` without `export const paths`",
|
|
295
|
+
details: "Prerendering needs a param list to render each path.",
|
|
296
296
|
suggestions: [
|
|
297
|
-
'
|
|
298
|
-
"
|
|
297
|
+
'Add `export const paths = () => [{ id: "…" }]`',
|
|
298
|
+
"Or remove `prerender` so the route renders at request time (SSR)",
|
|
299
299
|
],
|
|
300
300
|
});
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
const raw = await (paths as () => unknown | Promise<unknown>)();
|
|
304
304
|
if (!Array.isArray(raw)) {
|
|
305
|
-
throw prerenderError(route, "`paths()`
|
|
305
|
+
throw prerenderError(route, "`paths()` must return an array");
|
|
306
306
|
}
|
|
307
307
|
|
|
308
308
|
const expected = new Set(route.params);
|
|
309
309
|
const result: string[] = [];
|
|
310
310
|
for (const item of raw as unknown[]) {
|
|
311
311
|
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
312
|
-
throw prerenderError(route, "
|
|
312
|
+
throw prerenderError(route, "`paths()` items must be objects with string params");
|
|
313
313
|
}
|
|
314
314
|
const record = item as Record<string, unknown>;
|
|
315
315
|
const keys = Object.keys(record);
|
|
316
316
|
for (const name of expected) {
|
|
317
317
|
const value = record[name];
|
|
318
318
|
if (typeof value !== "string" || value.length === 0) {
|
|
319
|
-
throw prerenderError(route, `param \`${name}\`
|
|
319
|
+
throw prerenderError(route, `param \`${name}\` must be a non-empty string`);
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
for (const key of keys) {
|
|
323
323
|
if (!expected.has(key)) {
|
|
324
|
-
throw prerenderError(route, `param \`${key}\`
|
|
324
|
+
throw prerenderError(route, `param \`${key}\` is not in the route pattern`);
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
result.push(buildPathname(route, record as Record<string, string>));
|
|
@@ -356,7 +356,7 @@ function buildPathname(route: RouteDefinition, params: Record<string, string>):
|
|
|
356
356
|
|
|
357
357
|
function encodeSegment(route: RouteDefinition, value: string): string {
|
|
358
358
|
if (value.length === 0 || /^\.+$/.test(value)) {
|
|
359
|
-
throw prerenderError(route, `
|
|
359
|
+
throw prerenderError(route, `param value \`${value}\` is not valid for a file path`);
|
|
360
360
|
}
|
|
361
361
|
return encodeURIComponent(value);
|
|
362
362
|
}
|
|
@@ -368,9 +368,9 @@ function outputFile(clientDir: string, pathname: string): string {
|
|
|
368
368
|
if (file !== base && !file.startsWith(base + sep)) {
|
|
369
369
|
throw new MoonsError({
|
|
370
370
|
code: "E_PRERENDER_INVALID",
|
|
371
|
-
message: `
|
|
372
|
-
details: "
|
|
373
|
-
suggestions: ["
|
|
371
|
+
message: `prerender path escapes the client directory (\`${pathname}\`)`,
|
|
372
|
+
details: "The file name must stay inside `dist/client`.",
|
|
373
|
+
suggestions: ["Check the `paths` param values — do not use `..`"],
|
|
374
374
|
});
|
|
375
375
|
}
|
|
376
376
|
return file;
|
|
@@ -381,10 +381,10 @@ function prerenderError(route: RouteDefinition, message: string): MoonsError {
|
|
|
381
381
|
code: "E_PRERENDER_INVALID",
|
|
382
382
|
file: route.file,
|
|
383
383
|
message,
|
|
384
|
-
details: "
|
|
384
|
+
details: "The prerender contract is enforced.",
|
|
385
385
|
suggestions: [
|
|
386
|
-
"
|
|
387
|
-
"
|
|
386
|
+
"Fix the `paths`/params shape so it matches the route pattern",
|
|
387
|
+
"Do not use `redirect()` on a prerendered route",
|
|
388
388
|
],
|
|
389
389
|
});
|
|
390
390
|
}
|
package/src/server/shell.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const ROOT_PLACEHOLDER = '<div id="root"></div>';
|
|
|
14
14
|
|
|
15
15
|
export const DEFAULT_TEMPLATE = [
|
|
16
16
|
"<!doctype html>",
|
|
17
|
-
'<html lang="
|
|
17
|
+
'<html lang="en">',
|
|
18
18
|
"<head>",
|
|
19
19
|
' <meta charset="utf-8" />',
|
|
20
20
|
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
@@ -38,11 +38,11 @@ export function injectShell(
|
|
|
38
38
|
if (!template.includes(ROOT_PLACEHOLDER)) {
|
|
39
39
|
throw new MoonsError({
|
|
40
40
|
code: "E_INTERNAL",
|
|
41
|
-
message: `shell HTML
|
|
42
|
-
details: "index.html
|
|
41
|
+
message: `shell HTML does not contain \`${ROOT_PLACEHOLDER}\``,
|
|
42
|
+
details: "The app's index.html must contain that root element so SSR can inject markup.",
|
|
43
43
|
suggestions: [
|
|
44
|
-
`
|
|
45
|
-
"
|
|
44
|
+
`Add ${ROOT_PLACEHOLDER} to index.html`,
|
|
45
|
+
"Or remove index.html to use the built-in shell",
|
|
46
46
|
],
|
|
47
47
|
});
|
|
48
48
|
}
|
|
@@ -119,9 +119,9 @@ export function createFileShell(options: { clientDir?: string } = {}): Shell {
|
|
|
119
119
|
throw new MoonsError({
|
|
120
120
|
code: "E_INTERNAL",
|
|
121
121
|
file,
|
|
122
|
-
message: "`dist/client/index.html`
|
|
123
|
-
details: "
|
|
124
|
-
suggestions: ["
|
|
122
|
+
message: "`dist/client/index.html` not found",
|
|
123
|
+
details: "The client build must run before starting the production server.",
|
|
124
|
+
suggestions: ["Run `bun run build` first"],
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
cached = readFileSync(file, "utf8");
|
package/src/server/shutdown.ts
CHANGED
|
@@ -36,10 +36,10 @@ export function installGracefulShutdown(
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
shuttingDown = true;
|
|
39
|
-
log(`[moons]
|
|
39
|
+
log(`[moons] received ${signal} — no longer accepting connections, draining...`);
|
|
40
40
|
|
|
41
41
|
const timer = setTimeout(() => {
|
|
42
|
-
log(`[moons] drain
|
|
42
|
+
log(`[moons] drain exceeded ${timeoutMs}ms — forcing exit`);
|
|
43
43
|
const closeAll = (server as { closeAllConnections?: () => void }).closeAllConnections;
|
|
44
44
|
try {
|
|
45
45
|
closeAll?.call(server);
|
|
@@ -59,7 +59,7 @@ export function installGracefulShutdown(
|
|
|
59
59
|
server.close(async (error?: Error) => {
|
|
60
60
|
if (error !== undefined) {
|
|
61
61
|
clearTimeout(timer);
|
|
62
|
-
log(`[moons]
|
|
62
|
+
log(`[moons] server close failed: ${error.message}`);
|
|
63
63
|
exit(1);
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -70,11 +70,11 @@ export function installGracefulShutdown(
|
|
|
70
70
|
try {
|
|
71
71
|
await options.onShutdown();
|
|
72
72
|
} catch (hookError) {
|
|
73
|
-
log(`[moons] onShutdown
|
|
73
|
+
log(`[moons] onShutdown failed: ${String(hookError)}`);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
clearTimeout(timer);
|
|
77
|
-
log("[moons] shutdown
|
|
77
|
+
log("[moons] shutdown complete (full drain)");
|
|
78
78
|
exit(0);
|
|
79
79
|
});
|
|
80
80
|
};
|
package/src/server/storage.ts
CHANGED
|
@@ -21,7 +21,7 @@ export function createFileStorage(clientDir: string): PrerenderStorage {
|
|
|
21
21
|
async write(pathname: string, body: Uint8Array): Promise<void> {
|
|
22
22
|
const target = staticFileTarget(clientDir, pathname);
|
|
23
23
|
if (target === null) {
|
|
24
|
-
throw new Error(`[moons] isr: path
|
|
24
|
+
throw new Error(`[moons] isr: invalid path to write: ${pathname}`);
|
|
25
25
|
}
|
|
26
26
|
mkdirSync(dirname(target), { recursive: true });
|
|
27
27
|
const tmp = `${target}.tmp-${process.pid}`;
|
package/src/server-only.ts
CHANGED
|
@@ -202,19 +202,19 @@ export function serverOnlyImportError(input: {
|
|
|
202
202
|
}): MoonsError {
|
|
203
203
|
const sourceLabel =
|
|
204
204
|
input.source === "builtin"
|
|
205
|
-
? "
|
|
205
|
+
? "Match source: framework built-in server-only package list."
|
|
206
206
|
: input.source === "config"
|
|
207
|
-
? "
|
|
208
|
-
: `
|
|
207
|
+
? "Match source: `security.serverOnlyPackages` in moons.config.ts."
|
|
208
|
+
: `Match source: marker \`"moons": { "serverOnly": true }\` in ${input.markerPath ?? "package.json"}.`;
|
|
209
209
|
return new MoonsError({
|
|
210
210
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
211
211
|
file: input.relativeFile,
|
|
212
212
|
line: input.line,
|
|
213
|
-
message: `
|
|
214
|
-
details: `${sourceLabel}\
|
|
213
|
+
message: `importing${input.dynamic === true ? " (dynamic)" : ""} \`${input.specifier}\` — server-only package \`${input.packageName}\``,
|
|
214
|
+
details: `${sourceLabel}\nServer-only packages must not enter the client bundle.`,
|
|
215
215
|
suggestions: [
|
|
216
|
-
"
|
|
217
|
-
"
|
|
216
|
+
"Move the operation into `*.server.ts` and call it via a serverFn",
|
|
217
|
+
"Or replace/remove the import in the client module",
|
|
218
218
|
],
|
|
219
219
|
});
|
|
220
220
|
}
|
package/src/transform.ts
CHANGED
|
@@ -552,17 +552,17 @@ export function renderServerFnStub(
|
|
|
552
552
|
file: filename,
|
|
553
553
|
line: fn.line,
|
|
554
554
|
annotation: fn.name,
|
|
555
|
-
message: `schema ${kind} serverFn \`${fn.name}\`
|
|
555
|
+
message: `schema ${kind} of serverFn \`${fn.name}\` references server value \`${reference}\` from \`${serverImport.specifier}\``,
|
|
556
556
|
details:
|
|
557
|
-
`\`${reference}\`
|
|
558
|
-
"
|
|
559
|
-
" // a)
|
|
557
|
+
`\`${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).\n\n` +
|
|
558
|
+
"Pick one:\n" +
|
|
559
|
+
" // a) move the constant/schema to a client-safe module\n" +
|
|
560
560
|
` import { ${reference} } from "../shared/schemas";\n` +
|
|
561
|
-
" // b) inline
|
|
562
|
-
" // c)
|
|
561
|
+
" // b) inline the value if it is genuinely public in the schema, or\n" +
|
|
562
|
+
" // c) do extra validation in the server handler without referencing server values in the schema",
|
|
563
563
|
suggestions: [
|
|
564
|
-
"
|
|
565
|
-
"
|
|
564
|
+
"Move the referenced declaration to a client-safe shared module (e.g. `src/shared/schemas.ts`)",
|
|
565
|
+
"Or do the validation in the server handler instead of the schema expression",
|
|
566
566
|
],
|
|
567
567
|
});
|
|
568
568
|
}
|
|
@@ -576,21 +576,21 @@ export function renderServerFnStub(
|
|
|
576
576
|
file: filename,
|
|
577
577
|
line: fn.line,
|
|
578
578
|
annotation: fn.name,
|
|
579
|
-
message: `schema ${kind} serverFn \`${fn.name}\`
|
|
579
|
+
message: `schema ${kind} of serverFn \`${fn.name}\` references local declaration \`${reference}\``,
|
|
580
580
|
details:
|
|
581
|
-
`\`${reference}\`
|
|
582
|
-
"
|
|
583
|
-
` const ${reference} = z.object({ … }); //
|
|
581
|
+
`\`${reference}\` is dropped from the client stub, so its value is \`undefined\` in the browser (hydration crash).\n\n` +
|
|
582
|
+
"Before (rejected):\n" +
|
|
583
|
+
` const ${reference} = z.object({ … }); // local declaration in the same file\n` +
|
|
584
584
|
` export const ${fn.name} = serverFn({ ${kind}: ${reference}, … });\n\n` +
|
|
585
|
-
"
|
|
586
|
-
` // a) inline schema
|
|
585
|
+
"After (pick one):\n" +
|
|
586
|
+
` // a) inline the schema in the argument\n` +
|
|
587
587
|
` export const ${fn.name} = serverFn({ ${kind}: z.object({ … }), … });\n` +
|
|
588
|
-
` // b)
|
|
588
|
+
` // b) move the declaration to a shared module that may enter the client (e.g. src/shared/schemas.ts)\n` +
|
|
589
589
|
` import { ${reference} } from "../shared/schemas";`,
|
|
590
590
|
suggestions: [
|
|
591
|
-
`Inline schema
|
|
592
|
-
`
|
|
593
|
-
"
|
|
591
|
+
`Inline the schema in the argument \`serverFn({ ${kind}: … })\`, or`,
|
|
592
|
+
`Move \`${reference}\` to a shared module (e.g. \`src/shared/schemas.ts\`) and import it from there`,
|
|
593
|
+
"Never move secret values to shared — the schema must be client-safe (zod/Standard Schema)",
|
|
594
594
|
],
|
|
595
595
|
});
|
|
596
596
|
}
|
|
@@ -607,9 +607,9 @@ export function renderServerFnStub(
|
|
|
607
607
|
throw new MoonsError({
|
|
608
608
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
609
609
|
file: filename,
|
|
610
|
-
message: `
|
|
611
|
-
details: "Re-
|
|
612
|
-
suggestions: ["
|
|
610
|
+
message: `cannot resolve \`${reExport.source}\` for re-export`,
|
|
611
|
+
details: "Re-exports from `*.server.ts` must be statically resolvable.",
|
|
612
|
+
suggestions: ["Fix the re-export path", "Avoid dynamic re-exports from server files"],
|
|
613
613
|
});
|
|
614
614
|
}
|
|
615
615
|
const targetNames = serverFnExportNames(target, (specifier) =>
|
|
@@ -630,9 +630,9 @@ export function renderServerFnStub(
|
|
|
630
630
|
throw new MoonsError({
|
|
631
631
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
632
632
|
file: filename,
|
|
633
|
-
message: `\`export *\`
|
|
634
|
-
details: "`export *`
|
|
635
|
-
suggestions: ["
|
|
633
|
+
message: `\`export *\` from \`${specifier}\` cannot be resolved`,
|
|
634
|
+
details: "`export *` from `*.server.ts` must be statically expandable.",
|
|
635
|
+
suggestions: ["Fix the re-export path", "Replace it with named re-exports"],
|
|
636
636
|
});
|
|
637
637
|
}
|
|
638
638
|
const targetNames = serverFnExportNames(target, (nested) =>
|
|
@@ -646,7 +646,7 @@ export function renderServerFnStub(
|
|
|
646
646
|
}
|
|
647
647
|
|
|
648
648
|
if (importLines.length === 0 && exportLines.length === 0) {
|
|
649
|
-
return { code: "// @moons:
|
|
649
|
+
return { code: "// @moons: no serverFn exported\nexport {};\n", referenced };
|
|
650
650
|
}
|
|
651
651
|
|
|
652
652
|
return { code: `${[...importLines, "", ...exportLines].join("\n")}\n`, referenced };
|
|
@@ -747,12 +747,12 @@ export function nonContractServerFileError(file: string, projectRoot: string): M
|
|
|
747
747
|
return new MoonsError({
|
|
748
748
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
749
749
|
file: toRelative(projectRoot, file),
|
|
750
|
-
message: "file
|
|
750
|
+
message: "server file uses an extension outside the `*.server.ts` contract",
|
|
751
751
|
details:
|
|
752
|
-
"
|
|
752
|
+
"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.",
|
|
753
753
|
suggestions: [
|
|
754
|
-
"
|
|
755
|
-
"
|
|
754
|
+
"Rename the file to `*.server.ts`/`*.server.tsx` and put the `serverFn` inside it",
|
|
755
|
+
"Or move the value you need into a non-server file",
|
|
756
756
|
],
|
|
757
757
|
});
|
|
758
758
|
}
|
|
@@ -942,12 +942,12 @@ export async function checkClientModuleImports(
|
|
|
942
942
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
943
943
|
file: relativeFile,
|
|
944
944
|
line,
|
|
945
|
-
message: `
|
|
945
|
+
message: `importing \`${specifier}\` — \`moons.config.ts\` is a server-only module`,
|
|
946
946
|
details:
|
|
947
|
-
"
|
|
947
|
+
"The config contains `context`/`middleware` (server code) and never enters the client bundle (Addendum J.4).",
|
|
948
948
|
suggestions: [
|
|
949
|
-
"
|
|
950
|
-
"
|
|
949
|
+
"Move the value you need into a client-safe module (e.g. `src/shared/…`)",
|
|
950
|
+
"Get the value from the server via a loader/serverFn",
|
|
951
951
|
],
|
|
952
952
|
});
|
|
953
953
|
};
|
|
@@ -957,12 +957,12 @@ export async function checkClientModuleImports(
|
|
|
957
957
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
958
958
|
file: relativeFile,
|
|
959
959
|
line,
|
|
960
|
-
message: `
|
|
960
|
+
message: `importing \`${specifier}\` — \`${toRelative(projectRoot, resolvedTarget)}\` is inside \`src/server/**\` (server-only)`,
|
|
961
961
|
details:
|
|
962
|
-
"
|
|
962
|
+
"The `src/server/**` directory is for server code only (Addendum J.4); the `.server.ts` suffix is not the only guarantee.",
|
|
963
963
|
suggestions: [
|
|
964
|
-
"
|
|
965
|
-
"
|
|
964
|
+
"Move client-safe code to `src/shared/`",
|
|
965
|
+
"Access the value from the server via a loader/serverFn",
|
|
966
966
|
],
|
|
967
967
|
});
|
|
968
968
|
};
|
|
@@ -973,12 +973,12 @@ export async function checkClientModuleImports(
|
|
|
973
973
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
974
974
|
file: relativeFile,
|
|
975
975
|
line,
|
|
976
|
-
message: `
|
|
976
|
+
message: `importing \`${specifier}\` — \`${relApi}\` is a server-only API module`,
|
|
977
977
|
details:
|
|
978
|
-
"`src/api/**`
|
|
978
|
+
"`src/api/**` only runs on the server (HTTP endpoints) and never enters the client bundle.",
|
|
979
979
|
suggestions: [
|
|
980
|
-
"
|
|
981
|
-
"
|
|
980
|
+
"Move the operation into `*.server.ts` and call it via a serverFn",
|
|
981
|
+
"Do not import `src/api` files from client routes/components",
|
|
982
982
|
],
|
|
983
983
|
});
|
|
984
984
|
};
|
|
@@ -1034,8 +1034,8 @@ export async function checkClientModuleImports(
|
|
|
1034
1034
|
message,
|
|
1035
1035
|
details,
|
|
1036
1036
|
suggestions: [
|
|
1037
|
-
"
|
|
1038
|
-
"
|
|
1037
|
+
"Create a server function that returns the data you need, or",
|
|
1038
|
+
"If the value is genuinely public, redefine the constant in a client-safe module (never move secret values)",
|
|
1039
1039
|
],
|
|
1040
1040
|
});
|
|
1041
1041
|
};
|
|
@@ -1045,11 +1045,11 @@ export async function checkClientModuleImports(
|
|
|
1045
1045
|
code: "E_FORBIDDEN_SERVER_IMPORT",
|
|
1046
1046
|
file: relativeFile,
|
|
1047
1047
|
line,
|
|
1048
|
-
message: `
|
|
1049
|
-
details: "
|
|
1048
|
+
message: `importing \`${specifier}\` in a client module`,
|
|
1049
|
+
details: "Server-only modules may only be imported from *.server.ts.",
|
|
1050
1050
|
suggestions: [
|
|
1051
|
-
"
|
|
1052
|
-
"
|
|
1051
|
+
"Move the operation into a serverFn, or",
|
|
1052
|
+
"Remove the import",
|
|
1053
1053
|
],
|
|
1054
1054
|
});
|
|
1055
1055
|
};
|
|
@@ -1177,13 +1177,13 @@ export async function checkClientModuleImports(
|
|
|
1177
1177
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
1178
1178
|
file: relativeFile,
|
|
1179
1179
|
line,
|
|
1180
|
-
message: `
|
|
1180
|
+
message: `importing \`${name}\` — comes from \`${toRelative(projectRoot, serverFile)}\` (via a re-export chain)`,
|
|
1181
1181
|
details:
|
|
1182
|
-
`\`${toRelative(projectRoot, resolvedTarget)}\`
|
|
1183
|
-
"
|
|
1182
|
+
`\`${toRelative(projectRoot, resolvedTarget)}\` re-exports a value from a server file; ` +
|
|
1183
|
+
"the client stub does not export it (previously a raw bundler `MISSING_EXPORT` error).",
|
|
1184
1184
|
suggestions: [
|
|
1185
|
-
"
|
|
1186
|
-
"
|
|
1185
|
+
"Create a server function that returns the data you need, or",
|
|
1186
|
+
"Redefine the public constant in a client-safe module (never move secret values)",
|
|
1187
1187
|
],
|
|
1188
1188
|
});
|
|
1189
1189
|
}
|
|
@@ -1197,7 +1197,7 @@ export async function checkClientModuleImports(
|
|
|
1197
1197
|
|
|
1198
1198
|
if (statement.type === "ExportAllDeclaration") {
|
|
1199
1199
|
if (resolvedTarget === null) {
|
|
1200
|
-
reject(line, `\`export *\`
|
|
1200
|
+
reject(line, `\`export *\` from \`${targetRelative}\` cannot be resolved`, "Re-exports from `*.server.ts` must be statically resolvable.");
|
|
1201
1201
|
}
|
|
1202
1202
|
continue; // aman: target menjadi stub
|
|
1203
1203
|
}
|
|
@@ -1205,10 +1205,10 @@ export async function checkClientModuleImports(
|
|
|
1205
1205
|
const specifiers = (statement["specifiers"] ?? []) as AstNode[];
|
|
1206
1206
|
for (const item of specifiers) {
|
|
1207
1207
|
if (item.type === "ImportDefaultSpecifier") {
|
|
1208
|
-
reject(line, `
|
|
1208
|
+
reject(line, `importing the default export from ${targetRelative}`, "Default exports from a server file must not cross to the client.");
|
|
1209
1209
|
}
|
|
1210
1210
|
if (item.type === "ImportNamespaceSpecifier") {
|
|
1211
|
-
reject(line, `
|
|
1211
|
+
reject(line, `importing the namespace (\`* as\`) from ${targetRelative}`, "Namespace imports from a server file must not cross to the client.");
|
|
1212
1212
|
}
|
|
1213
1213
|
const name = ((item["local"] as AstNode | undefined)?.["name"] as string) ?? "";
|
|
1214
1214
|
// Type-only (Addendum J.5): Babel menaruh `importKind: "type"` di
|
|
@@ -1222,8 +1222,8 @@ export async function checkClientModuleImports(
|
|
|
1222
1222
|
if (name.length > 0 && !(await isAllowedServerFnImport(resolvedTarget, specifier, name))) {
|
|
1223
1223
|
reject(
|
|
1224
1224
|
line,
|
|
1225
|
-
`
|
|
1226
|
-
"
|
|
1225
|
+
`importing \`${name}\` from ${targetRelative}`,
|
|
1226
|
+
"Server code must not appear in the client.\nOnly `serverFn` may cross.",
|
|
1227
1227
|
);
|
|
1228
1228
|
}
|
|
1229
1229
|
}
|
|
@@ -1270,11 +1270,11 @@ export async function checkClientModuleImports(
|
|
|
1270
1270
|
code: "E_ENV_IN_CLIENT",
|
|
1271
1271
|
file: relativeFile,
|
|
1272
1272
|
line: node.loc?.start.line ?? 1,
|
|
1273
|
-
message: `
|
|
1274
|
-
details: "`env()`
|
|
1273
|
+
message: `calling \`${callText}\` in a client module`,
|
|
1274
|
+
details: "`env()` is only legal on the server.",
|
|
1275
1275
|
suggestions: [
|
|
1276
|
-
"
|
|
1277
|
-
"
|
|
1276
|
+
"Move the call into a serverFn, or",
|
|
1277
|
+
"Use import.meta.env for public values",
|
|
1278
1278
|
],
|
|
1279
1279
|
});
|
|
1280
1280
|
});
|
|
@@ -1338,7 +1338,7 @@ export async function checkClientModuleImports(
|
|
|
1338
1338
|
|
|
1339
1339
|
const targetRelative =
|
|
1340
1340
|
resolvedTarget === null ? request.text : toRelative(projectRoot, resolvedTarget);
|
|
1341
|
-
reject(request.node.loc?.start.line ?? 1, `
|
|
1341
|
+
reject(request.node.loc?.start.line ?? 1, `importing (dynamic) \`${request.text}\` from ${targetRelative}`, "The client must not dynamically import `*.server.ts`.");
|
|
1342
1342
|
}
|
|
1343
1343
|
|
|
1344
1344
|
// `import.meta.glob` yang mencakup `*.server.ts` (§12.7) — audit adversarial M6.
|
|
@@ -1368,11 +1368,11 @@ export async function checkClientModuleImports(
|
|
|
1368
1368
|
code: "E_SERVER_IMPORT_IN_CLIENT",
|
|
1369
1369
|
file: relativeFile,
|
|
1370
1370
|
line: node.loc?.start.line ?? 1,
|
|
1371
|
-
message: `
|
|
1372
|
-
details: "`import.meta.glob`
|
|
1371
|
+
message: `importing (glob) \`${pattern}\` in a client module`,
|
|
1372
|
+
details: "`import.meta.glob` patterns covering `*.server.ts` are forbidden in the client.",
|
|
1373
1373
|
suggestions: [
|
|
1374
|
-
"
|
|
1375
|
-
"
|
|
1374
|
+
"Statically import the `serverFn` from `*.server.ts` (that is all that may cross), or",
|
|
1375
|
+
"Move the data requirement into a server function",
|
|
1376
1376
|
],
|
|
1377
1377
|
});
|
|
1378
1378
|
}
|
|
@@ -1506,7 +1506,7 @@ export function checkRouteModuleExports(context: RouteExportCheckContext): void
|
|
|
1506
1506
|
: base === "_error.tsx" || base === "_404.tsx"
|
|
1507
1507
|
? ["default"]
|
|
1508
1508
|
: ["default", "loader", "meta", "prerender", "paths", "revalidate"];
|
|
1509
|
-
const allowedText = `
|
|
1509
|
+
const allowedText = `Only ${allowed.map((name) => `\`${name}\``).join(", ")} may be exported.`;
|
|
1510
1510
|
const relativeFile = toRelative(projectRoot, file);
|
|
1511
1511
|
const ast = parseModule(code, file);
|
|
1512
1512
|
|
|
@@ -1524,11 +1524,11 @@ export function checkRouteModuleExports(context: RouteExportCheckContext): void
|
|
|
1524
1524
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
1525
1525
|
file: relativeFile,
|
|
1526
1526
|
line: statement.loc?.start.line ?? 1,
|
|
1527
|
-
message: "`export *`
|
|
1527
|
+
message: "`export *` is not allowed in route modules",
|
|
1528
1528
|
details: allowedText,
|
|
1529
1529
|
suggestions: [
|
|
1530
|
-
`
|
|
1531
|
-
"
|
|
1530
|
+
`Move the file contents outside ${toRelative(projectRoot, routesDir)}/, or`,
|
|
1531
|
+
"Remove the `export *`",
|
|
1532
1532
|
],
|
|
1533
1533
|
});
|
|
1534
1534
|
}
|
|
@@ -1564,11 +1564,11 @@ export function checkRouteModuleExports(context: RouteExportCheckContext): void
|
|
|
1564
1564
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
1565
1565
|
file: relativeFile,
|
|
1566
1566
|
line: entry.line,
|
|
1567
|
-
message: `export \`${entry.name}\`
|
|
1567
|
+
message: `export \`${entry.name}\` is not allowed in route modules`,
|
|
1568
1568
|
details: allowedText,
|
|
1569
1569
|
suggestions: [
|
|
1570
|
-
`
|
|
1571
|
-
"
|
|
1570
|
+
`Move \`${entry.name}\` to a file outside ${toRelative(projectRoot, routesDir)}/, or`,
|
|
1571
|
+
"Remove the export",
|
|
1572
1572
|
],
|
|
1573
1573
|
});
|
|
1574
1574
|
}
|
|
@@ -1578,9 +1578,9 @@ export function checkRouteModuleExports(context: RouteExportCheckContext): void
|
|
|
1578
1578
|
throw new MoonsError({
|
|
1579
1579
|
code: "E_ROUTE_EXPORT_INVALID",
|
|
1580
1580
|
file: relativeFile,
|
|
1581
|
-
message: "
|
|
1582
|
-
details: "
|
|
1583
|
-
suggestions: ["
|
|
1581
|
+
message: "route module must export `default`",
|
|
1582
|
+
details: "Every route file needs an `export default` React component.",
|
|
1583
|
+
suggestions: ["Add `export default function Page() { ... }`"],
|
|
1584
1584
|
});
|
|
1585
1585
|
}
|
|
1586
1586
|
}
|