@zenithbuild/cli 0.7.4 → 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 +5 -3
- package/dist/adapters/adapter-netlify.d.ts +1 -1
- package/dist/adapters/adapter-netlify.js +56 -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 +70 -14
- 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 +1 -1
- package/dist/build/compiler-runtime.js +8 -2
- package/dist/build/page-loop-state.js +1 -1
- 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-server.js +79 -25
- package/dist/download-result.d.ts +14 -0
- package/dist/download-result.js +148 -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 +24 -5
- package/dist/preview.d.ts +11 -3
- package/dist/preview.js +188 -62
- 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 +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.js +1 -1
- package/dist/server-contract.d.ts +24 -16
- package/dist/server-contract.js +217 -25
- package/dist/server-error.d.ts +1 -1
- package/dist/server-error.js +2 -0
- package/dist/server-output.d.ts +2 -1
- package/dist/server-output.js +59 -11
- package/dist/server-runtime/node-server.js +34 -4
- 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
package/dist/server-output.js
CHANGED
|
@@ -3,6 +3,7 @@ import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
import { loadResourceRouteManifest } from './resource-manifest.js';
|
|
6
7
|
const PACKAGE_REQUIRE = createRequire(import.meta.url);
|
|
7
8
|
const RELATIVE_SPECIFIER_RE = /((?:import|export)\s+(?:[^'"]*?\s+from\s+)?|import\s*\()\s*(['"])([^'"]+)\2/g;
|
|
8
9
|
const SERVER_RUNTIME_FILES = [
|
|
@@ -14,6 +15,10 @@ const SERVER_RUNTIME_FILES = [
|
|
|
14
15
|
from: new URL('./server-contract.js', import.meta.url),
|
|
15
16
|
to: 'server-contract.js'
|
|
16
17
|
},
|
|
18
|
+
{
|
|
19
|
+
from: new URL('./auth/route-auth.js', import.meta.url),
|
|
20
|
+
to: 'auth/route-auth.js'
|
|
21
|
+
},
|
|
17
22
|
{
|
|
18
23
|
from: new URL('./base-path.js', import.meta.url),
|
|
19
24
|
to: 'base-path.js'
|
|
@@ -34,11 +39,27 @@ const SERVER_RUNTIME_FILES = [
|
|
|
34
39
|
from: new URL('./images/runtime.js', import.meta.url),
|
|
35
40
|
to: 'images/runtime.js'
|
|
36
41
|
},
|
|
42
|
+
{
|
|
43
|
+
from: new URL('./images/service.js', import.meta.url),
|
|
44
|
+
to: 'images/service.js'
|
|
45
|
+
},
|
|
37
46
|
{
|
|
38
47
|
from: new URL('./server-error.js', import.meta.url),
|
|
39
48
|
to: 'server-error.js'
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
from: new URL('./resource-response.js', import.meta.url),
|
|
52
|
+
to: 'resource-response.js'
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
from: new URL('./download-result.js', import.meta.url),
|
|
56
|
+
to: 'download-result.js'
|
|
40
57
|
}
|
|
41
58
|
];
|
|
59
|
+
const SPECIAL_SERVER_SPECIFIERS = new Map([
|
|
60
|
+
['zenith:server-contract', 'server-contract.js'],
|
|
61
|
+
['zenith:route-auth', 'auth/route-auth.js']
|
|
62
|
+
]);
|
|
42
63
|
function normalizeRouteName(routePath) {
|
|
43
64
|
if (routePath === '/') {
|
|
44
65
|
return 'index';
|
|
@@ -152,7 +173,7 @@ function outputPathForSource(projectRoot, modulesRoot, sourcePath) {
|
|
|
152
173
|
: relativePath.replace(/\.(tsx|ts|mts|cts|jsx|js|mjs|cjs)$/i, '.js');
|
|
153
174
|
return join(modulesRoot, nextRelative);
|
|
154
175
|
}
|
|
155
|
-
async function compileImportedModule({ projectRoot, modulesRoot, sourcePath, ts, seen }) {
|
|
176
|
+
async function compileImportedModule({ projectRoot, modulesRoot, serverDir, sourcePath, ts, seen }) {
|
|
156
177
|
if (seen.has(sourcePath)) {
|
|
157
178
|
return outputPathForSource(projectRoot, modulesRoot, sourcePath);
|
|
158
179
|
}
|
|
@@ -166,6 +187,12 @@ async function compileImportedModule({ projectRoot, modulesRoot, sourcePath, ts,
|
|
|
166
187
|
const source = await readFile(sourcePath, 'utf8');
|
|
167
188
|
let output = transpileSource(ts, source, sourcePath);
|
|
168
189
|
for (const specifier of gatherSpecifiers(output)) {
|
|
190
|
+
const specialSpecifierPath = SPECIAL_SERVER_SPECIFIERS.get(specifier);
|
|
191
|
+
if (specialSpecifierPath) {
|
|
192
|
+
const nextSpecifier = relative(dirname(outPath), join(serverDir, specialSpecifierPath)).replaceAll('\\', '/');
|
|
193
|
+
output = replaceSpecifier(output, specifier, nextSpecifier.startsWith('.') ? nextSpecifier : `./${nextSpecifier}`);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
169
196
|
if (!isRelativeSpecifier(specifier)) {
|
|
170
197
|
continue;
|
|
171
198
|
}
|
|
@@ -176,6 +203,7 @@ async function compileImportedModule({ projectRoot, modulesRoot, sourcePath, ts,
|
|
|
176
203
|
const compiledDependencyPath = await compileImportedModule({
|
|
177
204
|
projectRoot,
|
|
178
205
|
modulesRoot,
|
|
206
|
+
serverDir,
|
|
179
207
|
sourcePath: resolvedPath,
|
|
180
208
|
ts,
|
|
181
209
|
seen
|
|
@@ -186,12 +214,18 @@ async function compileImportedModule({ projectRoot, modulesRoot, sourcePath, ts,
|
|
|
186
214
|
await writeFile(outPath, output, 'utf8');
|
|
187
215
|
return outPath;
|
|
188
216
|
}
|
|
189
|
-
async function writeRouteModulePackage({ projectRoot, routeDir, route }) {
|
|
217
|
+
async function writeRouteModulePackage({ projectRoot, serverDir, routeDir, route }) {
|
|
190
218
|
const ts = resolveTypeScriptApi(projectRoot);
|
|
191
219
|
const modulesRoot = join(routeDir, 'modules');
|
|
192
220
|
const seen = new Set();
|
|
193
221
|
let entryOutput = transpileSource(ts, route.server_script || '', route.server_script_path || 'route-entry.ts');
|
|
194
222
|
for (const specifier of gatherSpecifiers(entryOutput)) {
|
|
223
|
+
const specialSpecifierPath = SPECIAL_SERVER_SPECIFIERS.get(specifier);
|
|
224
|
+
if (specialSpecifierPath) {
|
|
225
|
+
const nextSpecifier = relative(join(routeDir, 'route'), join(serverDir, specialSpecifierPath)).replaceAll('\\', '/');
|
|
226
|
+
entryOutput = replaceSpecifier(entryOutput, specifier, nextSpecifier.startsWith('.') ? nextSpecifier : `./${nextSpecifier}`);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
195
229
|
if (!isRelativeSpecifier(specifier)) {
|
|
196
230
|
continue;
|
|
197
231
|
}
|
|
@@ -202,6 +236,7 @@ async function writeRouteModulePackage({ projectRoot, routeDir, route }) {
|
|
|
202
236
|
const compiledDependencyPath = await compileImportedModule({
|
|
203
237
|
projectRoot,
|
|
204
238
|
modulesRoot,
|
|
239
|
+
serverDir,
|
|
205
240
|
sourcePath: resolvedPath,
|
|
206
241
|
ts,
|
|
207
242
|
seen
|
|
@@ -238,8 +273,15 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
238
273
|
catch {
|
|
239
274
|
routerManifest = { routes: [] };
|
|
240
275
|
}
|
|
241
|
-
const
|
|
242
|
-
const
|
|
276
|
+
const resourceManifest = await loadResourceRouteManifest(staticDir, basePath);
|
|
277
|
+
const pageRoutes = Array.isArray(routerManifest.routes) ? routerManifest.routes : [];
|
|
278
|
+
const serverRoutes = pageRoutes
|
|
279
|
+
.filter((route) => route.server_script && route.prerender !== true)
|
|
280
|
+
.map((route) => ({ ...route, route_kind: 'page' }))
|
|
281
|
+
.concat((Array.isArray(resourceManifest.routes) ? resourceManifest.routes : []).map((route) => ({
|
|
282
|
+
...route,
|
|
283
|
+
route_kind: 'resource'
|
|
284
|
+
})));
|
|
243
285
|
await mkdir(serverDir, { recursive: true });
|
|
244
286
|
await copyRuntimeFiles(serverDir);
|
|
245
287
|
const imageManifestSource = join(staticDir, '_zenith', 'image', 'manifest.json');
|
|
@@ -248,8 +290,10 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
248
290
|
const name = normalizeRouteName(route.path);
|
|
249
291
|
const routeDir = join(serverDir, 'routes', name);
|
|
250
292
|
await mkdir(routeDir, { recursive: true });
|
|
251
|
-
|
|
252
|
-
|
|
293
|
+
if (route.route_kind !== 'resource') {
|
|
294
|
+
const htmlSourcePath = join(staticDir, String(route.output || '').replace(/^\//, ''));
|
|
295
|
+
await copyOptionalFile(htmlSourcePath, join(routeDir, 'route', 'page.html'));
|
|
296
|
+
}
|
|
253
297
|
let pageAssetFile = null;
|
|
254
298
|
if (typeof route.page_asset === 'string' && route.page_asset.length > 0) {
|
|
255
299
|
const assetSourcePath = join(staticDir, route.page_asset.replace(/^\//, ''));
|
|
@@ -259,18 +303,20 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
259
303
|
}
|
|
260
304
|
}
|
|
261
305
|
let imageManifestFile = null;
|
|
262
|
-
if (await copyOptionalFile(imageManifestSource, join(routeDir, 'route', 'image-manifest.json'))) {
|
|
306
|
+
if (route.route_kind !== 'resource' && await copyOptionalFile(imageManifestSource, join(routeDir, 'route', 'image-manifest.json'))) {
|
|
263
307
|
imageManifestFile = 'image-manifest.json';
|
|
264
308
|
}
|
|
265
309
|
await writeRouteModulePackage({
|
|
266
310
|
projectRoot,
|
|
311
|
+
serverDir,
|
|
267
312
|
routeDir,
|
|
268
313
|
route
|
|
269
314
|
});
|
|
270
315
|
const meta = {
|
|
271
316
|
name,
|
|
272
317
|
path: route.path,
|
|
273
|
-
|
|
318
|
+
route_kind: route.route_kind || 'page',
|
|
319
|
+
output: route.output || null,
|
|
274
320
|
base_path: basePath,
|
|
275
321
|
page_asset: route.page_asset || null,
|
|
276
322
|
page_asset_file: pageAssetFile,
|
|
@@ -282,11 +328,13 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
282
328
|
has_guard: route.has_guard === true,
|
|
283
329
|
has_load: route.has_load === true,
|
|
284
330
|
has_action: route.has_action === true,
|
|
285
|
-
params:
|
|
286
|
-
|
|
331
|
+
params: Array.isArray(route.params) && route.params.length > 0
|
|
332
|
+
? [...route.params]
|
|
333
|
+
: extractRouteParams(route.path),
|
|
334
|
+
image_manifest_file: route.route_kind === 'resource' ? null : imageManifestFile,
|
|
287
335
|
image_config: config?.images || {}
|
|
288
336
|
};
|
|
289
|
-
if (Array.isArray(route.image_materialization) && route.image_materialization.length > 0) {
|
|
337
|
+
if (route.route_kind !== 'resource' && Array.isArray(route.image_materialization) && route.image_materialization.length > 0) {
|
|
290
338
|
meta.image_materialization = route.image_materialization;
|
|
291
339
|
}
|
|
292
340
|
await writeFile(join(routeDir, 'route.json'), `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
@@ -7,7 +7,7 @@ import { appLocalRedirectLocation, imageEndpointPath, normalizeBasePath, routeCh
|
|
|
7
7
|
import { handleImageRequest } from '../images/service.js';
|
|
8
8
|
import { createTrustedOriginResolver } from '../request-origin.js';
|
|
9
9
|
import { defaultRouteDenyMessage, logServerException, sanitizeRouteResult } from '../server-error.js';
|
|
10
|
-
import { executeRouteRequest, renderRouteRequest } from './route-render.js';
|
|
10
|
+
import { executeRouteRequest, renderResourceRouteRequest, renderRouteRequest } from './route-render.js';
|
|
11
11
|
import { resolveRequestRoute } from './resolve-request-route.js';
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = dirname(__filename);
|
|
@@ -97,9 +97,23 @@ async function createWebRequest(req, url) {
|
|
|
97
97
|
}
|
|
98
98
|
return new Request(url.toString(), init);
|
|
99
99
|
}
|
|
100
|
+
function getSetCookieValues(response) {
|
|
101
|
+
if (typeof response?.headers?.getSetCookie === 'function') {
|
|
102
|
+
return response.headers.getSetCookie();
|
|
103
|
+
}
|
|
104
|
+
const value = response?.headers?.get?.('set-cookie');
|
|
105
|
+
return typeof value === 'string' && value.length > 0 ? [value] : [];
|
|
106
|
+
}
|
|
100
107
|
async function sendFetchResponse(res, response, method) {
|
|
101
108
|
res.statusCode = response.status;
|
|
109
|
+
const setCookies = getSetCookieValues(response);
|
|
110
|
+
if (setCookies.length > 0) {
|
|
111
|
+
res.setHeader('set-cookie', setCookies);
|
|
112
|
+
}
|
|
102
113
|
for (const [key, value] of response.headers.entries()) {
|
|
114
|
+
if (key.toLowerCase() === 'set-cookie') {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
103
117
|
res.setHeader(key, value);
|
|
104
118
|
}
|
|
105
119
|
if (String(method || 'GET').toUpperCase() === 'HEAD') {
|
|
@@ -155,13 +169,16 @@ async function loadRuntimeContext(options = {}) {
|
|
|
155
169
|
base_path: '/'
|
|
156
170
|
});
|
|
157
171
|
const serverManifest = await readJson(join(serverDir, 'manifest.json'), { routes: [] });
|
|
172
|
+
const allServerRoutes = Array.isArray(serverManifest.routes) ? serverManifest.routes : [];
|
|
158
173
|
return {
|
|
159
174
|
distDir,
|
|
160
175
|
serverDir,
|
|
161
176
|
staticDir: resolve(serverDir, config.static_dir || '../static'),
|
|
162
177
|
buildManifest,
|
|
163
178
|
buildRoutes: Array.isArray(buildManifest.routes) ? buildManifest.routes : [],
|
|
164
|
-
serverRoutes:
|
|
179
|
+
serverRoutes: allServerRoutes,
|
|
180
|
+
pageServerRoutes: allServerRoutes.filter((route) => route?.route_kind !== 'resource'),
|
|
181
|
+
resourceServerRoutes: allServerRoutes.filter((route) => route?.route_kind === 'resource'),
|
|
165
182
|
images: config.images || {},
|
|
166
183
|
basePath: normalizeBasePath(config.base_path || '/')
|
|
167
184
|
};
|
|
@@ -206,7 +223,7 @@ async function handleRouteCheck(req, res, url, context) {
|
|
|
206
223
|
}
|
|
207
224
|
let result = { kind: 'allow' };
|
|
208
225
|
let routeId = buildResolved.route.path || '';
|
|
209
|
-
const serverResolved = resolveRequestRoute(canonicalTargetUrl, context.
|
|
226
|
+
const serverResolved = resolveRequestRoute(canonicalTargetUrl, context.pageServerRoutes);
|
|
210
227
|
if (serverResolved.matched && serverResolved.route) {
|
|
211
228
|
routeId = serverResolved.route.route_id || serverResolved.route.name || serverResolved.route.path || routeId;
|
|
212
229
|
try {
|
|
@@ -275,7 +292,20 @@ async function handleNodeRequest(req, res, context, serverOrigin) {
|
|
|
275
292
|
await sendStaticFile(res, assetPath, req.method);
|
|
276
293
|
return;
|
|
277
294
|
}
|
|
278
|
-
const
|
|
295
|
+
const resourceResolved = resolveRequestRoute(canonicalUrl, context.resourceServerRoutes);
|
|
296
|
+
if (resourceResolved.matched && resourceResolved.route) {
|
|
297
|
+
const routeDir = join(context.serverDir, 'routes', resourceResolved.route.name);
|
|
298
|
+
const request = await createWebRequest(req, url);
|
|
299
|
+
const response = await renderResourceRouteRequest({
|
|
300
|
+
request,
|
|
301
|
+
route: resourceResolved.route,
|
|
302
|
+
params: resourceResolved.params,
|
|
303
|
+
routeModulePath: join(routeDir, 'route', 'entry.js')
|
|
304
|
+
});
|
|
305
|
+
await sendFetchResponse(res, response, req.method);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const serverResolved = resolveRequestRoute(canonicalUrl, context.pageServerRoutes);
|
|
279
309
|
if (serverResolved.matched && serverResolved.route) {
|
|
280
310
|
const routeDir = join(context.serverDir, 'routes', serverResolved.route.name);
|
|
281
311
|
const request = await createWebRequest(req, url);
|
|
@@ -8,7 +8,7 @@ export function extractInternalParams(requestUrl: any, route: any): {};
|
|
|
8
8
|
* routeModulePath: string,
|
|
9
9
|
* guardOnly?: boolean
|
|
10
10
|
* }} options
|
|
11
|
-
* @returns {Promise<{ publicUrl: URL, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number }>}
|
|
11
|
+
* @returns {Promise<{ publicUrl: URL, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
|
|
12
12
|
*/
|
|
13
13
|
export function executeRouteRequest(options: {
|
|
14
14
|
request: Request;
|
|
@@ -34,6 +34,7 @@ export function executeRouteRequest(options: {
|
|
|
34
34
|
load: string;
|
|
35
35
|
};
|
|
36
36
|
status?: number;
|
|
37
|
+
setCookies?: string[];
|
|
37
38
|
}>;
|
|
38
39
|
/**
|
|
39
40
|
* @param {{
|
|
@@ -62,3 +63,26 @@ export function renderRouteRequest(options: {
|
|
|
62
63
|
imageManifestPath?: string | null;
|
|
63
64
|
imageConfig?: Record<string, unknown>;
|
|
64
65
|
}): Promise<Response>;
|
|
66
|
+
/**
|
|
67
|
+
* @param {{
|
|
68
|
+
* request: Request,
|
|
69
|
+
* route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null, route_kind?: string | null, base_path?: string | null },
|
|
70
|
+
* params: Record<string, string>,
|
|
71
|
+
* routeModulePath: string
|
|
72
|
+
* }} options
|
|
73
|
+
* @returns {Promise<Response>}
|
|
74
|
+
*/
|
|
75
|
+
export function renderResourceRouteRequest(options: {
|
|
76
|
+
request: Request;
|
|
77
|
+
route: {
|
|
78
|
+
path: string;
|
|
79
|
+
params?: string[];
|
|
80
|
+
route_id?: string | null;
|
|
81
|
+
server_script_path?: string | null;
|
|
82
|
+
file?: string | null;
|
|
83
|
+
route_kind?: string | null;
|
|
84
|
+
base_path?: string | null;
|
|
85
|
+
};
|
|
86
|
+
params: Record<string, string>;
|
|
87
|
+
routeModulePath: string;
|
|
88
|
+
}): Promise<Response>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { attachRouteAuth } from '../auth/route-auth.js';
|
|
3
4
|
import { appLocalRedirectLocation, normalizeBasePath, prependBasePath } from '../base-path.js';
|
|
4
5
|
import { createImageRuntimePayload, injectImageRuntimePayload } from '../images/payload.js';
|
|
5
6
|
import { materializeImageMarkup } from '../images/materialize.js';
|
|
7
|
+
import { buildResourceResponseDescriptor } from '../resource-response.js';
|
|
6
8
|
import { clientFacingRouteMessage, defaultRouteDenyMessage, logServerException } from '../server-error.js';
|
|
7
|
-
import { allow, data, deny, invalid, redirect, resolveRouteResult } from '../server-contract.js';
|
|
9
|
+
import { allow, data, deny, download, invalid, json, redirect, resolveRouteResult, text } from '../server-contract.js';
|
|
8
10
|
const MODULE_CACHE = new Map();
|
|
9
11
|
const INTERNAL_QUERY_PREFIX = '__zenith_param_';
|
|
10
12
|
function parseCookies(rawCookieHeader) {
|
|
@@ -41,12 +43,29 @@ function escapeInlineJson(payload) {
|
|
|
41
43
|
.replace(/\u2028/g, '\\u2028')
|
|
42
44
|
.replace(/\u2029/g, '\\u2029');
|
|
43
45
|
}
|
|
44
|
-
function
|
|
46
|
+
function appendSetCookieHeaders(headers, setCookies = []) {
|
|
47
|
+
for (const value of Array.isArray(setCookies) ? setCookies : []) {
|
|
48
|
+
headers.append('Set-Cookie', value);
|
|
49
|
+
}
|
|
50
|
+
return headers;
|
|
51
|
+
}
|
|
52
|
+
function createTextResponse(status, message, setCookies = []) {
|
|
53
|
+
const headers = new Headers({
|
|
54
|
+
'Content-Type': 'text/plain; charset=utf-8'
|
|
55
|
+
});
|
|
56
|
+
appendSetCookieHeaders(headers, setCookies);
|
|
45
57
|
return new Response(message || defaultRouteDenyMessage(status), {
|
|
46
58
|
status,
|
|
47
|
-
headers
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
headers
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function createResourceResponse(result, basePath, setCookies = []) {
|
|
63
|
+
const descriptor = buildResourceResponseDescriptor(result, basePath, setCookies);
|
|
64
|
+
const headers = new Headers(descriptor.headers);
|
|
65
|
+
appendSetCookieHeaders(headers, descriptor.setCookies);
|
|
66
|
+
return new Response(descriptor.body, {
|
|
67
|
+
status: descriptor.status,
|
|
68
|
+
headers
|
|
50
69
|
});
|
|
51
70
|
}
|
|
52
71
|
function injectSsrPayload(html, payload) {
|
|
@@ -140,9 +159,9 @@ async function loadRouteExports(routeModulePath) {
|
|
|
140
159
|
MODULE_CACHE.set(cacheKey, value);
|
|
141
160
|
return value;
|
|
142
161
|
}
|
|
143
|
-
function createRouteContext({ request, route, params, publicUrl }) {
|
|
162
|
+
function createRouteContext({ request, route, params, publicUrl, guardOnly = false }) {
|
|
144
163
|
const requestHeaders = Object.fromEntries(request.headers.entries());
|
|
145
|
-
|
|
164
|
+
const ctx = {
|
|
146
165
|
params: { ...params },
|
|
147
166
|
url: publicUrl,
|
|
148
167
|
headers: { ...requestHeaders },
|
|
@@ -156,20 +175,22 @@ function createRouteContext({ request, route, params, publicUrl }) {
|
|
|
156
175
|
},
|
|
157
176
|
env: {},
|
|
158
177
|
action: null,
|
|
159
|
-
auth: {
|
|
160
|
-
async getSession() {
|
|
161
|
-
return null;
|
|
162
|
-
},
|
|
163
|
-
async requireSession() {
|
|
164
|
-
throw redirect('/login', 302);
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
178
|
allow,
|
|
168
179
|
redirect,
|
|
169
180
|
deny,
|
|
170
181
|
invalid,
|
|
171
|
-
data
|
|
182
|
+
data,
|
|
183
|
+
json,
|
|
184
|
+
text,
|
|
185
|
+
download
|
|
172
186
|
};
|
|
187
|
+
attachRouteAuth(ctx, {
|
|
188
|
+
requestUrl: publicUrl,
|
|
189
|
+
guardOnly,
|
|
190
|
+
redirect,
|
|
191
|
+
deny
|
|
192
|
+
});
|
|
193
|
+
return ctx;
|
|
173
194
|
}
|
|
174
195
|
/**
|
|
175
196
|
* @param {{
|
|
@@ -179,24 +200,26 @@ function createRouteContext({ request, route, params, publicUrl }) {
|
|
|
179
200
|
* routeModulePath: string,
|
|
180
201
|
* guardOnly?: boolean
|
|
181
202
|
* }} options
|
|
182
|
-
* @returns {Promise<{ publicUrl: URL, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number }>}
|
|
203
|
+
* @returns {Promise<{ publicUrl: URL, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
|
|
183
204
|
*/
|
|
184
205
|
export async function executeRouteRequest(options) {
|
|
185
206
|
const { request, route, params, routeModulePath, guardOnly = false } = options;
|
|
186
207
|
const publicUrl = buildPublicUrl(request.url, route, params);
|
|
187
|
-
const ctx = createRouteContext({ request, route, params, publicUrl });
|
|
208
|
+
const ctx = createRouteContext({ request, route, params, publicUrl, guardOnly });
|
|
188
209
|
const exports = await loadRouteExports(routeModulePath);
|
|
189
210
|
const resolved = await resolveRouteResult({
|
|
190
211
|
exports,
|
|
191
212
|
ctx,
|
|
192
213
|
filePath: route.file || route.server_script_path || route.path,
|
|
193
|
-
guardOnly
|
|
214
|
+
guardOnly,
|
|
215
|
+
routeKind: route.route_kind === 'resource' ? 'resource' : 'page'
|
|
194
216
|
});
|
|
195
217
|
return {
|
|
196
218
|
publicUrl,
|
|
197
219
|
result: resolved.result,
|
|
198
220
|
trace: resolved.trace,
|
|
199
|
-
status: resolved.status
|
|
221
|
+
status: resolved.status,
|
|
222
|
+
setCookies: Array.isArray(resolved.setCookies) ? resolved.setCookies : []
|
|
200
223
|
};
|
|
201
224
|
}
|
|
202
225
|
/**
|
|
@@ -214,24 +237,26 @@ export async function executeRouteRequest(options) {
|
|
|
214
237
|
export async function renderRouteRequest(options) {
|
|
215
238
|
const { request, route, params, routeModulePath, shellHtmlPath, imageManifestPath = null, imageConfig = {} } = options;
|
|
216
239
|
try {
|
|
217
|
-
const { publicUrl, result, status } = await executeRouteRequest({
|
|
240
|
+
const { publicUrl, result, status, setCookies = [] } = await executeRouteRequest({
|
|
218
241
|
request,
|
|
219
242
|
route,
|
|
220
243
|
params,
|
|
221
244
|
routeModulePath
|
|
222
245
|
});
|
|
223
246
|
if (result.kind === 'redirect') {
|
|
247
|
+
const headers = new Headers({
|
|
248
|
+
Location: appLocalRedirectLocation(result.location, route.base_path || '/'),
|
|
249
|
+
'Cache-Control': 'no-store'
|
|
250
|
+
});
|
|
251
|
+
appendSetCookieHeaders(headers, setCookies);
|
|
224
252
|
return new Response('', {
|
|
225
253
|
status: Number.isInteger(result.status) ? result.status : 302,
|
|
226
|
-
headers
|
|
227
|
-
Location: appLocalRedirectLocation(result.location, route.base_path || '/'),
|
|
228
|
-
'Cache-Control': 'no-store'
|
|
229
|
-
}
|
|
254
|
+
headers
|
|
230
255
|
});
|
|
231
256
|
}
|
|
232
257
|
if (result.kind === 'deny') {
|
|
233
258
|
const status = Number.isInteger(result.status) ? result.status : 403;
|
|
234
|
-
return createTextResponse(status, clientFacingRouteMessage(status, result.message));
|
|
259
|
+
return createTextResponse(status, clientFacingRouteMessage(status, result.message), setCookies);
|
|
235
260
|
}
|
|
236
261
|
const ssrPayload = result.kind === 'data' && result.data && typeof result.data === 'object' && !Array.isArray(result.data)
|
|
237
262
|
? result.data
|
|
@@ -248,11 +273,13 @@ export async function renderRouteRequest(options) {
|
|
|
248
273
|
});
|
|
249
274
|
html = injectSsrPayload(html, ssrPayload);
|
|
250
275
|
html = injectImageRuntimePayload(html, imagePayload);
|
|
276
|
+
const headers = new Headers({
|
|
277
|
+
'Content-Type': 'text/html; charset=utf-8'
|
|
278
|
+
});
|
|
279
|
+
appendSetCookieHeaders(headers, setCookies);
|
|
251
280
|
return new Response(html, {
|
|
252
281
|
status: Number.isInteger(status) ? status : 200,
|
|
253
|
-
headers
|
|
254
|
-
'Content-Type': 'text/html; charset=utf-8'
|
|
255
|
-
}
|
|
282
|
+
headers
|
|
256
283
|
});
|
|
257
284
|
}
|
|
258
285
|
catch (error) {
|
|
@@ -260,3 +287,28 @@ export async function renderRouteRequest(options) {
|
|
|
260
287
|
return createTextResponse(500, defaultRouteDenyMessage(500));
|
|
261
288
|
}
|
|
262
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* @param {{
|
|
292
|
+
* request: Request,
|
|
293
|
+
* route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null, route_kind?: string | null, base_path?: string | null },
|
|
294
|
+
* params: Record<string, string>,
|
|
295
|
+
* routeModulePath: string
|
|
296
|
+
* }} options
|
|
297
|
+
* @returns {Promise<Response>}
|
|
298
|
+
*/
|
|
299
|
+
export async function renderResourceRouteRequest(options) {
|
|
300
|
+
const { request, route, params, routeModulePath } = options;
|
|
301
|
+
try {
|
|
302
|
+
const { result, setCookies = [] } = await executeRouteRequest({
|
|
303
|
+
request,
|
|
304
|
+
route: { ...route, route_kind: 'resource' },
|
|
305
|
+
params,
|
|
306
|
+
routeModulePath
|
|
307
|
+
});
|
|
308
|
+
return createResourceResponse(result, route.base_path || '/', setCookies);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
logServerException('node resource route render failed', error);
|
|
312
|
+
return createTextResponse(500, defaultRouteDenyMessage(500));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @param {{
|
|
3
3
|
* sourceFile: string,
|
|
4
|
-
* inlineServerScript?: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null,
|
|
4
|
+
* inlineServerScript?: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null,
|
|
5
5
|
* adjacentGuardPath?: string | null,
|
|
6
6
|
* adjacentLoadPath?: string | null,
|
|
7
7
|
* adjacentActionPath?: string | null
|
|
8
8
|
* }} input
|
|
9
|
-
* @returns {{ serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null, guardPath: string | null, loadPath: string | null, actionPath: string | null }}
|
|
9
|
+
* @returns {{ serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null, guardPath: string | null, loadPath: string | null, actionPath: string | null }}
|
|
10
10
|
*/
|
|
11
11
|
export function composeServerScriptEnvelope({ sourceFile, inlineServerScript, adjacentGuardPath, adjacentLoadPath, adjacentActionPath }: {
|
|
12
12
|
sourceFile: string;
|
|
@@ -17,6 +17,7 @@ export function composeServerScriptEnvelope({ sourceFile, inlineServerScript, ad
|
|
|
17
17
|
has_load: boolean;
|
|
18
18
|
has_action: boolean;
|
|
19
19
|
source_path: string;
|
|
20
|
+
export_paths?: string[];
|
|
20
21
|
} | null;
|
|
21
22
|
adjacentGuardPath?: string | null;
|
|
22
23
|
adjacentLoadPath?: string | null;
|
|
@@ -29,6 +30,7 @@ export function composeServerScriptEnvelope({ sourceFile, inlineServerScript, ad
|
|
|
29
30
|
has_load: boolean;
|
|
30
31
|
has_action: boolean;
|
|
31
32
|
source_path: string;
|
|
33
|
+
export_paths?: string[];
|
|
32
34
|
} | null;
|
|
33
35
|
guardPath: string | null;
|
|
34
36
|
loadPath: string | null;
|
|
@@ -65,12 +65,12 @@ function classifyInlineServerSource(source) {
|
|
|
65
65
|
/**
|
|
66
66
|
* @param {{
|
|
67
67
|
* sourceFile: string,
|
|
68
|
-
* inlineServerScript?: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null,
|
|
68
|
+
* inlineServerScript?: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null,
|
|
69
69
|
* adjacentGuardPath?: string | null,
|
|
70
70
|
* adjacentLoadPath?: string | null,
|
|
71
71
|
* adjacentActionPath?: string | null
|
|
72
72
|
* }} input
|
|
73
|
-
* @returns {{ serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null, guardPath: string | null, loadPath: string | null, actionPath: string | null }}
|
|
73
|
+
* @returns {{ serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null, guardPath: string | null, loadPath: string | null, actionPath: string | null }}
|
|
74
74
|
*/
|
|
75
75
|
export function composeServerScriptEnvelope({ sourceFile, inlineServerScript = null, adjacentGuardPath = null, adjacentLoadPath = null, adjacentActionPath = null }) {
|
|
76
76
|
const inlineSource = String(inlineServerScript?.source || '').trim();
|
|
@@ -128,7 +128,10 @@ export function composeServerScriptEnvelope({ sourceFile, inlineServerScript = n
|
|
|
128
128
|
has_guard: inlineHasGuard || Boolean(adjacentGuardPath),
|
|
129
129
|
has_load: inlineHasLoad || Boolean(adjacentLoadPath),
|
|
130
130
|
has_action: inlineHasAction || Boolean(adjacentActionPath),
|
|
131
|
-
source_path: sourceFile
|
|
131
|
+
source_path: sourceFile,
|
|
132
|
+
export_paths: Array.isArray(inlineServerScript?.export_paths)
|
|
133
|
+
? [...inlineServerScript.export_paths]
|
|
134
|
+
: []
|
|
132
135
|
},
|
|
133
136
|
guardPath: adjacentGuardPath,
|
|
134
137
|
loadPath: adjacentLoadPath,
|