@zenithbuild/cli 0.7.10 → 0.7.12
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 +14 -2
- package/dist/adapters/adapter-netlify-static.d.ts +2 -5
- package/dist/adapters/adapter-netlify.d.ts +2 -5
- package/dist/adapters/adapter-netlify.js +22 -5
- package/dist/adapters/adapter-types.d.ts +32 -13
- package/dist/adapters/adapter-types.js +0 -59
- package/dist/adapters/adapter-vercel-static.d.ts +2 -5
- package/dist/adapters/adapter-vercel.d.ts +2 -5
- package/dist/adapters/adapter-vercel.js +21 -6
- package/dist/adapters/copy-hosted-page-runtime.d.ts +2 -1
- package/dist/adapters/copy-hosted-page-runtime.js +68 -3
- package/dist/adapters/resolve-adapter.d.ts +6 -4
- package/dist/build/compiler-runtime.js +3 -0
- package/dist/build/expression-rewrites.d.ts +3 -1
- package/dist/build/expression-rewrites.js +14 -2
- package/dist/build/page-component-loop.d.ts +1 -0
- package/dist/build/page-component-loop.js +66 -6
- package/dist/build/page-ir-normalization.js +7 -0
- package/dist/build/page-loop-state.d.ts +2 -4
- package/dist/build/page-loop-state.js +17 -9
- package/dist/build/page-loop.js +18 -8
- package/dist/build/scoped-expression-context.d.ts +5 -0
- package/dist/build/scoped-expression-context.js +133 -0
- package/dist/build/server-script.js +13 -36
- package/dist/build/type-declarations.d.ts +2 -1
- package/dist/build/type-declarations.js +29 -52
- package/dist/build-output-manifest.d.ts +10 -6
- package/dist/build-output-manifest.js +4 -1
- package/dist/build.js +11 -2
- package/dist/component-instance-ir.js +1 -0
- package/dist/component-occurrences.d.ts +9 -0
- package/dist/component-occurrences.js +18 -0
- package/dist/config-plugins.d.ts +12 -0
- package/dist/config-plugins.js +100 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.js +56 -5
- package/dist/dev-build-session/helpers.js +27 -7
- package/dist/dev-build-session/session.js +19 -10
- package/dist/dev-server/build-error-response.d.ts +21 -0
- package/dist/dev-server/build-error-response.js +48 -0
- package/dist/dev-server/port-fallback.d.ts +15 -0
- package/dist/dev-server/port-fallback.js +61 -0
- package/dist/dev-server/request-handler.js +58 -5
- package/dist/dev-server/watcher.js +15 -0
- package/dist/dev-server.d.ts +5 -2
- package/dist/dev-server.js +129 -49
- package/dist/global-middleware-runtime-source.d.ts +15 -0
- package/dist/global-middleware-runtime-source.js +62 -0
- package/dist/global-middleware.d.ts +13 -0
- package/dist/global-middleware.js +252 -0
- package/dist/images/remote-fetch.d.ts +12 -0
- package/dist/images/remote-fetch.js +257 -0
- package/dist/images/service.d.ts +10 -0
- package/dist/images/service.js +9 -46
- package/dist/index.js +12 -2
- package/dist/manifest.d.ts +9 -1
- package/dist/manifest.js +70 -25
- package/dist/preview/request-handler.js +78 -5
- package/dist/preview/server-runner.d.ts +7 -2
- package/dist/preview/server-runner.js +19 -6
- package/dist/preview/server-script-runner-template.js +97 -29
- package/dist/resource-response.js +25 -8
- package/dist/resource-route-module.js +5 -22
- package/dist/route-classification.d.ts +11 -0
- package/dist/route-classification.js +21 -0
- package/dist/route-handler-export-analysis.d.ts +22 -0
- package/dist/route-handler-export-analysis.js +41 -0
- package/dist/scoped-server-data/analyze-owner-file.d.ts +3 -0
- package/dist/scoped-server-data/analyze-owner-file.js +149 -0
- package/dist/scoped-server-data/diagnostics.d.ts +18 -0
- package/dist/scoped-server-data/diagnostics.js +32 -0
- package/dist/scoped-server-data/lowering.d.ts +27 -0
- package/dist/scoped-server-data/lowering.js +242 -0
- package/dist/scoped-server-data/manifest-integration.d.ts +4 -0
- package/dist/scoped-server-data/manifest-integration.js +125 -0
- package/dist/scoped-server-data/owner-scanner.d.ts +6 -0
- package/dist/scoped-server-data/owner-scanner.js +55 -0
- package/dist/scoped-server-data/parse-owner-server-block.d.ts +12 -0
- package/dist/scoped-server-data/parse-owner-server-block.js +35 -0
- package/dist/scoped-server-data/runtime.d.ts +24 -0
- package/dist/scoped-server-data/runtime.js +121 -0
- package/dist/scoped-server-data/serialization-set.d.ts +2 -0
- package/dist/scoped-server-data/serialization-set.js +52 -0
- package/dist/scoped-server-data/static-props.d.ts +12 -0
- package/dist/scoped-server-data/static-props.js +307 -0
- package/dist/scoped-server-data/type-declarations.d.ts +10 -0
- package/dist/scoped-server-data/type-declarations.js +368 -0
- package/dist/scoped-server-data/types.d.ts +74 -0
- package/dist/scoped-server-data/types.js +1 -0
- package/dist/server-contract/auth-control-flow.d.ts +1 -0
- package/dist/server-contract/auth-control-flow.js +10 -0
- package/dist/server-contract/resolve.d.ts +19 -0
- package/dist/server-contract/resolve.js +85 -13
- package/dist/server-contract/resolved-envelope.d.ts +9 -0
- package/dist/server-contract/resolved-envelope.js +14 -0
- package/dist/server-contract/stage.js +1 -10
- package/dist/server-module-output.d.ts +9 -0
- package/dist/server-module-output.js +250 -0
- package/dist/server-output.d.ts +7 -1
- package/dist/server-output.js +144 -195
- package/dist/server-route-names.d.ts +2 -0
- package/dist/server-route-names.js +38 -0
- package/dist/server-runtime/matched-route-pipeline.d.ts +1 -0
- package/dist/server-runtime/matched-route-pipeline.js +1 -0
- package/dist/server-runtime/node-server.js +26 -3
- package/dist/server-runtime/route-render.d.ts +12 -3
- package/dist/server-runtime/route-render.js +67 -13
- package/dist/types/generate-env-dts.js +2 -44
- package/dist/types/zenith-env-dts.d.ts +4 -0
- package/dist/types/zenith-env-dts.js +96 -0
- package/package.json +3 -6
package/dist/server-output.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
4
|
-
import { basename, dirname, extname, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
4
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
5
|
import { loadResourceRouteManifest } from './resource-manifest.js';
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { assignServerRouteNames } from './server-route-names.js';
|
|
7
|
+
import { normalizeGlobalMiddlewareMetadata } from './global-middleware.js';
|
|
8
|
+
import { writeServerModulePackage } from './server-module-output.js';
|
|
9
|
+
const GLOBAL_MIDDLEWARE_MODULE = 'global-middleware/entry.js';
|
|
10
|
+
const SCOPED_SERVER_DATA_LOWERING_HELPER_UNAVAILABLE = '[Zenith:ScopedServerData] Server-output lowering helper is unavailable. Run the CLI build step before packaging scoped server data modules.';
|
|
9
11
|
const SERVER_RUNTIME_FILES = [
|
|
10
12
|
{
|
|
11
13
|
from: new URL('./server-runtime/route-render.js', import.meta.url),
|
|
@@ -20,6 +22,10 @@ const SERVER_RUNTIME_FILES = [
|
|
|
20
22
|
to: 'server-contract',
|
|
21
23
|
recursive: true
|
|
22
24
|
},
|
|
25
|
+
{
|
|
26
|
+
from: new URL('./scoped-server-data/runtime.js', import.meta.url),
|
|
27
|
+
to: 'scoped-server-data/runtime.js'
|
|
28
|
+
},
|
|
23
29
|
{
|
|
24
30
|
from: new URL('./server-middleware.js', import.meta.url),
|
|
25
31
|
to: 'server-middleware.js'
|
|
@@ -44,6 +50,10 @@ const SERVER_RUNTIME_FILES = [
|
|
|
44
50
|
from: new URL('./images/shared.js', import.meta.url),
|
|
45
51
|
to: 'images/shared.js'
|
|
46
52
|
},
|
|
53
|
+
{
|
|
54
|
+
from: new URL('./images/remote-fetch.js', import.meta.url),
|
|
55
|
+
to: 'images/remote-fetch.js'
|
|
56
|
+
},
|
|
47
57
|
{
|
|
48
58
|
from: new URL('./images/runtime.js', import.meta.url),
|
|
49
59
|
to: 'images/runtime.js'
|
|
@@ -65,197 +75,33 @@ const SERVER_RUNTIME_FILES = [
|
|
|
65
75
|
to: 'download-result.js'
|
|
66
76
|
}
|
|
67
77
|
];
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
]);
|
|
72
|
-
function normalizeRouteName(routePath) {
|
|
73
|
-
if (routePath === '/') {
|
|
74
|
-
return 'index';
|
|
75
|
-
}
|
|
76
|
-
return routePath
|
|
77
|
-
.replace(/^\//, '')
|
|
78
|
-
.replace(/\/+/g, '_')
|
|
79
|
-
.replace(/:/g, 'param_')
|
|
80
|
-
.replace(/\*/g, 'splat_')
|
|
81
|
-
.replace(/\?/g, 'opt')
|
|
82
|
-
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
83
|
-
.replace(/_+/g, '_')
|
|
84
|
-
.replace(/^_+|_+$/g, '');
|
|
85
|
-
}
|
|
86
|
-
function resolveTypeScriptApi(projectRoot) {
|
|
87
|
-
try {
|
|
88
|
-
const projectRequire = createRequire(join(projectRoot, '__zenith_server_output_loader__.js'));
|
|
89
|
-
return projectRequire('typescript');
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
try {
|
|
93
|
-
return PACKAGE_REQUIRE('typescript');
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
throw new Error('[Zenith:Build] Server-capable targets require the `typescript` package to transpile route modules.');
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function withJsExtension(specifier) {
|
|
101
|
-
if (specifier.endsWith('.json')) {
|
|
102
|
-
return specifier;
|
|
103
|
-
}
|
|
104
|
-
return specifier.replace(/\.(tsx|ts|mts|cts|jsx|js|mjs|cjs)$/i, '.js');
|
|
105
|
-
}
|
|
106
|
-
function replaceSpecifier(source, original, nextValue) {
|
|
107
|
-
return source.replace(new RegExp(`(['"])${escapeRegex(original)}\\1`, 'g'), (_, quote) => `${quote}${nextValue}${quote}`);
|
|
108
|
-
}
|
|
109
|
-
function escapeRegex(value) {
|
|
110
|
-
return String(value).replace(/[|\\{}()[\]^$+*?.-]/g, '\\$&');
|
|
111
|
-
}
|
|
112
|
-
function isRelativeSpecifier(specifier) {
|
|
113
|
-
return (specifier.startsWith('./') ||
|
|
114
|
-
specifier.startsWith('../') ||
|
|
115
|
-
specifier.startsWith('/') ||
|
|
116
|
-
specifier.startsWith('file:'));
|
|
117
|
-
}
|
|
118
|
-
function resolveModuleCandidates(basePath) {
|
|
119
|
-
if (extname(basePath)) {
|
|
120
|
-
return [basePath];
|
|
121
|
-
}
|
|
78
|
+
let scopedServerDataLoweringPromise = null;
|
|
79
|
+
function resolveScopedServerDataLoweringPath() {
|
|
80
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
122
81
|
return [
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
`${basePath}.mts`,
|
|
127
|
-
`${basePath}.cts`,
|
|
128
|
-
`${basePath}.js`,
|
|
129
|
-
`${basePath}.mjs`,
|
|
130
|
-
`${basePath}.cjs`,
|
|
131
|
-
`${basePath}.json`,
|
|
132
|
-
join(basePath, 'index.ts'),
|
|
133
|
-
join(basePath, 'index.tsx'),
|
|
134
|
-
join(basePath, 'index.mts'),
|
|
135
|
-
join(basePath, 'index.cts'),
|
|
136
|
-
join(basePath, 'index.js'),
|
|
137
|
-
join(basePath, 'index.mjs'),
|
|
138
|
-
join(basePath, 'index.cjs'),
|
|
139
|
-
join(basePath, 'index.json')
|
|
140
|
-
];
|
|
82
|
+
join(moduleDir, 'scoped-server-data', 'lowering.js'),
|
|
83
|
+
join(moduleDir, '..', 'dist', 'scoped-server-data', 'lowering.js')
|
|
84
|
+
].find((candidate) => existsSync(candidate)) || null;
|
|
141
85
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const baseDir = dirname(sourceFile);
|
|
147
|
-
const basePath = specifier.startsWith('file:')
|
|
148
|
-
? new URL(specifier)
|
|
149
|
-
: resolve(baseDir, specifier);
|
|
150
|
-
const filePath = basePath instanceof URL ? fileURLToPath(basePath) : basePath;
|
|
151
|
-
const candidates = resolveModuleCandidates(filePath);
|
|
152
|
-
const found = candidates.find((candidate) => existsSync(candidate));
|
|
153
|
-
if (!found) {
|
|
154
|
-
throw new Error(`[Zenith:Build] Cannot resolve server import "${specifier}" from "${sourceFile}"`);
|
|
86
|
+
async function getScopedServerDataLowering() {
|
|
87
|
+
const helperPath = resolveScopedServerDataLoweringPath();
|
|
88
|
+
if (!helperPath) {
|
|
89
|
+
throw new Error(SCOPED_SERVER_DATA_LOWERING_HELPER_UNAVAILABLE);
|
|
155
90
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
function gatherSpecifiers(source) {
|
|
159
|
-
const results = [];
|
|
160
|
-
for (const match of source.matchAll(RELATIVE_SPECIFIER_RE)) {
|
|
161
|
-
const specifier = String(match[3] || '');
|
|
162
|
-
results.push(specifier);
|
|
91
|
+
if (!scopedServerDataLoweringPromise) {
|
|
92
|
+
scopedServerDataLoweringPromise = import(pathToFileURL(helperPath).href);
|
|
163
93
|
}
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
function transpileSource(ts, source, filePath) {
|
|
167
|
-
return ts.transpileModule(source, {
|
|
168
|
-
compilerOptions: {
|
|
169
|
-
module: ts.ModuleKind.ESNext,
|
|
170
|
-
target: ts.ScriptTarget.ES2022,
|
|
171
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
172
|
-
esModuleInterop: true,
|
|
173
|
-
allowSyntheticDefaultImports: true
|
|
174
|
-
},
|
|
175
|
-
fileName: filePath
|
|
176
|
-
}).outputText;
|
|
177
|
-
}
|
|
178
|
-
function outputPathForSource(projectRoot, modulesRoot, sourcePath) {
|
|
179
|
-
const relativePath = relative(projectRoot, sourcePath).replaceAll('\\', '/');
|
|
180
|
-
const nextRelative = extname(relativePath) === '.json'
|
|
181
|
-
? relativePath
|
|
182
|
-
: relativePath.replace(/\.(tsx|ts|mts|cts|jsx|js|mjs|cjs)$/i, '.js');
|
|
183
|
-
return join(modulesRoot, nextRelative);
|
|
184
|
-
}
|
|
185
|
-
async function compileImportedModule({ projectRoot, modulesRoot, serverDir, sourcePath, ts, seen }) {
|
|
186
|
-
if (seen.has(sourcePath)) {
|
|
187
|
-
return outputPathForSource(projectRoot, modulesRoot, sourcePath);
|
|
188
|
-
}
|
|
189
|
-
seen.add(sourcePath);
|
|
190
|
-
const outPath = outputPathForSource(projectRoot, modulesRoot, sourcePath);
|
|
191
|
-
await mkdir(dirname(outPath), { recursive: true });
|
|
192
|
-
if (extname(sourcePath) === '.json') {
|
|
193
|
-
await cp(sourcePath, outPath, { force: true });
|
|
194
|
-
return outPath;
|
|
195
|
-
}
|
|
196
|
-
const source = await readFile(sourcePath, 'utf8');
|
|
197
|
-
let output = transpileSource(ts, source, sourcePath);
|
|
198
|
-
for (const specifier of gatherSpecifiers(output)) {
|
|
199
|
-
const specialSpecifierPath = SPECIAL_SERVER_SPECIFIERS.get(specifier);
|
|
200
|
-
if (specialSpecifierPath) {
|
|
201
|
-
const nextSpecifier = relative(dirname(outPath), join(serverDir, specialSpecifierPath)).replaceAll('\\', '/');
|
|
202
|
-
output = replaceSpecifier(output, specifier, nextSpecifier.startsWith('.') ? nextSpecifier : `./${nextSpecifier}`);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
if (!isRelativeSpecifier(specifier)) {
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
const resolvedPath = resolveImportedModule(specifier, sourcePath);
|
|
209
|
-
if (!resolvedPath) {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
const compiledDependencyPath = await compileImportedModule({
|
|
213
|
-
projectRoot,
|
|
214
|
-
modulesRoot,
|
|
215
|
-
serverDir,
|
|
216
|
-
sourcePath: resolvedPath,
|
|
217
|
-
ts,
|
|
218
|
-
seen
|
|
219
|
-
});
|
|
220
|
-
const nextSpecifier = relative(dirname(outPath), compiledDependencyPath).replaceAll('\\', '/');
|
|
221
|
-
output = replaceSpecifier(output, specifier, nextSpecifier.startsWith('.') ? nextSpecifier : `./${nextSpecifier}`);
|
|
222
|
-
}
|
|
223
|
-
await writeFile(outPath, output, 'utf8');
|
|
224
|
-
return outPath;
|
|
94
|
+
return scopedServerDataLoweringPromise;
|
|
225
95
|
}
|
|
226
96
|
async function writeRouteModulePackage({ projectRoot, serverDir, routeDir, route }) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
entryOutput = replaceSpecifier(entryOutput, specifier, nextSpecifier.startsWith('.') ? nextSpecifier : `./${nextSpecifier}`);
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (!isRelativeSpecifier(specifier)) {
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
241
|
-
const resolvedPath = resolveImportedModule(specifier, route.server_script_path || projectRoot);
|
|
242
|
-
if (!resolvedPath) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
const compiledDependencyPath = await compileImportedModule({
|
|
246
|
-
projectRoot,
|
|
247
|
-
modulesRoot,
|
|
248
|
-
serverDir,
|
|
249
|
-
sourcePath: resolvedPath,
|
|
250
|
-
ts,
|
|
251
|
-
seen
|
|
252
|
-
});
|
|
253
|
-
const nextSpecifier = relative(join(routeDir, 'route'), compiledDependencyPath).replaceAll('\\', '/');
|
|
254
|
-
entryOutput = replaceSpecifier(entryOutput, specifier, nextSpecifier.startsWith('.') ? nextSpecifier : `./${nextSpecifier}`);
|
|
255
|
-
}
|
|
256
|
-
const routeModulePath = join(routeDir, 'route', 'entry.js');
|
|
257
|
-
await mkdir(dirname(routeModulePath), { recursive: true });
|
|
258
|
-
await writeFile(routeModulePath, entryOutput, 'utf8');
|
|
97
|
+
await writeServerModulePackage({
|
|
98
|
+
projectRoot,
|
|
99
|
+
serverDir,
|
|
100
|
+
entrySource: route.server_script || '',
|
|
101
|
+
entrySourcePath: route.server_script_path || 'route-entry.ts',
|
|
102
|
+
entryOutputPath: join(routeDir, 'route', 'entry.js'),
|
|
103
|
+
modulesRoot: join(routeDir, 'modules')
|
|
104
|
+
});
|
|
259
105
|
}
|
|
260
106
|
async function copyRuntimeFiles(serverDir) {
|
|
261
107
|
for (const file of SERVER_RUNTIME_FILES) {
|
|
@@ -275,7 +121,81 @@ async function copyOptionalFile(sourcePath, targetPath) {
|
|
|
275
121
|
await cp(sourcePath, targetPath, { force: true });
|
|
276
122
|
return true;
|
|
277
123
|
}
|
|
278
|
-
|
|
124
|
+
async function writeGlobalMiddlewarePackage({ projectRoot, serverDir, globalMiddleware }) {
|
|
125
|
+
const metadata = normalizeGlobalMiddlewareMetadata(globalMiddleware);
|
|
126
|
+
if (!metadata) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const sourcePath = resolve(projectRoot, metadata.source_file);
|
|
130
|
+
if (!existsSync(sourcePath)) {
|
|
131
|
+
throw new Error(`[Zenith:Middleware] Cannot emit global middleware because source file "${metadata.source_file}" was not found.`);
|
|
132
|
+
}
|
|
133
|
+
await writeServerModulePackage({
|
|
134
|
+
projectRoot,
|
|
135
|
+
serverDir,
|
|
136
|
+
entrySource: await readFile(sourcePath, 'utf8'),
|
|
137
|
+
entrySourcePath: sourcePath,
|
|
138
|
+
entryOutputPath: join(serverDir, GLOBAL_MIDDLEWARE_MODULE),
|
|
139
|
+
modulesRoot: join(serverDir, 'global-middleware', 'modules'),
|
|
140
|
+
validateMiddlewareImports: true
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
...metadata,
|
|
144
|
+
module: GLOBAL_MIDDLEWARE_MODULE
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function mergePageRouteMetadata(route, manifestEntry) {
|
|
148
|
+
if (!manifestEntry) {
|
|
149
|
+
return route;
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
...route,
|
|
153
|
+
file: manifestEntry.file || route.file || null,
|
|
154
|
+
has_scoped_server_data: manifestEntry.has_scoped_server_data === true || route.has_scoped_server_data === true,
|
|
155
|
+
scoped_server_data: Array.isArray(manifestEntry.scoped_server_data)
|
|
156
|
+
? manifestEntry.scoped_server_data
|
|
157
|
+
: (Array.isArray(route.scoped_server_data) ? route.scoped_server_data : [])
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function writeScopedServerDataModules({ projectRoot, serverDir, route, pagesDir, srcDir, registry, compilerOpts }) {
|
|
161
|
+
if (route.route_kind === 'resource' || route.has_scoped_server_data !== true) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
if (!pagesDir || !srcDir || !registry) {
|
|
165
|
+
throw new Error(`[Zenith:ScopedServerData] Cannot package scoped server data for route "${route.path}" without build context.`);
|
|
166
|
+
}
|
|
167
|
+
if (typeof route.file !== 'string' || route.file.length === 0) {
|
|
168
|
+
throw new Error(`[Zenith:ScopedServerData] Cannot package scoped server data for route "${route.path}" without a source file.`);
|
|
169
|
+
}
|
|
170
|
+
const pageFile = resolve(pagesDir, route.file);
|
|
171
|
+
const pageSource = await readFile(pageFile, 'utf8');
|
|
172
|
+
const lowering = await getScopedServerDataLowering();
|
|
173
|
+
const lowered = lowering.lowerRouteScopedServerData({
|
|
174
|
+
pageSource,
|
|
175
|
+
pageFile,
|
|
176
|
+
registry,
|
|
177
|
+
srcDir,
|
|
178
|
+
projectRoot,
|
|
179
|
+
compilerOpts,
|
|
180
|
+
scopedServerData: Array.isArray(route.scoped_server_data) ? route.scoped_server_data : []
|
|
181
|
+
});
|
|
182
|
+
if (!Array.isArray(lowered.scopedServerData) || lowered.scopedServerData.length === 0) {
|
|
183
|
+
throw new Error(`[Zenith:ScopedServerData] Cannot package scoped server data for route "${route.path}" because no owners were lowered.`);
|
|
184
|
+
}
|
|
185
|
+
for (const module of lowered.modules) {
|
|
186
|
+
const entryOutputPath = lowering.resolveScopedServerModuleOutputPath(serverDir, module.module);
|
|
187
|
+
await writeServerModulePackage({
|
|
188
|
+
projectRoot,
|
|
189
|
+
serverDir,
|
|
190
|
+
entrySource: module.source,
|
|
191
|
+
entrySourcePath: module.sourcePath,
|
|
192
|
+
entryOutputPath,
|
|
193
|
+
modulesRoot: join(serverDir, 'scoped', '_modules')
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return lowered.scopedServerData;
|
|
197
|
+
}
|
|
198
|
+
export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot, config, basePath = '/', globalMiddleware = null, pageManifest = [], pagesDir = null, srcDir = null, registry = null, compilerOpts = {} }) {
|
|
279
199
|
const serverDir = join(coreOutputDir, 'server');
|
|
280
200
|
await rm(serverDir, { recursive: true, force: true });
|
|
281
201
|
let routerManifest = { routes: [] };
|
|
@@ -286,20 +206,31 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
286
206
|
routerManifest = { routes: [] };
|
|
287
207
|
}
|
|
288
208
|
const resourceManifest = await loadResourceRouteManifest(staticDir, basePath);
|
|
289
|
-
const
|
|
209
|
+
const pageManifestByPath = new Map((Array.isArray(pageManifest) ? pageManifest : [])
|
|
210
|
+
.filter((entry) => entry?.route_kind !== 'resource')
|
|
211
|
+
.map((entry) => [entry.path, entry]));
|
|
212
|
+
const pageRoutes = (Array.isArray(routerManifest.routes) ? routerManifest.routes : [])
|
|
213
|
+
.map((route) => mergePageRouteMetadata(route, pageManifestByPath.get(route.path)));
|
|
290
214
|
const serverRoutes = pageRoutes
|
|
291
|
-
.filter((route) => route.
|
|
215
|
+
.filter((route) => route.prerender !== true && (route.server_script || route.has_scoped_server_data === true))
|
|
292
216
|
.map((route) => ({ ...route, route_kind: 'page' }))
|
|
293
217
|
.concat((Array.isArray(resourceManifest.routes) ? resourceManifest.routes : []).map((route) => ({
|
|
294
218
|
...route,
|
|
295
219
|
route_kind: 'resource'
|
|
296
220
|
})));
|
|
221
|
+
if (serverRoutes.some((route) => route.route_kind !== 'resource' && route.has_scoped_server_data === true)) {
|
|
222
|
+
await getScopedServerDataLowering();
|
|
223
|
+
}
|
|
297
224
|
await mkdir(serverDir, { recursive: true });
|
|
298
225
|
await copyRuntimeFiles(serverDir);
|
|
226
|
+
const serverGlobalMiddlewareMetadata = await writeGlobalMiddlewarePackage({
|
|
227
|
+
projectRoot,
|
|
228
|
+
serverDir,
|
|
229
|
+
globalMiddleware
|
|
230
|
+
});
|
|
299
231
|
const imageManifestSource = join(staticDir, '_zenith', 'image', 'manifest.json');
|
|
300
232
|
const emittedRoutes = [];
|
|
301
|
-
for (const route of serverRoutes) {
|
|
302
|
-
const name = normalizeRouteName(route.path);
|
|
233
|
+
for (const { route, name } of assignServerRouteNames(serverRoutes)) {
|
|
303
234
|
const routeDir = join(serverDir, 'routes', name);
|
|
304
235
|
await mkdir(routeDir, { recursive: true });
|
|
305
236
|
if (route.route_kind !== 'resource') {
|
|
@@ -324,6 +255,15 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
324
255
|
routeDir,
|
|
325
256
|
route
|
|
326
257
|
});
|
|
258
|
+
const scopedServerData = await writeScopedServerDataModules({
|
|
259
|
+
projectRoot,
|
|
260
|
+
serverDir,
|
|
261
|
+
route,
|
|
262
|
+
pagesDir,
|
|
263
|
+
srcDir,
|
|
264
|
+
registry,
|
|
265
|
+
compilerOpts
|
|
266
|
+
});
|
|
327
267
|
const meta = {
|
|
328
268
|
name,
|
|
329
269
|
path: route.path,
|
|
@@ -346,13 +286,22 @@ export async function writeServerOutput({ coreOutputDir, staticDir, projectRoot,
|
|
|
346
286
|
image_manifest_file: route.route_kind === 'resource' ? null : imageManifestFile,
|
|
347
287
|
image_config: config?.images || {}
|
|
348
288
|
};
|
|
289
|
+
if (scopedServerData.length > 0) {
|
|
290
|
+
meta.has_scoped_server_data = true;
|
|
291
|
+
meta.scoped_server_data = scopedServerData;
|
|
292
|
+
}
|
|
349
293
|
if (route.route_kind !== 'resource' && Array.isArray(route.image_materialization) && route.image_materialization.length > 0) {
|
|
350
294
|
meta.image_materialization = route.image_materialization;
|
|
351
295
|
}
|
|
352
296
|
await writeFile(join(routeDir, 'route.json'), `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
353
297
|
emittedRoutes.push(meta);
|
|
354
298
|
}
|
|
355
|
-
|
|
299
|
+
const serverManifest = {
|
|
300
|
+
base_path: basePath,
|
|
301
|
+
...(serverGlobalMiddlewareMetadata ? { global_middleware: serverGlobalMiddlewareMetadata } : {}),
|
|
302
|
+
routes: emittedRoutes
|
|
303
|
+
};
|
|
304
|
+
await writeFile(join(serverDir, 'manifest.json'), `${JSON.stringify(serverManifest, null, 2)}\n`, 'utf8');
|
|
356
305
|
return {
|
|
357
306
|
serverDir,
|
|
358
307
|
routes: emittedRoutes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export function normalizeRouteName(routePath) {
|
|
3
|
+
if (routePath === '/') {
|
|
4
|
+
return 'index';
|
|
5
|
+
}
|
|
6
|
+
return routePath
|
|
7
|
+
.replace(/^\//, '')
|
|
8
|
+
.replace(/\/+/g, '_')
|
|
9
|
+
.replace(/:/g, 'param_')
|
|
10
|
+
.replace(/\*/g, 'splat_')
|
|
11
|
+
.replace(/\?/g, 'opt')
|
|
12
|
+
.replace(/[^a-zA-Z0-9_]/g, '_')
|
|
13
|
+
.replace(/_+/g, '_')
|
|
14
|
+
.replace(/^_+|_+$/g, '') || 'route';
|
|
15
|
+
}
|
|
16
|
+
function routeNameHash(route) {
|
|
17
|
+
return createHash('sha256')
|
|
18
|
+
.update(`${route.route_kind || 'page'}:${route.path || ''}`)
|
|
19
|
+
.digest('hex')
|
|
20
|
+
.slice(0, 8);
|
|
21
|
+
}
|
|
22
|
+
export function assignServerRouteNames(routes) {
|
|
23
|
+
const used = new Set();
|
|
24
|
+
return routes.map((route) => {
|
|
25
|
+
const baseName = normalizeRouteName(route.path);
|
|
26
|
+
let name = baseName;
|
|
27
|
+
if (used.has(name)) {
|
|
28
|
+
name = `${baseName}_${routeNameHash(route)}`;
|
|
29
|
+
}
|
|
30
|
+
let suffix = 2;
|
|
31
|
+
while (used.has(name)) {
|
|
32
|
+
name = `${baseName}_${routeNameHash(route)}_${suffix}`;
|
|
33
|
+
suffix += 1;
|
|
34
|
+
}
|
|
35
|
+
used.add(name);
|
|
36
|
+
return { route, name };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { executeMatchedRoutePipeline, runGlobalMiddlewareChain } from "../server-contract/resolve.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { executeMatchedRoutePipeline, runGlobalMiddlewareChain } from '../server-contract/resolve.js';
|
|
@@ -11,6 +11,7 @@ import { executeRouteRequest, renderResourceRouteRequest, renderRouteRequest } f
|
|
|
11
11
|
import { resolveRequestRoute } from './resolve-request-route.js';
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = dirname(__filename);
|
|
14
|
+
const PUBLIC_ORIGIN_ENV = 'ZENITH_PUBLIC_ORIGIN';
|
|
14
15
|
const MIME_TYPES = {
|
|
15
16
|
'.html': 'text/html; charset=utf-8',
|
|
16
17
|
'.js': 'application/javascript; charset=utf-8',
|
|
@@ -60,6 +61,22 @@ function resolveWithinRoot(rootDir, requestPath) {
|
|
|
60
61
|
}
|
|
61
62
|
return null;
|
|
62
63
|
}
|
|
64
|
+
function resolveManifestMiddlewareModulePath(serverDir, serverManifest) {
|
|
65
|
+
const modulePath = serverManifest?.global_middleware?.module;
|
|
66
|
+
if (typeof modulePath !== 'string' || modulePath.trim().length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const normalized = normalize(modulePath).replace(/\\/g, '/');
|
|
70
|
+
if (normalized === '..' || normalized.startsWith('../') || normalized.startsWith('/')) {
|
|
71
|
+
throw new Error('[Zenith:Middleware] Invalid global middleware module path in server manifest.');
|
|
72
|
+
}
|
|
73
|
+
const root = resolve(serverDir);
|
|
74
|
+
const candidate = resolve(root, normalized);
|
|
75
|
+
if (candidate !== root && candidate.startsWith(`${root}${sep}`)) {
|
|
76
|
+
return candidate;
|
|
77
|
+
}
|
|
78
|
+
throw new Error('[Zenith:Middleware] Invalid global middleware module path in server manifest.');
|
|
79
|
+
}
|
|
63
80
|
function toStaticFilePath(staticDir, pathname) {
|
|
64
81
|
let resolvedPath = pathname;
|
|
65
82
|
if (resolvedPath === '/') {
|
|
@@ -192,6 +209,7 @@ async function loadRuntimeContext(options = {}) {
|
|
|
192
209
|
});
|
|
193
210
|
const serverManifest = await readJson(join(serverDir, 'manifest.json'), { routes: [] });
|
|
194
211
|
const allServerRoutes = Array.isArray(serverManifest.routes) ? serverManifest.routes : [];
|
|
212
|
+
const globalMiddlewareModulePath = resolveManifestMiddlewareModulePath(serverDir, serverManifest);
|
|
195
213
|
return {
|
|
196
214
|
distDir,
|
|
197
215
|
serverDir,
|
|
@@ -201,6 +219,7 @@ async function loadRuntimeContext(options = {}) {
|
|
|
201
219
|
serverRoutes: allServerRoutes,
|
|
202
220
|
pageServerRoutes: allServerRoutes.filter((route) => route?.route_kind !== 'resource'),
|
|
203
221
|
resourceServerRoutes: allServerRoutes.filter((route) => route?.route_kind === 'resource'),
|
|
222
|
+
globalMiddlewareModulePath,
|
|
204
223
|
images: config.images || {},
|
|
205
224
|
basePath: normalizeBasePath(config.base_path || '/')
|
|
206
225
|
};
|
|
@@ -322,7 +341,8 @@ async function handleNodeRequest(req, res, context, serverOrigin) {
|
|
|
322
341
|
request,
|
|
323
342
|
route: resourceResolved.route,
|
|
324
343
|
params: resourceResolved.params,
|
|
325
|
-
routeModulePath: join(routeDir, 'route', 'entry.js')
|
|
344
|
+
routeModulePath: join(routeDir, 'route', 'entry.js'),
|
|
345
|
+
globalMiddlewareModulePath: context.globalMiddlewareModulePath
|
|
326
346
|
});
|
|
327
347
|
await sendFetchResponse(res, response, req.method);
|
|
328
348
|
return;
|
|
@@ -336,6 +356,7 @@ async function handleNodeRequest(req, res, context, serverOrigin) {
|
|
|
336
356
|
route: serverResolved.route,
|
|
337
357
|
params: serverResolved.params,
|
|
338
358
|
routeModulePath: join(routeDir, 'route', 'entry.js'),
|
|
359
|
+
globalMiddlewareModulePath: context.globalMiddlewareModulePath,
|
|
339
360
|
shellHtmlPath: join(routeDir, 'route', 'page.html'),
|
|
340
361
|
imageManifestPath: serverResolved.route.image_manifest_file
|
|
341
362
|
? join(routeDir, 'route', serverResolved.route.image_manifest_file)
|
|
@@ -375,8 +396,9 @@ function createNodeRequestHandler(context, resolveServerOrigin) {
|
|
|
375
396
|
}
|
|
376
397
|
export async function createRequestHandler(options = {}) {
|
|
377
398
|
const context = await loadRuntimeContext(options);
|
|
399
|
+
const publicOrigin = options.publicOrigin ?? process.env[PUBLIC_ORIGIN_ENV];
|
|
378
400
|
const resolveServerOrigin = createTrustedOriginResolver({
|
|
379
|
-
publicOrigin
|
|
401
|
+
publicOrigin,
|
|
380
402
|
host: options.host || '127.0.0.1',
|
|
381
403
|
port: Number.isInteger(options.port) ? options.port : undefined,
|
|
382
404
|
label: 'createRequestHandler()'
|
|
@@ -388,8 +410,9 @@ export async function createNodeServer(options = {}) {
|
|
|
388
410
|
const { port = 3000, host = '127.0.0.1' } = options;
|
|
389
411
|
const context = await loadRuntimeContext(options);
|
|
390
412
|
let actualPort = Number.isInteger(port) && port > 0 ? port : 0;
|
|
413
|
+
const publicOrigin = options.publicOrigin ?? process.env[PUBLIC_ORIGIN_ENV];
|
|
391
414
|
const resolveServerOrigin = createTrustedOriginResolver({
|
|
392
|
-
publicOrigin
|
|
415
|
+
publicOrigin,
|
|
393
416
|
host,
|
|
394
417
|
getPort: () => actualPort,
|
|
395
418
|
label: 'createNodeServer()'
|
|
@@ -6,9 +6,10 @@ export function extractInternalParams(requestUrl: any, route: any): {};
|
|
|
6
6
|
* route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null },
|
|
7
7
|
* params: Record<string, string>,
|
|
8
8
|
* routeModulePath: string,
|
|
9
|
+
* globalMiddlewareModulePath?: string | null,
|
|
9
10
|
* guardOnly?: boolean
|
|
10
11
|
* }} options
|
|
11
|
-
* @returns {Promise<{ publicUrl: URL, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
|
|
12
|
+
* @returns {Promise<{ publicUrl: URL, ctx: object, result: { kind: string, [key: string]: unknown }, trace: { guard: string, action: string, load: string }, status?: number, setCookies?: string[] }>}
|
|
12
13
|
*/
|
|
13
14
|
export function executeRouteRequest(options: {
|
|
14
15
|
request: Request;
|
|
@@ -21,9 +22,11 @@ export function executeRouteRequest(options: {
|
|
|
21
22
|
};
|
|
22
23
|
params: Record<string, string>;
|
|
23
24
|
routeModulePath: string;
|
|
25
|
+
globalMiddlewareModulePath?: string | null;
|
|
24
26
|
guardOnly?: boolean;
|
|
25
27
|
}): Promise<{
|
|
26
28
|
publicUrl: URL;
|
|
29
|
+
ctx: object;
|
|
27
30
|
result: {
|
|
28
31
|
kind: string;
|
|
29
32
|
[key: string]: unknown;
|
|
@@ -42,9 +45,11 @@ export function executeRouteRequest(options: {
|
|
|
42
45
|
* route: { path: string, params?: string[], route_id?: string | null, server_script_path?: string | null, file?: string | null },
|
|
43
46
|
* params: Record<string, string>,
|
|
44
47
|
* routeModulePath: string,
|
|
48
|
+
* globalMiddlewareModulePath?: string | null,
|
|
45
49
|
* shellHtmlPath: string,
|
|
46
50
|
* imageManifestPath?: string | null,
|
|
47
|
-
* imageConfig?: Record<string, unknown
|
|
51
|
+
* imageConfig?: Record<string, unknown>,
|
|
52
|
+
* scopedModuleBaseDir?: string | null
|
|
48
53
|
* }} options
|
|
49
54
|
* @returns {Promise<Response>}
|
|
50
55
|
*/
|
|
@@ -59,16 +64,19 @@ export function renderRouteRequest(options: {
|
|
|
59
64
|
};
|
|
60
65
|
params: Record<string, string>;
|
|
61
66
|
routeModulePath: string;
|
|
67
|
+
globalMiddlewareModulePath?: string | null;
|
|
62
68
|
shellHtmlPath: string;
|
|
63
69
|
imageManifestPath?: string | null;
|
|
64
70
|
imageConfig?: Record<string, unknown>;
|
|
71
|
+
scopedModuleBaseDir?: string | null;
|
|
65
72
|
}): Promise<Response>;
|
|
66
73
|
/**
|
|
67
74
|
* @param {{
|
|
68
75
|
* request: Request,
|
|
69
76
|
* 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
77
|
* params: Record<string, string>,
|
|
71
|
-
* routeModulePath: string
|
|
78
|
+
* routeModulePath: string,
|
|
79
|
+
* globalMiddlewareModulePath?: string | null
|
|
72
80
|
* }} options
|
|
73
81
|
* @returns {Promise<Response>}
|
|
74
82
|
*/
|
|
@@ -85,4 +93,5 @@ export function renderResourceRouteRequest(options: {
|
|
|
85
93
|
};
|
|
86
94
|
params: Record<string, string>;
|
|
87
95
|
routeModulePath: string;
|
|
96
|
+
globalMiddlewareModulePath?: string | null;
|
|
88
97
|
}): Promise<Response>;
|