@zenithbuild/cli 0.7.4 → 0.7.7
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 +5 -3
- package/dist/adapters/adapter-netlify.d.ts +1 -1
- package/dist/adapters/adapter-netlify.js +48 -14
- 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 +67 -19
- package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
- package/dist/adapters/copy-hosted-page-runtime.js +50 -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 +1 -1
- package/dist/build/compiler-runtime.js +8 -2
- package/dist/build/hoisted-code-transforms.d.ts +4 -1
- package/dist/build/hoisted-code-transforms.js +5 -3
- package/dist/build/page-ir-normalization.d.ts +1 -1
- package/dist/build/page-ir-normalization.js +33 -3
- package/dist/build/page-loop-state.js +1 -1
- package/dist/build/page-loop.js +46 -2
- package/dist/build/server-script.d.ts +2 -1
- package/dist/build/server-script.js +7 -3
- package/dist/build-output-manifest.d.ts +3 -2
- package/dist/build-output-manifest.js +3 -0
- package/dist/build.js +29 -17
- package/dist/dev-build-session/helpers.d.ts +29 -0
- package/dist/dev-build-session/helpers.js +223 -0
- package/dist/dev-build-session/session.d.ts +24 -0
- package/dist/dev-build-session/session.js +204 -0
- package/dist/dev-build-session/state.d.ts +37 -0
- package/dist/dev-build-session/state.js +17 -0
- package/dist/dev-build-session.d.ts +1 -24
- package/dist/dev-build-session.js +1 -434
- package/dist/dev-server/css-state.d.ts +7 -0
- package/dist/dev-server/css-state.js +92 -0
- package/dist/dev-server/not-found.d.ts +23 -0
- package/dist/dev-server/not-found.js +129 -0
- package/dist/dev-server/request-handler.d.ts +1 -0
- package/dist/dev-server/request-handler.js +376 -0
- package/dist/dev-server/route-check.d.ts +9 -0
- package/dist/dev-server/route-check.js +100 -0
- package/dist/dev-server/watcher.d.ts +5 -0
- package/dist/dev-server/watcher.js +216 -0
- package/dist/dev-server.js +136 -883
- package/dist/download-result.d.ts +14 -0
- package/dist/download-result.js +148 -0
- package/dist/images/payload.js +4 -0
- package/dist/images/service.d.ts +13 -1
- package/dist/images/service.js +45 -15
- package/dist/manifest.d.ts +15 -1
- package/dist/manifest.js +70 -6
- package/dist/preview/create-preview-server.d.ts +18 -0
- package/dist/preview/create-preview-server.js +71 -0
- package/dist/preview/manifest.d.ts +42 -0
- package/dist/preview/manifest.js +57 -0
- package/dist/preview/paths.d.ts +3 -0
- package/dist/preview/paths.js +38 -0
- package/dist/preview/payload.d.ts +6 -0
- package/dist/preview/payload.js +34 -0
- package/dist/preview/request-handler.d.ts +1 -0
- package/dist/preview/request-handler.js +300 -0
- package/dist/preview/server-runner.d.ts +49 -0
- package/dist/preview/server-runner.js +220 -0
- package/dist/preview/server-script-runner-template.d.ts +1 -0
- package/dist/preview/server-script-runner-template.js +425 -0
- package/dist/preview.d.ts +5 -104
- package/dist/preview.js +7 -993
- package/dist/request-body.d.ts +0 -1
- package/dist/request-body.js +0 -6
- package/dist/resource-manifest.d.ts +16 -0
- package/dist/resource-manifest.js +53 -0
- package/dist/resource-response.d.ts +49 -0
- package/dist/resource-response.js +160 -0
- package/dist/resource-route-module.d.ts +15 -0
- package/dist/resource-route-module.js +129 -0
- package/dist/route-check-support.js +1 -1
- package/dist/server-contract/constants.d.ts +5 -0
- package/dist/server-contract/constants.js +5 -0
- package/dist/server-contract/export-validation.d.ts +5 -0
- package/dist/server-contract/export-validation.js +59 -0
- package/dist/server-contract/json-serializable.d.ts +1 -0
- package/dist/server-contract/json-serializable.js +52 -0
- package/dist/server-contract/resolve.d.ts +15 -0
- package/dist/server-contract/resolve.js +271 -0
- package/dist/server-contract/result-helpers.d.ts +51 -0
- package/dist/server-contract/result-helpers.js +59 -0
- package/dist/server-contract/route-result-validation.d.ts +2 -0
- package/dist/server-contract/route-result-validation.js +73 -0
- package/dist/server-contract/stage.d.ts +6 -0
- package/dist/server-contract/stage.js +22 -0
- package/dist/server-contract.d.ts +6 -54
- package/dist/server-contract.js +9 -301
- package/dist/server-error.d.ts +1 -1
- package/dist/server-error.js +2 -0
- package/dist/server-middleware.d.ts +10 -0
- package/dist/server-middleware.js +30 -0
- package/dist/server-output.d.ts +2 -1
- package/dist/server-output.js +72 -12
- package/dist/server-runtime/node-server.js +59 -7
- package/dist/server-runtime/route-render.d.ts +25 -1
- package/dist/server-runtime/route-render.js +81 -29
- package/dist/server-script-composition.d.ts +4 -2
- package/dist/server-script-composition.js +6 -3
- package/dist/static-export-paths.d.ts +3 -0
- package/dist/static-export-paths.js +160 -0
- package/package.json +3 -3
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function buildAttachmentContentDisposition(filename: any): string;
|
|
2
|
+
export function createDownloadResult(body: any, options?: {}): {
|
|
3
|
+
kind: string;
|
|
4
|
+
body: any;
|
|
5
|
+
bodyEncoding: string;
|
|
6
|
+
bodySize: number;
|
|
7
|
+
filename: string;
|
|
8
|
+
contentType: string;
|
|
9
|
+
status: number;
|
|
10
|
+
};
|
|
11
|
+
export function assertValidDownloadResult(value: any, where?: string): void;
|
|
12
|
+
export function decodeDownloadResultBody(result: any, where?: string): Buffer<ArrayBuffer>;
|
|
13
|
+
export const DOWNLOAD_PAYLOAD_LIMIT_BYTES: number;
|
|
14
|
+
export const DOWNLOAD_DEFAULT_CONTENT_TYPE: "application/octet-stream";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export const DOWNLOAD_PAYLOAD_LIMIT_BYTES = 5 * 1024 * 1024;
|
|
2
|
+
export const DOWNLOAD_DEFAULT_CONTENT_TYPE = 'application/octet-stream';
|
|
3
|
+
const CONTROL_CHAR_RE = /[\0-\x1F\x7F]/;
|
|
4
|
+
const PATH_SEPARATOR_RE = /[\\/]/;
|
|
5
|
+
function formatWhere(where = 'download(...)') {
|
|
6
|
+
return String(where || 'download(...)');
|
|
7
|
+
}
|
|
8
|
+
function normalizeFilename(filename, where = 'download(...)') {
|
|
9
|
+
const label = formatWhere(where);
|
|
10
|
+
const value = String(filename ?? '').trim();
|
|
11
|
+
if (!value) {
|
|
12
|
+
throw new Error(`[Zenith] ${label}: download filename is required.`);
|
|
13
|
+
}
|
|
14
|
+
if (CONTROL_CHAR_RE.test(value) || PATH_SEPARATOR_RE.test(value)) {
|
|
15
|
+
throw new Error(`[Zenith] ${label}: download filename must not contain path separators or control characters.`);
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function normalizeContentType(contentType, where = 'download(...)') {
|
|
20
|
+
const label = formatWhere(where);
|
|
21
|
+
if (contentType === undefined || contentType === null) {
|
|
22
|
+
return DOWNLOAD_DEFAULT_CONTENT_TYPE;
|
|
23
|
+
}
|
|
24
|
+
const value = String(contentType).trim();
|
|
25
|
+
if (!value) {
|
|
26
|
+
throw new Error(`[Zenith] ${label}: download contentType must be a non-empty string when provided.`);
|
|
27
|
+
}
|
|
28
|
+
if (CONTROL_CHAR_RE.test(value)) {
|
|
29
|
+
throw new Error(`[Zenith] ${label}: download contentType must not contain control characters.`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function isBlobLike(value) {
|
|
34
|
+
return (typeof Blob !== 'undefined' && value instanceof Blob)
|
|
35
|
+
|| (typeof File !== 'undefined' && value instanceof File);
|
|
36
|
+
}
|
|
37
|
+
function encodeBuffer(buffer, encoding) {
|
|
38
|
+
return encoding === 'utf8'
|
|
39
|
+
? buffer.toString('utf8')
|
|
40
|
+
: buffer.toString('base64');
|
|
41
|
+
}
|
|
42
|
+
function decodeBody(body, bodyEncoding, where = 'download(...)') {
|
|
43
|
+
const label = formatWhere(where);
|
|
44
|
+
if (typeof body !== 'string') {
|
|
45
|
+
throw new Error(`[Zenith] ${label}: download body must be a string after normalization.`);
|
|
46
|
+
}
|
|
47
|
+
if (bodyEncoding === 'utf8') {
|
|
48
|
+
return Buffer.from(body, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
if (bodyEncoding === 'base64') {
|
|
51
|
+
return Buffer.from(body, 'base64');
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`[Zenith] ${label}: download bodyEncoding must be "utf8" or "base64".`);
|
|
54
|
+
}
|
|
55
|
+
function normalizeBody(body, where = 'download(...)') {
|
|
56
|
+
const label = formatWhere(where);
|
|
57
|
+
if (isBlobLike(body)) {
|
|
58
|
+
throw new Error(`[Zenith] ${label}: download body must be string, Uint8Array, ArrayBuffer, or Buffer-compatible bytes.`);
|
|
59
|
+
}
|
|
60
|
+
if (typeof body === 'string') {
|
|
61
|
+
const size = Buffer.byteLength(body, 'utf8');
|
|
62
|
+
if (size > DOWNLOAD_PAYLOAD_LIMIT_BYTES) {
|
|
63
|
+
throw new Error(`[Zenith] ${label}: download payload exceeds ${DOWNLOAD_PAYLOAD_LIMIT_BYTES} bytes.`);
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
body: body,
|
|
67
|
+
bodyEncoding: 'utf8',
|
|
68
|
+
bodySize: size
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (body instanceof ArrayBuffer) {
|
|
72
|
+
const buffer = Buffer.from(body);
|
|
73
|
+
if (buffer.byteLength > DOWNLOAD_PAYLOAD_LIMIT_BYTES) {
|
|
74
|
+
throw new Error(`[Zenith] ${label}: download payload exceeds ${DOWNLOAD_PAYLOAD_LIMIT_BYTES} bytes.`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
body: encodeBuffer(buffer, 'base64'),
|
|
78
|
+
bodyEncoding: 'base64',
|
|
79
|
+
bodySize: buffer.byteLength
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (ArrayBuffer.isView(body)) {
|
|
83
|
+
const buffer = Buffer.from(body.buffer, body.byteOffset, body.byteLength);
|
|
84
|
+
if (buffer.byteLength > DOWNLOAD_PAYLOAD_LIMIT_BYTES) {
|
|
85
|
+
throw new Error(`[Zenith] ${label}: download payload exceeds ${DOWNLOAD_PAYLOAD_LIMIT_BYTES} bytes.`);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
body: encodeBuffer(buffer, 'base64'),
|
|
89
|
+
bodyEncoding: 'base64',
|
|
90
|
+
bodySize: buffer.byteLength
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`[Zenith] ${label}: download body must be string, Uint8Array, ArrayBuffer, or Buffer-compatible bytes.`);
|
|
94
|
+
}
|
|
95
|
+
function buildAsciiFilename(filename) {
|
|
96
|
+
const replaced = Array.from(String(filename || '')).map((char) => {
|
|
97
|
+
const code = char.charCodeAt(0);
|
|
98
|
+
if (code < 0x20 || code > 0x7E || char === '"' || char === '\\') {
|
|
99
|
+
return '_';
|
|
100
|
+
}
|
|
101
|
+
return char;
|
|
102
|
+
}).join('');
|
|
103
|
+
return replaced || 'download';
|
|
104
|
+
}
|
|
105
|
+
export function buildAttachmentContentDisposition(filename) {
|
|
106
|
+
const safeFilename = normalizeFilename(filename, 'download result');
|
|
107
|
+
const asciiFilename = buildAsciiFilename(safeFilename);
|
|
108
|
+
const encodedFilename = encodeURIComponent(safeFilename)
|
|
109
|
+
.replace(/['()]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
110
|
+
.replace(/\*/g, '%2A');
|
|
111
|
+
return `attachment; filename="${asciiFilename}"; filename*=UTF-8''${encodedFilename}`;
|
|
112
|
+
}
|
|
113
|
+
export function createDownloadResult(body, options = {}) {
|
|
114
|
+
const filename = normalizeFilename(options?.filename, 'download(...)');
|
|
115
|
+
const contentType = normalizeContentType(options?.contentType, 'download(...)');
|
|
116
|
+
const normalized = normalizeBody(body, 'download(...)');
|
|
117
|
+
return {
|
|
118
|
+
kind: 'download',
|
|
119
|
+
body: normalized.body,
|
|
120
|
+
bodyEncoding: normalized.bodyEncoding,
|
|
121
|
+
bodySize: normalized.bodySize,
|
|
122
|
+
filename,
|
|
123
|
+
contentType,
|
|
124
|
+
status: 200
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function assertValidDownloadResult(value, where = 'download result') {
|
|
128
|
+
const label = formatWhere(where);
|
|
129
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
130
|
+
throw new Error(`[Zenith] ${label}: download result must be an object.`);
|
|
131
|
+
}
|
|
132
|
+
normalizeFilename(value.filename, label);
|
|
133
|
+
normalizeContentType(value.contentType, label);
|
|
134
|
+
if (value.status !== 200) {
|
|
135
|
+
throw new Error(`[Zenith] ${label}: download status is fixed to 200 in this milestone.`);
|
|
136
|
+
}
|
|
137
|
+
if (!Number.isInteger(value.bodySize) || value.bodySize < 0 || value.bodySize > DOWNLOAD_PAYLOAD_LIMIT_BYTES) {
|
|
138
|
+
throw new Error(`[Zenith] ${label}: download bodySize must be an integer between 0 and ${DOWNLOAD_PAYLOAD_LIMIT_BYTES}.`);
|
|
139
|
+
}
|
|
140
|
+
const buffer = decodeBody(value.body, value.bodyEncoding, label);
|
|
141
|
+
if (buffer.byteLength !== value.bodySize) {
|
|
142
|
+
throw new Error(`[Zenith] ${label}: download bodySize does not match the normalized body.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export function decodeDownloadResultBody(result, where = 'download result') {
|
|
146
|
+
assertValidDownloadResult(result, where);
|
|
147
|
+
return decodeBody(result.body, result.bodyEncoding, where);
|
|
148
|
+
}
|
package/dist/images/payload.js
CHANGED
|
@@ -18,6 +18,10 @@ function serializeInlineScriptJson(payload) {
|
|
|
18
18
|
.replace(/\u2029/g, '\\u2029');
|
|
19
19
|
}
|
|
20
20
|
export function injectImageRuntimePayload(html, payload) {
|
|
21
|
+
// Only inject if the HTML contains Zenith image markers or unsafeHTML
|
|
22
|
+
if (!/data-zx-(data-zenith-image|unsafeHTML)/.test(html)) {
|
|
23
|
+
return html;
|
|
24
|
+
}
|
|
21
25
|
const safePayload = createImageRuntimePayload(payload?.config || {}, payload?.localImages || {}, payload?.mode || 'passthrough', payload?.basePath || '/');
|
|
22
26
|
const globalName = imageRuntimeGlobalName();
|
|
23
27
|
const serialized = serializeInlineScriptJson(safePayload);
|
package/dist/images/service.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
export function buildImageArtifacts(options: any): Promise<{
|
|
2
2
|
manifest: {};
|
|
3
3
|
}>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* @param {Request | { url?: string } | null | undefined} request
|
|
6
|
+
* @param {{ requestUrl?: URL | string, projectRoot: string, config?: Record<string, unknown> }} options
|
|
7
|
+
* @returns {Promise<Response>}
|
|
8
|
+
*/
|
|
9
|
+
export function handleImageFetchRequest(request: Request | {
|
|
10
|
+
url?: string;
|
|
11
|
+
} | null | undefined, options: {
|
|
12
|
+
requestUrl?: URL | string;
|
|
13
|
+
projectRoot: string;
|
|
14
|
+
config?: Record<string, unknown>;
|
|
15
|
+
}): Promise<Response>;
|
|
16
|
+
export function handleImageRequest(_req: any, res: any, options: any): Promise<boolean>;
|
package/dist/images/service.js
CHANGED
|
@@ -220,7 +220,29 @@ function remoteCachePaths(cacheDir, cacheKey) {
|
|
|
220
220
|
metaPath: join(cacheDir, `${cacheKey}.json`)
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
|
-
|
|
223
|
+
function createJsonResponse(status, payload) {
|
|
224
|
+
return new Response(JSON.stringify(payload), {
|
|
225
|
+
status,
|
|
226
|
+
headers: {
|
|
227
|
+
'Content-Type': 'application/json'
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function createBufferResponse(status, contentType, buffer, cacheSeconds) {
|
|
232
|
+
return new Response(buffer, {
|
|
233
|
+
status,
|
|
234
|
+
headers: {
|
|
235
|
+
'Content-Type': contentType,
|
|
236
|
+
'Cache-Control': `public, max-age=${cacheSeconds}`
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function sendResponse(res, response) {
|
|
241
|
+
res.writeHead(response.status, Object.fromEntries(response.headers.entries()));
|
|
242
|
+
const body = await response.arrayBuffer();
|
|
243
|
+
res.end(Buffer.from(body));
|
|
244
|
+
}
|
|
245
|
+
async function createImageResponse(options) {
|
|
224
246
|
const { requestUrl, projectRoot, config: rawConfig } = options;
|
|
225
247
|
const config = normalizeImageConfig(rawConfig);
|
|
226
248
|
const url = requestUrl instanceof URL ? requestUrl : new URL(String(requestUrl));
|
|
@@ -230,14 +252,10 @@ export async function handleImageRequest(req, res, options) {
|
|
|
230
252
|
const format = normalizeImageFormat(url.searchParams.get('f') || '');
|
|
231
253
|
const quality = Number.isInteger(requestedQuality) && requestedQuality > 0 ? requestedQuality : config.quality;
|
|
232
254
|
if (!remoteUrl) {
|
|
233
|
-
|
|
234
|
-
res.end(JSON.stringify({ error: 'missing_url' }));
|
|
235
|
-
return true;
|
|
255
|
+
return createJsonResponse(400, { error: 'missing_url' });
|
|
236
256
|
}
|
|
237
257
|
if (!Number.isInteger(width) || width <= 0) {
|
|
238
|
-
|
|
239
|
-
res.end(JSON.stringify({ error: 'invalid_width' }));
|
|
240
|
-
return true;
|
|
258
|
+
return createJsonResponse(400, { error: 'invalid_width' });
|
|
241
259
|
}
|
|
242
260
|
try {
|
|
243
261
|
const remote = await validateRemoteTarget(remoteUrl, config);
|
|
@@ -253,8 +271,7 @@ export async function handleImageRequest(req, res, options) {
|
|
|
253
271
|
const contentType = typeof parsedMeta?.contentType === 'string'
|
|
254
272
|
? parsedMeta.contentType
|
|
255
273
|
: mimeTypeForFormat(format || 'jpg');
|
|
256
|
-
|
|
257
|
-
return true;
|
|
274
|
+
return createBufferResponse(200, contentType, cached, config.minimumCacheTTL);
|
|
258
275
|
}
|
|
259
276
|
const response = await fetch(remote, {
|
|
260
277
|
headers: {
|
|
@@ -288,15 +305,28 @@ export async function handleImageRequest(req, res, options) {
|
|
|
288
305
|
format: targetFormat
|
|
289
306
|
}, null, 2)}\n`, 'utf8')
|
|
290
307
|
]);
|
|
291
|
-
|
|
292
|
-
return true;
|
|
308
|
+
return createBufferResponse(200, mimeTypeForFormat(targetFormat), output, config.minimumCacheTTL);
|
|
293
309
|
}
|
|
294
310
|
catch (error) {
|
|
295
|
-
|
|
296
|
-
res.end(JSON.stringify({
|
|
311
|
+
return createJsonResponse(400, {
|
|
297
312
|
error: 'image_request_failed',
|
|
298
313
|
message: error instanceof Error ? error.message : String(error)
|
|
299
|
-
})
|
|
300
|
-
return true;
|
|
314
|
+
});
|
|
301
315
|
}
|
|
302
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* @param {Request | { url?: string } | null | undefined} request
|
|
319
|
+
* @param {{ requestUrl?: URL | string, projectRoot: string, config?: Record<string, unknown> }} options
|
|
320
|
+
* @returns {Promise<Response>}
|
|
321
|
+
*/
|
|
322
|
+
export async function handleImageFetchRequest(request, options) {
|
|
323
|
+
return createImageResponse({
|
|
324
|
+
...options,
|
|
325
|
+
requestUrl: request?.url || options?.requestUrl
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
export async function handleImageRequest(_req, res, options) {
|
|
329
|
+
const response = await createImageResponse(options);
|
|
330
|
+
await sendResponse(res, response);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* @typedef {{
|
|
3
3
|
* path: string,
|
|
4
4
|
* file: string,
|
|
5
|
+
* route_kind?: 'page' | 'resource',
|
|
5
6
|
* path_kind: 'static' | 'dynamic',
|
|
6
7
|
* render_mode: 'prerender' | 'server',
|
|
7
|
-
* params: string[]
|
|
8
|
+
* params: string[],
|
|
9
|
+
* server_script?: string,
|
|
10
|
+
* server_script_path?: string,
|
|
11
|
+
* has_guard?: boolean,
|
|
12
|
+
* has_load?: boolean,
|
|
13
|
+
* has_action?: boolean,
|
|
14
|
+
* export_paths?: string[]
|
|
8
15
|
* }} ManifestEntry
|
|
9
16
|
*/
|
|
10
17
|
/**
|
|
@@ -29,7 +36,14 @@ export function serializeManifest(entries: ManifestEntry[]): string;
|
|
|
29
36
|
export type ManifestEntry = {
|
|
30
37
|
path: string;
|
|
31
38
|
file: string;
|
|
39
|
+
route_kind?: "page" | "resource";
|
|
32
40
|
path_kind: "static" | "dynamic";
|
|
33
41
|
render_mode: "prerender" | "server";
|
|
34
42
|
params: string[];
|
|
43
|
+
server_script?: string;
|
|
44
|
+
server_script_path?: string;
|
|
45
|
+
has_guard?: boolean;
|
|
46
|
+
has_load?: boolean;
|
|
47
|
+
has_action?: boolean;
|
|
48
|
+
export_paths?: string[];
|
|
35
49
|
};
|
package/dist/manifest.js
CHANGED
|
@@ -16,16 +16,25 @@
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
import { readFileSync } from 'node:fs';
|
|
18
18
|
import { readdir, stat } from 'node:fs/promises';
|
|
19
|
-
import { join, relative, sep, basename, extname, dirname } from 'node:path';
|
|
19
|
+
import { join, relative, sep, basename, extname, dirname, resolve } from 'node:path';
|
|
20
20
|
import { extractServerScript } from './build/server-script.js';
|
|
21
|
+
import { analyzeResourceRouteModule, isResourceRouteFile } from './resource-route-module.js';
|
|
21
22
|
import { composeServerScriptEnvelope, resolveAdjacentServerModules } from './server-script-composition.js';
|
|
23
|
+
import { validateStaticExportPaths } from './static-export-paths.js';
|
|
22
24
|
/**
|
|
23
25
|
* @typedef {{
|
|
24
26
|
* path: string,
|
|
25
27
|
* file: string,
|
|
28
|
+
* route_kind?: 'page' | 'resource',
|
|
26
29
|
* path_kind: 'static' | 'dynamic',
|
|
27
30
|
* render_mode: 'prerender' | 'server',
|
|
28
|
-
* params: string[]
|
|
31
|
+
* params: string[],
|
|
32
|
+
* server_script?: string,
|
|
33
|
+
* server_script_path?: string,
|
|
34
|
+
* has_guard?: boolean,
|
|
35
|
+
* has_load?: boolean,
|
|
36
|
+
* has_action?: boolean,
|
|
37
|
+
* export_paths?: string[]
|
|
29
38
|
* }} ManifestEntry
|
|
30
39
|
*/
|
|
31
40
|
/**
|
|
@@ -38,6 +47,11 @@ import { composeServerScriptEnvelope, resolveAdjacentServerModules } from './ser
|
|
|
38
47
|
*/
|
|
39
48
|
export async function generateManifest(pagesDir, extension = '.zen', options = {}) {
|
|
40
49
|
const entries = await _scanDir(pagesDir, pagesDir, extension, options.compilerOpts || {});
|
|
50
|
+
const apiAliasState = _resolveSrcApiAliasState(pagesDir);
|
|
51
|
+
if (apiAliasState) {
|
|
52
|
+
const aliasEntries = await _scanResourceDir(apiAliasState.aliasDir, apiAliasState.srcDir);
|
|
53
|
+
entries.push(...aliasEntries);
|
|
54
|
+
}
|
|
41
55
|
// Validate: no repeated param names in any single route
|
|
42
56
|
for (const entry of entries) {
|
|
43
57
|
_validateParams(entry.path);
|
|
@@ -75,17 +89,60 @@ async function _scanDir(dir, root, ext, compilerOpts) {
|
|
|
75
89
|
}
|
|
76
90
|
else if (item.endsWith(ext)) {
|
|
77
91
|
const routePath = _fileToRoute(fullPath, root, ext);
|
|
78
|
-
entries.push(
|
|
92
|
+
entries.push(buildPageManifestEntry({
|
|
79
93
|
fullPath,
|
|
80
94
|
root,
|
|
81
95
|
routePath,
|
|
82
96
|
compilerOpts
|
|
83
97
|
}));
|
|
84
98
|
}
|
|
99
|
+
else if (isResourceRouteFile(item)) {
|
|
100
|
+
entries.push(analyzeResourceRouteModule(fullPath, root));
|
|
101
|
+
}
|
|
85
102
|
}
|
|
86
103
|
return entries;
|
|
87
104
|
}
|
|
88
|
-
function
|
|
105
|
+
async function _scanResourceDir(dir, root) {
|
|
106
|
+
/** @type {ManifestEntry[]} */
|
|
107
|
+
const entries = [];
|
|
108
|
+
let items;
|
|
109
|
+
try {
|
|
110
|
+
items = await readdir(dir);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
items.sort();
|
|
116
|
+
for (const item of items) {
|
|
117
|
+
const fullPath = join(dir, item);
|
|
118
|
+
const info = await stat(fullPath);
|
|
119
|
+
if (info.isDirectory()) {
|
|
120
|
+
const nested = await _scanResourceDir(fullPath, root);
|
|
121
|
+
entries.push(...nested);
|
|
122
|
+
}
|
|
123
|
+
else if (isResourceRouteFile(item)) {
|
|
124
|
+
entries.push(analyzeResourceRouteModule(fullPath, root));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return entries;
|
|
128
|
+
}
|
|
129
|
+
function _resolveSrcApiAliasState(pagesDir) {
|
|
130
|
+
const resolvedPagesDir = resolve(pagesDir);
|
|
131
|
+
const srcDir = dirname(resolvedPagesDir);
|
|
132
|
+
if (basename(srcDir) !== 'src') {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const aliasDir = join(srcDir, 'api');
|
|
136
|
+
if (aliasDir === resolvedPagesDir ||
|
|
137
|
+
aliasDir.startsWith(`${resolvedPagesDir}${sep}`)) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
aliasDir,
|
|
142
|
+
srcDir
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function buildPageManifestEntry({ fullPath, root, routePath, compilerOpts }) {
|
|
89
146
|
const rawSource = readFileSync(fullPath, 'utf8');
|
|
90
147
|
const inlineServerScript = extractServerScript(rawSource, fullPath, compilerOpts).serverScript;
|
|
91
148
|
const { guardPath, loadPath, actionPath } = resolveAdjacentServerModules(fullPath);
|
|
@@ -96,12 +153,17 @@ function buildManifestEntry({ fullPath, root, routePath, compilerOpts }) {
|
|
|
96
153
|
adjacentLoadPath: loadPath,
|
|
97
154
|
adjacentActionPath: actionPath
|
|
98
155
|
});
|
|
156
|
+
const exportPaths = Array.isArray(composed.serverScript?.export_paths)
|
|
157
|
+
? validateStaticExportPaths(routePath, composed.serverScript.export_paths, fullPath)
|
|
158
|
+
: [];
|
|
99
159
|
return {
|
|
100
160
|
path: routePath,
|
|
101
161
|
file: relative(root, fullPath),
|
|
162
|
+
route_kind: 'page',
|
|
102
163
|
path_kind: _isDynamic(routePath) ? 'dynamic' : 'static',
|
|
103
164
|
render_mode: composed.serverScript && composed.serverScript.prerender !== true ? 'server' : 'prerender',
|
|
104
|
-
params: extractRouteParams(routePath)
|
|
165
|
+
params: extractRouteParams(routePath),
|
|
166
|
+
...(exportPaths.length > 0 ? { export_paths: exportPaths } : {})
|
|
105
167
|
};
|
|
106
168
|
}
|
|
107
169
|
function extractRouteParams(routePath) {
|
|
@@ -325,7 +387,9 @@ function segmentWeight(segment) {
|
|
|
325
387
|
* @returns {string}
|
|
326
388
|
*/
|
|
327
389
|
export function serializeManifest(entries) {
|
|
328
|
-
const lines = entries
|
|
390
|
+
const lines = entries
|
|
391
|
+
.filter((entry) => entry?.route_kind !== 'resource')
|
|
392
|
+
.map((e) => {
|
|
329
393
|
const hasParams = _isDynamic(e.path);
|
|
330
394
|
const loader = hasParams
|
|
331
395
|
? `(params) => import('./pages/${e.file}')`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create and start a preview server.
|
|
3
|
+
*
|
|
4
|
+
* @param {{ distDir: string, port?: number, host?: string, logger?: object | null, config?: object, projectRoot?: string }} options
|
|
5
|
+
* @returns {Promise<{ server: import('http').Server, port: number, close: () => void }>}
|
|
6
|
+
*/
|
|
7
|
+
export function createPreviewServer(options: {
|
|
8
|
+
distDir: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
host?: string;
|
|
11
|
+
logger?: object | null;
|
|
12
|
+
config?: object;
|
|
13
|
+
projectRoot?: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
server: import("http").Server;
|
|
16
|
+
port: number;
|
|
17
|
+
close: () => void;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { normalizeBasePath } from '../base-path.js';
|
|
4
|
+
import { resolveBuildAdapter } from '../adapters/resolve-adapter.js';
|
|
5
|
+
import { isConfigKeyExplicit, isLoadedConfig, loadConfig, validateConfig } from '../config.js';
|
|
6
|
+
import { createTrustedOriginResolver } from '../request-origin.js';
|
|
7
|
+
import { supportsTargetRouteCheck } from '../route-check-support.js';
|
|
8
|
+
import { createSilentLogger } from '../ui/logger.js';
|
|
9
|
+
import { createPreviewRequestHandler } from './request-handler.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create and start a preview server.
|
|
12
|
+
*
|
|
13
|
+
* @param {{ distDir: string, port?: number, host?: string, logger?: object | null, config?: object, projectRoot?: string }} options
|
|
14
|
+
* @returns {Promise<{ server: import('http').Server, port: number, close: () => void }>}
|
|
15
|
+
*/
|
|
16
|
+
export async function createPreviewServer(options) {
|
|
17
|
+
const resolvedProjectRoot = options?.projectRoot ? resolve(options.projectRoot) : resolve(options.distDir, '..');
|
|
18
|
+
const loadedConfig = await loadConfig(resolvedProjectRoot);
|
|
19
|
+
const resolvedConfig = options?.config && typeof options.config === 'object'
|
|
20
|
+
? (() => {
|
|
21
|
+
const overrideConfig = isLoadedConfig(options.config)
|
|
22
|
+
? options.config
|
|
23
|
+
: validateConfig(options.config);
|
|
24
|
+
const mergedConfig = { ...loadedConfig };
|
|
25
|
+
for (const key of Object.keys(overrideConfig)) {
|
|
26
|
+
if (isConfigKeyExplicit(overrideConfig, key)) {
|
|
27
|
+
mergedConfig[key] = overrideConfig[key];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return mergedConfig;
|
|
31
|
+
})()
|
|
32
|
+
: loadedConfig;
|
|
33
|
+
const { distDir, port = 4000, host = '127.0.0.1', logger: providedLogger = null } = options;
|
|
34
|
+
const projectRoot = resolvedProjectRoot;
|
|
35
|
+
const config = resolvedConfig;
|
|
36
|
+
const logger = providedLogger || createSilentLogger();
|
|
37
|
+
const verboseLogging = logger.mode?.logLevel === 'verbose';
|
|
38
|
+
const configuredBasePath = normalizeBasePath(config.basePath || '/');
|
|
39
|
+
const resolvedTarget = resolveBuildAdapter(config).target;
|
|
40
|
+
const routeCheckEnabled = supportsTargetRouteCheck(resolvedTarget);
|
|
41
|
+
const isStaticExportTarget = resolvedTarget === 'static-export';
|
|
42
|
+
let actualPort = port;
|
|
43
|
+
const resolveServerOrigin = createTrustedOriginResolver({
|
|
44
|
+
host,
|
|
45
|
+
getPort: () => actualPort,
|
|
46
|
+
label: 'preview server'
|
|
47
|
+
});
|
|
48
|
+
const server = createServer(createPreviewRequestHandler({
|
|
49
|
+
distDir,
|
|
50
|
+
projectRoot,
|
|
51
|
+
config,
|
|
52
|
+
logger,
|
|
53
|
+
verboseLogging,
|
|
54
|
+
configuredBasePath,
|
|
55
|
+
routeCheckEnabled,
|
|
56
|
+
isStaticExportTarget,
|
|
57
|
+
serverOrigin: resolveServerOrigin
|
|
58
|
+
}));
|
|
59
|
+
return new Promise((resolveServer) => {
|
|
60
|
+
server.listen(port, host, () => {
|
|
61
|
+
actualPort = server.address().port;
|
|
62
|
+
resolveServer({
|
|
63
|
+
server,
|
|
64
|
+
port: actualPort,
|
|
65
|
+
close: () => {
|
|
66
|
+
server.close();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {{
|
|
3
|
+
* path: string;
|
|
4
|
+
* output: string;
|
|
5
|
+
* server_script?: string | null;
|
|
6
|
+
* server_script_path?: string | null;
|
|
7
|
+
* prerender?: boolean;
|
|
8
|
+
* route_id?: string;
|
|
9
|
+
* pattern?: string;
|
|
10
|
+
* params_shape?: Record<string, string>;
|
|
11
|
+
* has_guard?: boolean;
|
|
12
|
+
* has_load?: boolean;
|
|
13
|
+
* guard_module_ref?: string | null;
|
|
14
|
+
* load_module_ref?: string | null;
|
|
15
|
+
* }} PreviewRoute
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} distDir
|
|
19
|
+
* @returns {Promise<PreviewRoute[]>}
|
|
20
|
+
*/
|
|
21
|
+
export function loadRouteManifest(distDir: string): Promise<PreviewRoute[]>;
|
|
22
|
+
export function loadRouteSurfaceState(distDir: any, fallbackBasePath?: string): Promise<{
|
|
23
|
+
basePath: string;
|
|
24
|
+
pageRoutes: any;
|
|
25
|
+
resourceRoutes: any[];
|
|
26
|
+
}>;
|
|
27
|
+
export const matchRoute: typeof matchManifestRoute;
|
|
28
|
+
export type PreviewRoute = {
|
|
29
|
+
path: string;
|
|
30
|
+
output: string;
|
|
31
|
+
server_script?: string | null;
|
|
32
|
+
server_script_path?: string | null;
|
|
33
|
+
prerender?: boolean;
|
|
34
|
+
route_id?: string;
|
|
35
|
+
pattern?: string;
|
|
36
|
+
params_shape?: Record<string, string>;
|
|
37
|
+
has_guard?: boolean;
|
|
38
|
+
has_load?: boolean;
|
|
39
|
+
guard_module_ref?: string | null;
|
|
40
|
+
load_module_ref?: string | null;
|
|
41
|
+
};
|
|
42
|
+
import { matchRoute as matchManifestRoute } from '../server/resolve-request-route.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { normalizeBasePath } from '../base-path.js';
|
|
4
|
+
import { loadResourceRouteManifest } from '../resource-manifest.js';
|
|
5
|
+
import { compareRouteSpecificity, matchRoute as matchManifestRoute } from '../server/resolve-request-route.js';
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {{
|
|
8
|
+
* path: string;
|
|
9
|
+
* output: string;
|
|
10
|
+
* server_script?: string | null;
|
|
11
|
+
* server_script_path?: string | null;
|
|
12
|
+
* prerender?: boolean;
|
|
13
|
+
* route_id?: string;
|
|
14
|
+
* pattern?: string;
|
|
15
|
+
* params_shape?: Record<string, string>;
|
|
16
|
+
* has_guard?: boolean;
|
|
17
|
+
* has_load?: boolean;
|
|
18
|
+
* guard_module_ref?: string | null;
|
|
19
|
+
* load_module_ref?: string | null;
|
|
20
|
+
* }} PreviewRoute
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} distDir
|
|
24
|
+
* @returns {Promise<PreviewRoute[]>}
|
|
25
|
+
*/
|
|
26
|
+
export async function loadRouteManifest(distDir) {
|
|
27
|
+
const state = await loadRouteSurfaceState(distDir, '/');
|
|
28
|
+
return state.pageRoutes;
|
|
29
|
+
}
|
|
30
|
+
export async function loadRouteSurfaceState(distDir, fallbackBasePath = '/') {
|
|
31
|
+
const manifestPath = join(distDir, 'assets', 'router-manifest.json');
|
|
32
|
+
const resourceState = await loadResourceRouteManifest(distDir, normalizeBasePath(fallbackBasePath || '/'));
|
|
33
|
+
try {
|
|
34
|
+
const source = await readFile(manifestPath, 'utf8');
|
|
35
|
+
const parsed = JSON.parse(source);
|
|
36
|
+
const routes = Array.isArray(parsed?.routes) ? parsed.routes : [];
|
|
37
|
+
const basePath = normalizeBasePath(parsed?.base_path || resourceState.basePath || fallbackBasePath || '/');
|
|
38
|
+
return {
|
|
39
|
+
basePath,
|
|
40
|
+
pageRoutes: routes
|
|
41
|
+
.filter((entry) => entry &&
|
|
42
|
+
typeof entry === 'object' &&
|
|
43
|
+
typeof entry.path === 'string' &&
|
|
44
|
+
typeof entry.output === 'string')
|
|
45
|
+
.sort((a, b) => compareRouteSpecificity(a.path, b.path)),
|
|
46
|
+
resourceRoutes: Array.isArray(resourceState.routes) ? resourceState.routes : []
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return {
|
|
51
|
+
basePath: normalizeBasePath(resourceState.basePath || fallbackBasePath || '/'),
|
|
52
|
+
pageRoutes: [],
|
|
53
|
+
resourceRoutes: Array.isArray(resourceState.routes) ? resourceState.routes : []
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export const matchRoute = matchManifestRoute;
|