@zenithbuild/cli 0.7.3 → 0.7.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/README.md +18 -13
- package/dist/adapters/adapter-netlify.d.ts +1 -1
- package/dist/adapters/adapter-netlify.js +56 -13
- package/dist/adapters/adapter-node.js +8 -0
- package/dist/adapters/adapter-static-export.d.ts +5 -0
- package/dist/adapters/adapter-static-export.js +115 -0
- package/dist/adapters/adapter-types.d.ts +3 -1
- package/dist/adapters/adapter-types.js +5 -2
- package/dist/adapters/adapter-vercel.d.ts +1 -1
- package/dist/adapters/adapter-vercel.js +70 -13
- package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
- package/dist/adapters/copy-hosted-page-runtime.js +49 -0
- package/dist/adapters/resolve-adapter.js +4 -0
- package/dist/adapters/route-rules.d.ts +5 -0
- package/dist/adapters/route-rules.js +9 -0
- package/dist/adapters/validate-hosted-resource-routes.d.ts +1 -0
- package/dist/adapters/validate-hosted-resource-routes.js +13 -0
- package/dist/auth/route-auth.d.ts +6 -0
- package/dist/auth/route-auth.js +236 -0
- package/dist/build/compiler-runtime.d.ts +10 -9
- package/dist/build/compiler-runtime.js +58 -2
- package/dist/build/compiler-signal-expression.d.ts +1 -0
- package/dist/build/compiler-signal-expression.js +155 -0
- package/dist/build/expression-rewrites.d.ts +1 -6
- package/dist/build/expression-rewrites.js +61 -65
- package/dist/build/page-component-loop.d.ts +3 -13
- package/dist/build/page-component-loop.js +21 -46
- package/dist/build/page-ir-normalization.d.ts +0 -8
- package/dist/build/page-ir-normalization.js +13 -234
- package/dist/build/page-loop-state.d.ts +6 -9
- package/dist/build/page-loop-state.js +9 -8
- package/dist/build/page-loop.js +27 -22
- package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
- package/dist/build/scoped-identifier-rewrite.js +28 -128
- package/dist/build/server-script.d.ts +3 -1
- package/dist/build/server-script.js +35 -5
- package/dist/build-output-manifest.d.ts +3 -2
- package/dist/build-output-manifest.js +3 -0
- package/dist/build.js +32 -18
- package/dist/component-instance-ir.js +158 -52
- package/dist/dev-build-session.js +20 -6
- package/dist/dev-server.js +152 -55
- package/dist/download-result.d.ts +14 -0
- package/dist/download-result.js +148 -0
- package/dist/framework-components/Image.zen +1 -1
- package/dist/images/materialization-plan.d.ts +1 -0
- package/dist/images/materialization-plan.js +6 -0
- package/dist/images/materialize.d.ts +5 -3
- package/dist/images/materialize.js +24 -109
- package/dist/images/router-manifest.d.ts +1 -0
- package/dist/images/router-manifest.js +49 -0
- package/dist/images/service.d.ts +13 -1
- package/dist/images/service.js +45 -15
- package/dist/index.js +8 -2
- package/dist/manifest.d.ts +15 -1
- package/dist/manifest.js +27 -7
- package/dist/preview.d.ts +13 -4
- package/dist/preview.js +261 -101
- package/dist/request-body.d.ts +1 -0
- package/dist/request-body.js +7 -0
- package/dist/request-origin.d.ts +2 -0
- package/dist/request-origin.js +45 -0
- package/dist/resource-manifest.d.ts +16 -0
- package/dist/resource-manifest.js +53 -0
- package/dist/resource-response.d.ts +34 -0
- package/dist/resource-response.js +71 -0
- package/dist/resource-route-module.d.ts +15 -0
- package/dist/resource-route-module.js +129 -0
- package/dist/route-check-support.d.ts +1 -0
- package/dist/route-check-support.js +4 -0
- package/dist/server-contract.d.ts +29 -6
- package/dist/server-contract.js +304 -42
- package/dist/server-error.d.ts +4 -0
- package/dist/server-error.js +36 -0
- package/dist/server-output.d.ts +4 -1
- package/dist/server-output.js +71 -10
- package/dist/server-runtime/node-server.js +67 -31
- package/dist/server-runtime/route-render.d.ts +27 -3
- package/dist/server-runtime/route-render.js +94 -53
- package/dist/server-script-composition.d.ts +13 -5
- package/dist/server-script-composition.js +29 -11
- package/dist/static-export-paths.d.ts +3 -0
- package/dist/static-export-paths.js +160 -0
- package/package.json +6 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function readRequestBodyBuffer(req: any): Promise<Buffer<ArrayBuffer>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const DEFAULT_HOST = '127.0.0.1';
|
|
2
|
+
export function publicHost(host) {
|
|
3
|
+
const normalized = String(host || DEFAULT_HOST).trim() || DEFAULT_HOST;
|
|
4
|
+
if (normalized === '0.0.0.0' || normalized === '::') {
|
|
5
|
+
return DEFAULT_HOST;
|
|
6
|
+
}
|
|
7
|
+
return normalized;
|
|
8
|
+
}
|
|
9
|
+
function normalizePublicOrigin(value, label) {
|
|
10
|
+
const raw = String(value || '').trim();
|
|
11
|
+
if (!raw) {
|
|
12
|
+
throw new Error(`[Zenith:Server] ${label} must be a non-empty absolute origin`);
|
|
13
|
+
}
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = new URL(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new Error(`[Zenith:Server] ${label} must be an absolute origin`);
|
|
20
|
+
}
|
|
21
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
22
|
+
throw new Error(`[Zenith:Server] ${label} must use http or https`);
|
|
23
|
+
}
|
|
24
|
+
if (parsed.username || parsed.password) {
|
|
25
|
+
throw new Error(`[Zenith:Server] ${label} must not include credentials`);
|
|
26
|
+
}
|
|
27
|
+
if (parsed.pathname !== '/' || parsed.search || parsed.hash) {
|
|
28
|
+
throw new Error(`[Zenith:Server] ${label} must not include a path, query, or hash`);
|
|
29
|
+
}
|
|
30
|
+
return parsed.origin;
|
|
31
|
+
}
|
|
32
|
+
export function createTrustedOriginResolver(options = {}) {
|
|
33
|
+
const { publicOrigin = undefined, host = DEFAULT_HOST, port = undefined, getPort = undefined, label = 'server' } = options;
|
|
34
|
+
if (publicOrigin !== undefined && publicOrigin !== null && String(publicOrigin).trim().length > 0) {
|
|
35
|
+
const origin = normalizePublicOrigin(publicOrigin, `${label} publicOrigin`);
|
|
36
|
+
return () => origin;
|
|
37
|
+
}
|
|
38
|
+
return () => {
|
|
39
|
+
const resolvedPort = typeof getPort === 'function' ? getPort() : port;
|
|
40
|
+
if (!Number.isInteger(resolvedPort) || resolvedPort <= 0) {
|
|
41
|
+
throw new Error(`[Zenith:Server] ${label} requires "publicOrigin" when a trusted port is unavailable; raw Host headers are not trusted`);
|
|
42
|
+
}
|
|
43
|
+
return `http://${publicHost(host)}:${resolvedPort}`;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function writeResourceRouteManifest(staticDir: any, routeManifest: any, basePath?: string): Promise<({
|
|
2
|
+
path: any;
|
|
3
|
+
file: any;
|
|
4
|
+
route_kind: string;
|
|
5
|
+
server_script: any;
|
|
6
|
+
server_script_path: any;
|
|
7
|
+
has_guard: boolean;
|
|
8
|
+
has_load: boolean;
|
|
9
|
+
has_action: boolean;
|
|
10
|
+
params: any;
|
|
11
|
+
route_id: any;
|
|
12
|
+
} | null)[]>;
|
|
13
|
+
export function loadResourceRouteManifest(distDir: any, fallbackBasePath?: string): Promise<{
|
|
14
|
+
basePath: any;
|
|
15
|
+
routes: any;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { compareRouteSpecificity } from './server/resolve-request-route.js';
|
|
4
|
+
function sanitizeResourceRoute(entry) {
|
|
5
|
+
if (!entry || typeof entry !== 'object') {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
if (typeof entry.path !== 'string' || typeof entry.server_script !== 'string') {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
path: entry.path,
|
|
13
|
+
file: typeof entry.file === 'string' ? entry.file : '',
|
|
14
|
+
route_kind: 'resource',
|
|
15
|
+
server_script: entry.server_script,
|
|
16
|
+
server_script_path: typeof entry.server_script_path === 'string' ? entry.server_script_path : '',
|
|
17
|
+
has_guard: entry.has_guard === true,
|
|
18
|
+
has_load: entry.has_load === true,
|
|
19
|
+
has_action: entry.has_action === true,
|
|
20
|
+
params: Array.isArray(entry.params) ? entry.params.filter((value) => typeof value === 'string') : [],
|
|
21
|
+
route_id: typeof entry.route_id === 'string' ? entry.route_id : null
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export async function writeResourceRouteManifest(staticDir, routeManifest, basePath = '/') {
|
|
25
|
+
const routes = (Array.isArray(routeManifest) ? routeManifest : [])
|
|
26
|
+
.filter((entry) => entry?.route_kind === 'resource')
|
|
27
|
+
.map((entry) => sanitizeResourceRoute(entry))
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.sort((left, right) => compareRouteSpecificity(left.path, right.path));
|
|
30
|
+
const manifestPath = join(staticDir, 'assets', 'resource-manifest.json');
|
|
31
|
+
await mkdir(join(staticDir, 'assets'), { recursive: true });
|
|
32
|
+
await writeFile(manifestPath, `${JSON.stringify({ base_path: basePath, routes }, null, 2)}\n`, 'utf8');
|
|
33
|
+
return routes;
|
|
34
|
+
}
|
|
35
|
+
export async function loadResourceRouteManifest(distDir, fallbackBasePath = '/') {
|
|
36
|
+
const manifestPath = join(distDir, 'assets', 'resource-manifest.json');
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(await readFile(manifestPath, 'utf8'));
|
|
39
|
+
return {
|
|
40
|
+
basePath: typeof parsed?.base_path === 'string' ? parsed.base_path : fallbackBasePath,
|
|
41
|
+
routes: (Array.isArray(parsed?.routes) ? parsed.routes : [])
|
|
42
|
+
.map((entry) => sanitizeResourceRoute(entry))
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.sort((left, right) => compareRouteSpecificity(left.path, right.path))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return {
|
|
49
|
+
basePath: fallbackBasePath,
|
|
50
|
+
routes: []
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function buildResourceResponseDescriptor(result: any, basePath?: string, setCookies?: any[]): {
|
|
2
|
+
status: any;
|
|
3
|
+
headers: {
|
|
4
|
+
Location: string;
|
|
5
|
+
'Cache-Control': string;
|
|
6
|
+
'Content-Type'?: undefined;
|
|
7
|
+
'Content-Disposition'?: undefined;
|
|
8
|
+
'Content-Length'?: undefined;
|
|
9
|
+
};
|
|
10
|
+
body: string;
|
|
11
|
+
setCookies: any[];
|
|
12
|
+
} | {
|
|
13
|
+
status: any;
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': string;
|
|
16
|
+
Location?: undefined;
|
|
17
|
+
'Cache-Control'?: undefined;
|
|
18
|
+
'Content-Disposition'?: undefined;
|
|
19
|
+
'Content-Length'?: undefined;
|
|
20
|
+
};
|
|
21
|
+
body: any;
|
|
22
|
+
setCookies: any[];
|
|
23
|
+
} | {
|
|
24
|
+
status: number;
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': any;
|
|
27
|
+
'Content-Disposition': string;
|
|
28
|
+
'Content-Length': string;
|
|
29
|
+
Location?: undefined;
|
|
30
|
+
'Cache-Control'?: undefined;
|
|
31
|
+
};
|
|
32
|
+
body: Buffer<ArrayBuffer>;
|
|
33
|
+
setCookies: any[];
|
|
34
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { appLocalRedirectLocation } from './base-path.js';
|
|
2
|
+
import { buildAttachmentContentDisposition, decodeDownloadResultBody } from './download-result.js';
|
|
3
|
+
import { clientFacingRouteMessage, defaultRouteDenyMessage } from './server-error.js';
|
|
4
|
+
function serializeJsonBody(payload) {
|
|
5
|
+
return JSON.stringify(payload);
|
|
6
|
+
}
|
|
7
|
+
export function buildResourceResponseDescriptor(result, basePath = '/', setCookies = []) {
|
|
8
|
+
if (!result || typeof result !== 'object') {
|
|
9
|
+
return {
|
|
10
|
+
status: 500,
|
|
11
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
12
|
+
body: defaultRouteDenyMessage(500),
|
|
13
|
+
setCookies
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (result.kind === 'redirect') {
|
|
17
|
+
return {
|
|
18
|
+
status: Number.isInteger(result.status) ? result.status : 302,
|
|
19
|
+
headers: {
|
|
20
|
+
Location: appLocalRedirectLocation(result.location, basePath),
|
|
21
|
+
'Cache-Control': 'no-store'
|
|
22
|
+
},
|
|
23
|
+
body: '',
|
|
24
|
+
setCookies
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (result.kind === 'deny') {
|
|
28
|
+
const status = Number.isInteger(result.status) ? result.status : 403;
|
|
29
|
+
return {
|
|
30
|
+
status,
|
|
31
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
32
|
+
body: clientFacingRouteMessage(status, result.message),
|
|
33
|
+
setCookies
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (result.kind === 'json') {
|
|
37
|
+
return {
|
|
38
|
+
status: Number.isInteger(result.status) ? result.status : 200,
|
|
39
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
40
|
+
body: serializeJsonBody(result.data),
|
|
41
|
+
setCookies
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (result.kind === 'text') {
|
|
45
|
+
return {
|
|
46
|
+
status: Number.isInteger(result.status) ? result.status : 200,
|
|
47
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
48
|
+
body: result.body,
|
|
49
|
+
setCookies
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (result.kind === 'download') {
|
|
53
|
+
const body = decodeDownloadResultBody(result, 'resource download response');
|
|
54
|
+
return {
|
|
55
|
+
status: 200,
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': result.contentType,
|
|
58
|
+
'Content-Disposition': buildAttachmentContentDisposition(result.filename),
|
|
59
|
+
'Content-Length': String(body.byteLength)
|
|
60
|
+
},
|
|
61
|
+
body,
|
|
62
|
+
setCookies
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status: 500,
|
|
67
|
+
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
68
|
+
body: defaultRouteDenyMessage(500),
|
|
69
|
+
setCookies
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function isResourceRouteFile(fileName: any): boolean;
|
|
2
|
+
export function resourceRouteFileToRoute(filePath: any, root: any): string;
|
|
3
|
+
export function analyzeResourceRouteModule(fullPath: any, root: any): {
|
|
4
|
+
path: string;
|
|
5
|
+
file: string;
|
|
6
|
+
path_kind: string;
|
|
7
|
+
render_mode: string;
|
|
8
|
+
route_kind: string;
|
|
9
|
+
params: string[];
|
|
10
|
+
server_script: string;
|
|
11
|
+
server_script_path: any;
|
|
12
|
+
has_guard: boolean;
|
|
13
|
+
has_load: boolean;
|
|
14
|
+
has_action: boolean;
|
|
15
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { relative, sep } from 'node:path';
|
|
3
|
+
const RESOURCE_EXTENSIONS = ['.resource.ts', '.resource.js', '.resource.mts', '.resource.cts', '.resource.mjs', '.resource.cjs'];
|
|
4
|
+
const FORBIDDEN_RESOURCE_EXPORT_RE = /\bexport\s+const\s+(?:data|prerender|exportPaths|ssr_data|props|ssr)\b/;
|
|
5
|
+
function readSingleExport(source, name) {
|
|
6
|
+
const fnMatch = source.match(new RegExp(`\\bexport\\s+(?:async\\s+)?function\\s+${name}\\s*\\(([^)]*)\\)`));
|
|
7
|
+
const constParenMatch = source.match(new RegExp(`\\bexport\\s+const\\s+${name}\\s*=\\s*(?:async\\s*)?\\(([^)]*)\\)\\s*=>`));
|
|
8
|
+
const constSingleArgMatch = source.match(new RegExp(`\\bexport\\s+const\\s+${name}\\s*=\\s*(?:async\\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\\s*=>`));
|
|
9
|
+
const matchCount = Number(Boolean(fnMatch)) +
|
|
10
|
+
Number(Boolean(constParenMatch)) +
|
|
11
|
+
Number(Boolean(constSingleArgMatch));
|
|
12
|
+
return {
|
|
13
|
+
fnMatch,
|
|
14
|
+
constParenMatch,
|
|
15
|
+
constSingleArgMatch,
|
|
16
|
+
hasExport: matchCount > 0,
|
|
17
|
+
matchCount
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function assertSingleCtxArg(sourceFile, name, exportMatch) {
|
|
21
|
+
const singleArg = String(exportMatch.constSingleArgMatch?.[1] || '').trim();
|
|
22
|
+
const paramsText = String((exportMatch.fnMatch || exportMatch.constParenMatch)?.[1] || '').trim();
|
|
23
|
+
const arity = singleArg ? 1 : paramsText.length === 0 ? 0 : paramsText.split(',').length;
|
|
24
|
+
if (arity !== 1) {
|
|
25
|
+
throw new Error(`Zenith resource route contract violation:\n` +
|
|
26
|
+
` File: ${sourceFile}\n` +
|
|
27
|
+
` Reason: ${name}(ctx) must accept exactly one argument\n` +
|
|
28
|
+
` Example: export async function ${name}(ctx) { ... }`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function segmentsToRoute(segments) {
|
|
32
|
+
const routeSegments = segments.map((seg) => {
|
|
33
|
+
const optionalCatchAllMatch = seg.match(/^\[\[\.\.\.([a-zA-Z_][a-zA-Z0-9_]*)\]\]$/);
|
|
34
|
+
if (optionalCatchAllMatch) {
|
|
35
|
+
return `*${optionalCatchAllMatch[1]}?`;
|
|
36
|
+
}
|
|
37
|
+
const catchAllMatch = seg.match(/^\[\.\.\.([a-zA-Z_][a-zA-Z0-9_]*)\]$/);
|
|
38
|
+
if (catchAllMatch) {
|
|
39
|
+
return `*${catchAllMatch[1]}`;
|
|
40
|
+
}
|
|
41
|
+
const paramMatch = seg.match(/^\[([a-zA-Z_][a-zA-Z0-9_]*)\]$/);
|
|
42
|
+
if (paramMatch) {
|
|
43
|
+
return `:${paramMatch[1]}`;
|
|
44
|
+
}
|
|
45
|
+
return seg;
|
|
46
|
+
});
|
|
47
|
+
if (routeSegments.length > 0) {
|
|
48
|
+
const last = routeSegments[routeSegments.length - 1];
|
|
49
|
+
if (last === 'index' || last === 'page') {
|
|
50
|
+
routeSegments.pop();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return `/${routeSegments.join('/')}`;
|
|
54
|
+
}
|
|
55
|
+
export function isResourceRouteFile(fileName) {
|
|
56
|
+
return RESOURCE_EXTENSIONS.some((extension) => fileName.endsWith(extension));
|
|
57
|
+
}
|
|
58
|
+
export function resourceRouteFileToRoute(filePath, root) {
|
|
59
|
+
const rel = relative(root, filePath);
|
|
60
|
+
const extension = RESOURCE_EXTENSIONS.find((candidate) => rel.endsWith(candidate));
|
|
61
|
+
if (!extension) {
|
|
62
|
+
throw new Error(`[Zenith CLI] Resource route "${filePath}" does not use a supported .resource.* extension.`);
|
|
63
|
+
}
|
|
64
|
+
const withoutExt = rel.slice(0, -extension.length);
|
|
65
|
+
const segments = withoutExt.split(sep).filter(Boolean);
|
|
66
|
+
const route = segmentsToRoute(segments);
|
|
67
|
+
return route === '/' ? '/' : route.replace(/\/+/g, '/');
|
|
68
|
+
}
|
|
69
|
+
export function analyzeResourceRouteModule(fullPath, root) {
|
|
70
|
+
const source = readFileSync(fullPath, 'utf8').trim();
|
|
71
|
+
if (!source) {
|
|
72
|
+
throw new Error(`Zenith resource route contract violation:\n` +
|
|
73
|
+
` File: ${fullPath}\n` +
|
|
74
|
+
` Reason: resource route module is empty\n` +
|
|
75
|
+
` Example: export async function load(ctx) { return ctx.json({ ok: true }); }`);
|
|
76
|
+
}
|
|
77
|
+
if (FORBIDDEN_RESOURCE_EXPORT_RE.test(source)) {
|
|
78
|
+
throw new Error(`Zenith resource route contract violation:\n` +
|
|
79
|
+
` File: ${fullPath}\n` +
|
|
80
|
+
` Reason: resource routes may only export guard(ctx), load(ctx), and action(ctx)\n` +
|
|
81
|
+
` Example: remove page-only exports such as data/prerender/exportPaths`);
|
|
82
|
+
}
|
|
83
|
+
const guardExport = readSingleExport(source, 'guard');
|
|
84
|
+
const loadExport = readSingleExport(source, 'load');
|
|
85
|
+
const actionExport = readSingleExport(source, 'action');
|
|
86
|
+
for (const [name, exportMatch] of [
|
|
87
|
+
['guard', guardExport],
|
|
88
|
+
['load', loadExport],
|
|
89
|
+
['action', actionExport]
|
|
90
|
+
]) {
|
|
91
|
+
if (exportMatch.matchCount > 1) {
|
|
92
|
+
throw new Error(`Zenith resource route contract violation:\n` +
|
|
93
|
+
` File: ${fullPath}\n` +
|
|
94
|
+
` Reason: multiple ${name} exports detected\n` +
|
|
95
|
+
` Example: keep exactly one export for ${name}(ctx)`);
|
|
96
|
+
}
|
|
97
|
+
if (exportMatch.hasExport) {
|
|
98
|
+
assertSingleCtxArg(fullPath, name, exportMatch);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!loadExport.hasExport && !actionExport.hasExport) {
|
|
102
|
+
throw new Error(`Zenith resource route contract violation:\n` +
|
|
103
|
+
` File: ${fullPath}\n` +
|
|
104
|
+
` Reason: resource routes must export load(ctx), action(ctx), or both\n` +
|
|
105
|
+
` Example: export async function load(ctx) { return ctx.text('ok'); }`);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
path: resourceRouteFileToRoute(fullPath, root),
|
|
109
|
+
file: relative(root, fullPath).replaceAll('\\', '/'),
|
|
110
|
+
path_kind: resourceRouteFileToRoute(fullPath, root).split('/').some((segment) => segment.startsWith(':') || segment.startsWith('*'))
|
|
111
|
+
? 'dynamic'
|
|
112
|
+
: 'static',
|
|
113
|
+
render_mode: 'server',
|
|
114
|
+
route_kind: 'resource',
|
|
115
|
+
params: resourceRouteFileToRoute(fullPath, root)
|
|
116
|
+
.split('/')
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.filter((segment) => segment.startsWith(':') || segment.startsWith('*'))
|
|
119
|
+
.map((segment) => {
|
|
120
|
+
const raw = segment.slice(1);
|
|
121
|
+
return raw.endsWith('?') ? raw.slice(0, -1) : raw;
|
|
122
|
+
}),
|
|
123
|
+
server_script: source,
|
|
124
|
+
server_script_path: fullPath,
|
|
125
|
+
has_guard: guardExport.hasExport,
|
|
126
|
+
has_load: loadExport.hasExport,
|
|
127
|
+
has_action: actionExport.hasExport
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function supportsTargetRouteCheck(target: any): boolean;
|
|
@@ -15,22 +15,45 @@ export function data(payload: any): {
|
|
|
15
15
|
kind: string;
|
|
16
16
|
data: any;
|
|
17
17
|
};
|
|
18
|
-
export function
|
|
18
|
+
export function invalid(payload: any, status?: number): {
|
|
19
|
+
kind: string;
|
|
20
|
+
data: any;
|
|
21
|
+
status: number;
|
|
22
|
+
};
|
|
23
|
+
export function json(payload: any, status?: number): {
|
|
24
|
+
kind: string;
|
|
25
|
+
data: any;
|
|
26
|
+
status: number;
|
|
27
|
+
};
|
|
28
|
+
export function text(body: any, status?: number): {
|
|
29
|
+
kind: string;
|
|
30
|
+
body: string;
|
|
31
|
+
status: number;
|
|
32
|
+
};
|
|
33
|
+
export function download(body: any, options?: {}): {
|
|
34
|
+
kind: string;
|
|
35
|
+
body: any;
|
|
36
|
+
bodyEncoding: string;
|
|
37
|
+
bodySize: number;
|
|
38
|
+
filename: string;
|
|
39
|
+
contentType: string;
|
|
40
|
+
status: number;
|
|
41
|
+
};
|
|
42
|
+
export function validateServerExports({ exports, filePath, routeKind }: {
|
|
19
43
|
exports: any;
|
|
20
44
|
filePath: any;
|
|
45
|
+
routeKind?: string | undefined;
|
|
21
46
|
}): void;
|
|
22
47
|
export function assertJsonSerializable(value: any, where?: string): void;
|
|
23
|
-
export function resolveRouteResult({ exports, ctx, filePath, guardOnly }: {
|
|
48
|
+
export function resolveRouteResult({ exports, ctx, filePath, guardOnly, routeKind }: {
|
|
24
49
|
exports: any;
|
|
25
50
|
ctx: any;
|
|
26
51
|
filePath: any;
|
|
27
52
|
guardOnly?: boolean | undefined;
|
|
53
|
+
routeKind?: string | undefined;
|
|
28
54
|
}): Promise<{
|
|
29
55
|
result: any;
|
|
30
|
-
trace:
|
|
31
|
-
guard: string;
|
|
32
|
-
load: string;
|
|
33
|
-
};
|
|
56
|
+
trace: any;
|
|
34
57
|
}>;
|
|
35
58
|
export function resolveServerPayload({ exports, ctx, filePath }: {
|
|
36
59
|
exports: any;
|