@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
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { createScopedServerDiagnostic, SCOPED_SERVER_DIAGNOSTIC } from './diagnostics.js';
|
|
3
|
+
const PACKAGE_REQUIRE = createRequire(import.meta.url);
|
|
4
|
+
let TYPESCRIPT_API;
|
|
5
|
+
export function parseScopedComponentStaticProps(options) {
|
|
6
|
+
const props = {};
|
|
7
|
+
const diagnostics = [];
|
|
8
|
+
const attrs = String(options.attrs || '').trim();
|
|
9
|
+
if (!attrs) {
|
|
10
|
+
return { props, diagnostics };
|
|
11
|
+
}
|
|
12
|
+
let tokens;
|
|
13
|
+
try {
|
|
14
|
+
tokens = tokenizeAttrs(attrs);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
diagnostics.push(createUnsupportedPropDiagnostic(options, null, error instanceof Error ? error.message : 'attribute syntax is unsupported'));
|
|
18
|
+
return { props, diagnostics };
|
|
19
|
+
}
|
|
20
|
+
for (const token of tokens) {
|
|
21
|
+
if (isEventLikeProp(token.name)) {
|
|
22
|
+
diagnostics.push(createUnsupportedPropDiagnostic(options, token.name, 'event/function props require runtime evaluation'));
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (token.valueKind === 'bare') {
|
|
26
|
+
props[token.name] = true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (token.valueKind === 'quoted') {
|
|
30
|
+
props[token.name] = parseQuotedAttrValue(token.value || '', token.quote || '"');
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const parsed = parseLiteralExpression(token.value || '');
|
|
34
|
+
if (parsed.ok) {
|
|
35
|
+
props[token.name] = parsed.value;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
diagnostics.push(createUnsupportedPropDiagnostic(options, token.name, parsed.reason));
|
|
39
|
+
}
|
|
40
|
+
return { props, diagnostics };
|
|
41
|
+
}
|
|
42
|
+
function tokenizeAttrs(attrs) {
|
|
43
|
+
const tokens = [];
|
|
44
|
+
let index = 0;
|
|
45
|
+
while (index < attrs.length) {
|
|
46
|
+
index = skipWhitespace(attrs, index);
|
|
47
|
+
if (index >= attrs.length) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (attrs[index] === '{' && attrs.slice(index + 1).trimStart().startsWith('...')) {
|
|
51
|
+
throw new Error('spread props are unsupported');
|
|
52
|
+
}
|
|
53
|
+
const nameStart = index;
|
|
54
|
+
while (index < attrs.length && /[A-Za-z0-9_$:-]/.test(attrs[index])) {
|
|
55
|
+
index += 1;
|
|
56
|
+
}
|
|
57
|
+
const name = attrs.slice(nameStart, index);
|
|
58
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$:-]*$/.test(name)) {
|
|
59
|
+
throw new Error(`unsupported prop name near "${attrs.slice(nameStart, nameStart + 16).trim()}"`);
|
|
60
|
+
}
|
|
61
|
+
index = skipWhitespace(attrs, index);
|
|
62
|
+
if (attrs[index] !== '=') {
|
|
63
|
+
tokens.push({ name, valueKind: 'bare' });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
index += 1;
|
|
67
|
+
index = skipWhitespace(attrs, index);
|
|
68
|
+
const quote = attrs[index];
|
|
69
|
+
if (quote === '"' || quote === "'") {
|
|
70
|
+
const parsed = readQuoted(attrs, index, quote);
|
|
71
|
+
tokens.push({ name, valueKind: 'quoted', value: parsed.value, quote });
|
|
72
|
+
index = parsed.nextIndex;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (attrs[index] === '{') {
|
|
76
|
+
const parsed = readBraced(attrs, index);
|
|
77
|
+
tokens.push({ name, valueKind: 'expression', value: parsed.value });
|
|
78
|
+
index = parsed.nextIndex;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`unsupported value syntax for prop "${name}"`);
|
|
82
|
+
}
|
|
83
|
+
return tokens;
|
|
84
|
+
}
|
|
85
|
+
function readQuoted(source, start, quote) {
|
|
86
|
+
let index = start + 1;
|
|
87
|
+
let value = '';
|
|
88
|
+
while (index < source.length) {
|
|
89
|
+
const ch = source[index];
|
|
90
|
+
if (ch === '\\') {
|
|
91
|
+
const next = source[index + 1];
|
|
92
|
+
if (next === undefined) {
|
|
93
|
+
throw new Error('unterminated quoted prop value');
|
|
94
|
+
}
|
|
95
|
+
value += ch + next;
|
|
96
|
+
index += 2;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (ch === quote) {
|
|
100
|
+
return { value, nextIndex: index + 1 };
|
|
101
|
+
}
|
|
102
|
+
value += ch;
|
|
103
|
+
index += 1;
|
|
104
|
+
}
|
|
105
|
+
throw new Error('unterminated quoted prop value');
|
|
106
|
+
}
|
|
107
|
+
function readBraced(source, start) {
|
|
108
|
+
let index = start + 1;
|
|
109
|
+
let depth = 1;
|
|
110
|
+
let mode = 'code';
|
|
111
|
+
let escaped = false;
|
|
112
|
+
while (index < source.length) {
|
|
113
|
+
const ch = source[index];
|
|
114
|
+
if (mode !== 'code') {
|
|
115
|
+
if (escaped) {
|
|
116
|
+
escaped = false;
|
|
117
|
+
index += 1;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (ch === '\\') {
|
|
121
|
+
escaped = true;
|
|
122
|
+
index += 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if ((mode === 'single' && ch === "'") ||
|
|
126
|
+
(mode === 'double' && ch === '"') ||
|
|
127
|
+
(mode === 'template' && ch === '`')) {
|
|
128
|
+
mode = 'code';
|
|
129
|
+
}
|
|
130
|
+
index += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (ch === "'") {
|
|
134
|
+
mode = 'single';
|
|
135
|
+
index += 1;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (ch === '"') {
|
|
139
|
+
mode = 'double';
|
|
140
|
+
index += 1;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (ch === '`') {
|
|
144
|
+
mode = 'template';
|
|
145
|
+
index += 1;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ch === '{') {
|
|
149
|
+
depth += 1;
|
|
150
|
+
}
|
|
151
|
+
else if (ch === '}') {
|
|
152
|
+
depth -= 1;
|
|
153
|
+
if (depth === 0) {
|
|
154
|
+
return { value: source.slice(start + 1, index), nextIndex: index + 1 };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
index += 1;
|
|
158
|
+
}
|
|
159
|
+
throw new Error('unterminated braced prop expression');
|
|
160
|
+
}
|
|
161
|
+
function parseQuotedAttrValue(value, quote) {
|
|
162
|
+
let out = '';
|
|
163
|
+
let index = 0;
|
|
164
|
+
while (index < value.length) {
|
|
165
|
+
const ch = value[index];
|
|
166
|
+
if (ch === '\\' && value[index + 1] === quote) {
|
|
167
|
+
out += quote;
|
|
168
|
+
index += 2;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
out += ch;
|
|
172
|
+
index += 1;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
function parseLiteralExpression(expr) {
|
|
177
|
+
const ts = loadTypeScriptApi();
|
|
178
|
+
if (!ts) {
|
|
179
|
+
return { ok: false, reason: 'TypeScript parser is unavailable' };
|
|
180
|
+
}
|
|
181
|
+
const source = `const __zenith_static_prop__ = (${String(expr || '').trim()});`;
|
|
182
|
+
const sourceFile = ts.createSourceFile('zenith-scoped-static-prop.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
183
|
+
const parseDiagnostics = sourceFile.parseDiagnostics || [];
|
|
184
|
+
if (parseDiagnostics.length > 0) {
|
|
185
|
+
return { ok: false, reason: 'expression syntax is unsupported' };
|
|
186
|
+
}
|
|
187
|
+
const statement = sourceFile.statements[0];
|
|
188
|
+
if (!statement || !ts.isVariableStatement(statement)) {
|
|
189
|
+
return { ok: false, reason: 'expression syntax is unsupported' };
|
|
190
|
+
}
|
|
191
|
+
const initializer = statement.declarationList.declarations[0]?.initializer;
|
|
192
|
+
const root = initializer && ts.isParenthesizedExpression(initializer) ? initializer.expression : initializer;
|
|
193
|
+
if (!root) {
|
|
194
|
+
return { ok: false, reason: 'expression syntax is unsupported' };
|
|
195
|
+
}
|
|
196
|
+
return literalValueFromNode(root, ts);
|
|
197
|
+
}
|
|
198
|
+
function literalValueFromNode(node, ts) {
|
|
199
|
+
if (ts.isParenthesizedExpression(node)) {
|
|
200
|
+
return literalValueFromNode(node.expression, ts);
|
|
201
|
+
}
|
|
202
|
+
if (ts.isStringLiteral(node)) {
|
|
203
|
+
return { ok: true, value: node.text };
|
|
204
|
+
}
|
|
205
|
+
if (ts.isNumericLiteral(node)) {
|
|
206
|
+
return { ok: true, value: Number(node.text) };
|
|
207
|
+
}
|
|
208
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword) {
|
|
209
|
+
return { ok: true, value: true };
|
|
210
|
+
}
|
|
211
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword) {
|
|
212
|
+
return { ok: true, value: false };
|
|
213
|
+
}
|
|
214
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) {
|
|
215
|
+
return { ok: true, value: null };
|
|
216
|
+
}
|
|
217
|
+
if (ts.isPrefixUnaryExpression(node) &&
|
|
218
|
+
(node.operator === ts.SyntaxKind.MinusToken || node.operator === ts.SyntaxKind.PlusToken) &&
|
|
219
|
+
ts.isNumericLiteral(node.operand)) {
|
|
220
|
+
const value = Number(node.operand.text);
|
|
221
|
+
return { ok: true, value: node.operator === ts.SyntaxKind.MinusToken ? -value : value };
|
|
222
|
+
}
|
|
223
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
224
|
+
const out = [];
|
|
225
|
+
for (const element of node.elements) {
|
|
226
|
+
if (ts.isSpreadElement(element)) {
|
|
227
|
+
return { ok: false, reason: 'array spreads require runtime evaluation' };
|
|
228
|
+
}
|
|
229
|
+
const parsed = literalValueFromNode(element, ts);
|
|
230
|
+
if (!parsed.ok) {
|
|
231
|
+
return parsed;
|
|
232
|
+
}
|
|
233
|
+
out.push(parsed.value);
|
|
234
|
+
}
|
|
235
|
+
return { ok: true, value: out };
|
|
236
|
+
}
|
|
237
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
238
|
+
const out = {};
|
|
239
|
+
for (const prop of node.properties) {
|
|
240
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
241
|
+
return { ok: false, reason: 'object shorthand, methods, and spreads require runtime evaluation' };
|
|
242
|
+
}
|
|
243
|
+
const key = propertyNameToString(prop.name, ts);
|
|
244
|
+
if (key == null) {
|
|
245
|
+
return { ok: false, reason: 'computed object keys require runtime evaluation' };
|
|
246
|
+
}
|
|
247
|
+
const parsed = literalValueFromNode(prop.initializer, ts);
|
|
248
|
+
if (!parsed.ok) {
|
|
249
|
+
return parsed;
|
|
250
|
+
}
|
|
251
|
+
out[key] = parsed.value;
|
|
252
|
+
}
|
|
253
|
+
return { ok: true, value: out };
|
|
254
|
+
}
|
|
255
|
+
return { ok: false, reason: unsupportedExpressionReason(node, ts) };
|
|
256
|
+
}
|
|
257
|
+
function propertyNameToString(name, ts) {
|
|
258
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
259
|
+
return name.text;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
function unsupportedExpressionReason(node, ts) {
|
|
264
|
+
if (ts.isIdentifier(node)) {
|
|
265
|
+
return 'identifiers require runtime evaluation';
|
|
266
|
+
}
|
|
267
|
+
if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) {
|
|
268
|
+
return 'member expressions require runtime evaluation';
|
|
269
|
+
}
|
|
270
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
271
|
+
return 'function calls require runtime evaluation';
|
|
272
|
+
}
|
|
273
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
274
|
+
return 'function values are unsupported';
|
|
275
|
+
}
|
|
276
|
+
if (ts.isTemplateExpression(node) || node.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
277
|
+
return 'template literals are unsupported for scoped component props';
|
|
278
|
+
}
|
|
279
|
+
return 'expression requires runtime evaluation';
|
|
280
|
+
}
|
|
281
|
+
function createUnsupportedPropDiagnostic(options, propName, reason) {
|
|
282
|
+
const occurrence = options.occurrenceId ? ` occurrence "${options.occurrenceId}"` : '';
|
|
283
|
+
const prop = propName ? ` prop "${propName}"` : ' prop expression';
|
|
284
|
+
return createScopedServerDiagnostic(SCOPED_SERVER_DIAGNOSTIC.UNSUPPORTED_COMPONENT_PROP, 'error', `Unsupported scoped component prop expression for "${options.ownerKey}"${occurrence}${prop}: ${reason}.`, options.contextFile);
|
|
285
|
+
}
|
|
286
|
+
function isEventLikeProp(name) {
|
|
287
|
+
return name.startsWith('on:') || /^on[A-Z]/.test(name);
|
|
288
|
+
}
|
|
289
|
+
function skipWhitespace(source, index) {
|
|
290
|
+
let cursor = index;
|
|
291
|
+
while (cursor < source.length && /\s/.test(source[cursor])) {
|
|
292
|
+
cursor += 1;
|
|
293
|
+
}
|
|
294
|
+
return cursor;
|
|
295
|
+
}
|
|
296
|
+
function loadTypeScriptApi() {
|
|
297
|
+
if (TYPESCRIPT_API !== undefined) {
|
|
298
|
+
return TYPESCRIPT_API;
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
TYPESCRIPT_API = PACKAGE_REQUIRE('typescript');
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
TYPESCRIPT_API = null;
|
|
305
|
+
}
|
|
306
|
+
return TYPESCRIPT_API;
|
|
307
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ManifestScopedServerDataEntry } from './types.js';
|
|
2
|
+
interface RouteManifestEntry {
|
|
3
|
+
scoped_server_data?: ManifestScopedServerDataEntry[];
|
|
4
|
+
}
|
|
5
|
+
export interface RenderScopedServerDataDtsOptions {
|
|
6
|
+
manifest: RouteManifestEntry[];
|
|
7
|
+
srcDir: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function renderScopedServerDataDts(options: RenderScopedServerDataDtsOptions): string;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { isAbsolute, resolve, sep } from 'node:path';
|
|
4
|
+
import { partitionScriptBlocks } from './parse-owner-server-block.js';
|
|
5
|
+
const PACKAGE_REQUIRE = createRequire(import.meta.url);
|
|
6
|
+
let TYPESCRIPT_API;
|
|
7
|
+
export function renderScopedServerDataDts(options) {
|
|
8
|
+
const srcDir = resolve(String(options.srcDir || ''));
|
|
9
|
+
const entries = collectScopedEntries(options.manifest);
|
|
10
|
+
const owners = new Map();
|
|
11
|
+
const runtimeKeys = new Map();
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (!entry || typeof entry.ownerKey !== 'string' || entry.ownerKey.length === 0) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (!owners.has(entry.ownerKey)) {
|
|
17
|
+
owners.set(entry.ownerKey, entry);
|
|
18
|
+
}
|
|
19
|
+
for (const key of runtimeKeysForEntry(entry)) {
|
|
20
|
+
if (!runtimeKeys.has(key)) {
|
|
21
|
+
runtimeKeys.set(key, entry.ownerKey);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const ownerDeclarations = [...owners.entries()]
|
|
26
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
27
|
+
.map(([key, entry]) => ({
|
|
28
|
+
key,
|
|
29
|
+
type: inferOwnerType(entry, resolveOwnerFilePath(key, srcDir))
|
|
30
|
+
}));
|
|
31
|
+
const runtimeDeclarations = [...runtimeKeys.entries()]
|
|
32
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
33
|
+
return renderDts(ownerDeclarations, runtimeDeclarations);
|
|
34
|
+
}
|
|
35
|
+
function collectScopedEntries(manifest) {
|
|
36
|
+
const entries = [];
|
|
37
|
+
for (const route of Array.isArray(manifest) ? manifest : []) {
|
|
38
|
+
const scoped = Array.isArray(route?.scoped_server_data) ? route.scoped_server_data : [];
|
|
39
|
+
for (const entry of scoped) {
|
|
40
|
+
entries.push(entry);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return entries;
|
|
44
|
+
}
|
|
45
|
+
function runtimeKeysForEntry(entry) {
|
|
46
|
+
if (entry.ownerKind === 'layout') {
|
|
47
|
+
return [`layout:${entry.ownerKey}`];
|
|
48
|
+
}
|
|
49
|
+
if (entry.ownerKind === 'component' && entry.instanceStrategy === 'per-instance') {
|
|
50
|
+
return Array.isArray(entry.instances)
|
|
51
|
+
? entry.instances.map((item) => item.key).filter((key) => typeof key === 'string' && key.length > 0)
|
|
52
|
+
: [];
|
|
53
|
+
}
|
|
54
|
+
if (entry.ownerKind === 'component') {
|
|
55
|
+
return [`component:${entry.ownerKey}`];
|
|
56
|
+
}
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
function resolveOwnerFilePath(ownerKey, srcDir) {
|
|
60
|
+
const raw = String(ownerKey || '').replace(/\\/g, '/');
|
|
61
|
+
if (!raw || isAbsolute(raw) || /^[A-Za-z]:[\\/]/.test(raw)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const relative = raw.startsWith('src/') ? raw.slice(4) : raw;
|
|
65
|
+
const parts = relative.split('/');
|
|
66
|
+
if (parts.some((part) => part.length === 0 || part === '.' || part === '..')) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const resolved = resolve(srcDir, ...parts);
|
|
70
|
+
if (resolved !== srcDir && !resolved.startsWith(`${srcDir}${sep}`)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return resolved;
|
|
74
|
+
}
|
|
75
|
+
function inferOwnerType(entry, ownerPath) {
|
|
76
|
+
if (!ownerPath || !existsSync(ownerPath)) {
|
|
77
|
+
return entry.syntax === 'explicit-data' ? 'Record<string, unknown>' : variablesType(entry, new Map());
|
|
78
|
+
}
|
|
79
|
+
const ts = loadTypeScriptApi();
|
|
80
|
+
if (!ts) {
|
|
81
|
+
return entry.syntax === 'explicit-data' ? 'Record<string, unknown>' : variablesType(entry, new Map());
|
|
82
|
+
}
|
|
83
|
+
const source = readFileSync(ownerPath, 'utf8');
|
|
84
|
+
const serverBody = readSingleServerBody(source);
|
|
85
|
+
if (!serverBody) {
|
|
86
|
+
return entry.syntax === 'explicit-data' ? 'Record<string, unknown>' : variablesType(entry, new Map());
|
|
87
|
+
}
|
|
88
|
+
const sourceFile = ts.createSourceFile(ownerPath, serverBody, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
89
|
+
if (entry.syntax === 'explicit-data') {
|
|
90
|
+
return inferExplicitDataType(sourceFile, ts);
|
|
91
|
+
}
|
|
92
|
+
const variableTypes = inferTopLevelVariableTypes(sourceFile, ts, serverBody);
|
|
93
|
+
return variablesType(entry, variableTypes);
|
|
94
|
+
}
|
|
95
|
+
function readSingleServerBody(source) {
|
|
96
|
+
const { serverBlocks } = partitionScriptBlocks(source);
|
|
97
|
+
return serverBlocks.length === 1 ? String(serverBlocks[0]?.body || '').trim() : '';
|
|
98
|
+
}
|
|
99
|
+
function inferTopLevelVariableTypes(sourceFile, ts, source) {
|
|
100
|
+
const out = new Map();
|
|
101
|
+
for (const statement of sourceFile.statements) {
|
|
102
|
+
if (!ts.isVariableStatement(statement)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
106
|
+
if (!ts.isIdentifier(declaration.name)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const annotated = declaration.type ? safeTypeAnnotation(declaration.type, ts, source) : null;
|
|
110
|
+
const inferred = annotated || (declaration.initializer ? inferExpressionType(declaration.initializer, ts) : null);
|
|
111
|
+
out.set(declaration.name.text, inferred || 'unknown');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function inferExplicitDataType(sourceFile, ts) {
|
|
117
|
+
for (const statement of sourceFile.statements) {
|
|
118
|
+
if (!ts.isVariableStatement(statement)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const hasExport = statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
122
|
+
if (!hasExport) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
126
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== 'data') {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const inferred = declaration.initializer ? inferDataInitializerReturn(declaration.initializer, ts) : null;
|
|
130
|
+
return inferred && inferred.startsWith('{') ? inferred : 'Record<string, unknown>';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return 'Record<string, unknown>';
|
|
134
|
+
}
|
|
135
|
+
function inferDataInitializerReturn(node, ts) {
|
|
136
|
+
const target = unwrapExpression(node, ts);
|
|
137
|
+
if (ts.isArrowFunction(target) || ts.isFunctionExpression(target)) {
|
|
138
|
+
if (target.type) {
|
|
139
|
+
const annotated = safeScopedDataReturnAnnotation(target.type, ts, target.getSourceFile().text);
|
|
140
|
+
if (annotated && annotated.startsWith('{')) {
|
|
141
|
+
return annotated;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (ts.isBlock(target.body)) {
|
|
145
|
+
const returned = singleReturnExpression(target.body, ts);
|
|
146
|
+
return returned ? inferExpressionType(returned, ts) : null;
|
|
147
|
+
}
|
|
148
|
+
return inferExpressionType(target.body, ts);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function singleReturnExpression(block, ts) {
|
|
153
|
+
const returns = block.statements.filter(ts.isReturnStatement);
|
|
154
|
+
if (returns.length !== 1) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return returns[0].expression || null;
|
|
158
|
+
}
|
|
159
|
+
function inferExpressionType(node, ts) {
|
|
160
|
+
const expr = unwrapExpression(node, ts);
|
|
161
|
+
if (ts.isStringLiteral(expr) || expr.kind === ts.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
162
|
+
return 'string';
|
|
163
|
+
}
|
|
164
|
+
if (ts.isNumericLiteral(expr)) {
|
|
165
|
+
return 'number';
|
|
166
|
+
}
|
|
167
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword || expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
168
|
+
return 'boolean';
|
|
169
|
+
}
|
|
170
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
171
|
+
return 'null';
|
|
172
|
+
}
|
|
173
|
+
if (ts.isPrefixUnaryExpression(expr) &&
|
|
174
|
+
(expr.operator === ts.SyntaxKind.MinusToken || expr.operator === ts.SyntaxKind.PlusToken) &&
|
|
175
|
+
ts.isNumericLiteral(expr.operand)) {
|
|
176
|
+
return 'number';
|
|
177
|
+
}
|
|
178
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
179
|
+
return inferObjectType(expr, ts);
|
|
180
|
+
}
|
|
181
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
182
|
+
return inferArrayType(expr, ts);
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
function inferObjectType(node, ts) {
|
|
187
|
+
const fields = [];
|
|
188
|
+
for (const prop of node.properties) {
|
|
189
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
const name = propertyNameToString(prop.name, ts);
|
|
193
|
+
if (name == null) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const valueType = inferExpressionType(prop.initializer, ts);
|
|
197
|
+
if (!valueType) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
fields.push(`${safePropertyName(name)}: ${valueType};`);
|
|
201
|
+
}
|
|
202
|
+
return `{ ${fields.join(' ')} }`;
|
|
203
|
+
}
|
|
204
|
+
function inferArrayType(node, ts) {
|
|
205
|
+
const elementTypes = [];
|
|
206
|
+
for (const element of node.elements) {
|
|
207
|
+
if (ts.isSpreadElement(element)) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
const type = inferExpressionType(element, ts);
|
|
211
|
+
if (!type) {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
if (!elementTypes.includes(type)) {
|
|
215
|
+
elementTypes.push(type);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (elementTypes.length === 0) {
|
|
219
|
+
return 'unknown[]';
|
|
220
|
+
}
|
|
221
|
+
return elementTypes.length === 1 ? `${elementTypes[0]}[]` : `Array<${elementTypes.join(' | ')}>`;
|
|
222
|
+
}
|
|
223
|
+
function safeTypeAnnotation(node, ts, source) {
|
|
224
|
+
if (node.kind === ts.SyntaxKind.StringKeyword ||
|
|
225
|
+
node.kind === ts.SyntaxKind.NumberKeyword ||
|
|
226
|
+
node.kind === ts.SyntaxKind.BooleanKeyword ||
|
|
227
|
+
node.kind === ts.SyntaxKind.UnknownKeyword) {
|
|
228
|
+
return node.getText();
|
|
229
|
+
}
|
|
230
|
+
if (ts.isLiteralTypeNode(node)) {
|
|
231
|
+
return literalTypeText(node, ts);
|
|
232
|
+
}
|
|
233
|
+
if (ts.isArrayTypeNode(node)) {
|
|
234
|
+
const item = safeTypeAnnotation(node.elementType, ts, source);
|
|
235
|
+
return item ? `${item}[]` : null;
|
|
236
|
+
}
|
|
237
|
+
if (ts.isTupleTypeNode(node)) {
|
|
238
|
+
const items = node.elements.map((item) => safeTypeAnnotation(item, ts, source));
|
|
239
|
+
return items.every(Boolean) ? `[${items.join(', ')}]` : null;
|
|
240
|
+
}
|
|
241
|
+
if (ts.isUnionTypeNode(node)) {
|
|
242
|
+
const items = node.types.map((item) => safeTypeAnnotation(item, ts, source));
|
|
243
|
+
return items.every(Boolean) ? items.join(' | ') : null;
|
|
244
|
+
}
|
|
245
|
+
if (ts.isTypeLiteralNode(node)) {
|
|
246
|
+
return safeTypeLiteral(node, ts, source);
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
function safeScopedDataReturnAnnotation(node, ts, source) {
|
|
251
|
+
if (ts.isTypeReferenceNode(node) && isPromiseLikeTypeReference(node, ts)) {
|
|
252
|
+
const inner = node.typeArguments?.[0];
|
|
253
|
+
return inner ? safeTypeAnnotation(inner, ts, source) : null;
|
|
254
|
+
}
|
|
255
|
+
return safeTypeAnnotation(node, ts, source);
|
|
256
|
+
}
|
|
257
|
+
function isPromiseLikeTypeReference(node, ts) {
|
|
258
|
+
if (!ts.isIdentifier(node.typeName)) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
return node.typeName.text === 'Promise' || node.typeName.text === 'PromiseLike';
|
|
262
|
+
}
|
|
263
|
+
function safeTypeLiteral(node, ts, source) {
|
|
264
|
+
const fields = [];
|
|
265
|
+
for (const member of node.members) {
|
|
266
|
+
if (!ts.isPropertySignature(member) || !member.type || !member.name) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
const name = propertyNameToString(member.name, ts);
|
|
270
|
+
const type = safeTypeAnnotation(member.type, ts, source);
|
|
271
|
+
if (name == null || !type) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
fields.push(`${safePropertyName(name)}${member.questionToken ? '?' : ''}: ${type};`);
|
|
275
|
+
}
|
|
276
|
+
return `{ ${fields.join(' ')} }`;
|
|
277
|
+
}
|
|
278
|
+
function literalTypeText(node, ts) {
|
|
279
|
+
const literal = node.literal;
|
|
280
|
+
if (ts.isStringLiteral(literal)) {
|
|
281
|
+
return JSON.stringify(literal.text);
|
|
282
|
+
}
|
|
283
|
+
if (ts.isNumericLiteral(literal)) {
|
|
284
|
+
return literal.text;
|
|
285
|
+
}
|
|
286
|
+
if (literal.kind === ts.SyntaxKind.TrueKeyword) {
|
|
287
|
+
return 'true';
|
|
288
|
+
}
|
|
289
|
+
if (literal.kind === ts.SyntaxKind.FalseKeyword) {
|
|
290
|
+
return 'false';
|
|
291
|
+
}
|
|
292
|
+
if (literal.kind === ts.SyntaxKind.NullKeyword) {
|
|
293
|
+
return 'null';
|
|
294
|
+
}
|
|
295
|
+
if (ts.isPrefixUnaryExpression(literal) && ts.isNumericLiteral(literal.operand)) {
|
|
296
|
+
return literal.getText();
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
function variablesType(entry, variableTypes) {
|
|
301
|
+
const names = Array.isArray(entry.serializedVariableNames) ? [...entry.serializedVariableNames].sort() : [];
|
|
302
|
+
if (names.length === 0) {
|
|
303
|
+
return '{}';
|
|
304
|
+
}
|
|
305
|
+
const fields = names.map((name) => `${safePropertyName(name)}: ${variableTypes.get(name) || 'unknown'};`);
|
|
306
|
+
return `{ ${fields.join(' ')} }`;
|
|
307
|
+
}
|
|
308
|
+
function renderDts(owners, runtimeEntries) {
|
|
309
|
+
const lines = [
|
|
310
|
+
'// Auto-generated by Zenith CLI. Do not edit manually.',
|
|
311
|
+
'export {};',
|
|
312
|
+
'',
|
|
313
|
+
'declare global {',
|
|
314
|
+
' namespace Zenith {',
|
|
315
|
+
' interface ScopedServerDataOwnerMap {'
|
|
316
|
+
];
|
|
317
|
+
for (const owner of owners) {
|
|
318
|
+
lines.push(` ${JSON.stringify(owner.key)}: ${owner.type};`);
|
|
319
|
+
}
|
|
320
|
+
lines.push(' }');
|
|
321
|
+
lines.push('');
|
|
322
|
+
lines.push(' interface ScopedServerDataRuntimeMap {');
|
|
323
|
+
for (const [runtimeKey, ownerKey] of runtimeEntries) {
|
|
324
|
+
lines.push(` ${JSON.stringify(runtimeKey)}: ScopedServerDataOwnerMap[${JSON.stringify(ownerKey)}];`);
|
|
325
|
+
}
|
|
326
|
+
lines.push(' }');
|
|
327
|
+
lines.push('');
|
|
328
|
+
lines.push(' type ScopedServerDataFor<K extends keyof ScopedServerDataOwnerMap> =');
|
|
329
|
+
lines.push(' ScopedServerDataOwnerMap[K];');
|
|
330
|
+
lines.push('');
|
|
331
|
+
lines.push(' type ScopedServerRuntimeDataFor<K extends keyof ScopedServerDataRuntimeMap> =');
|
|
332
|
+
lines.push(' ScopedServerDataRuntimeMap[K];');
|
|
333
|
+
lines.push(' }');
|
|
334
|
+
lines.push('}');
|
|
335
|
+
lines.push('');
|
|
336
|
+
return `${lines.join('\n')}\n`;
|
|
337
|
+
}
|
|
338
|
+
function unwrapExpression(node, ts) {
|
|
339
|
+
let current = node;
|
|
340
|
+
while (ts.isParenthesizedExpression(current) ||
|
|
341
|
+
ts.isAsExpression(current) ||
|
|
342
|
+
ts.isTypeAssertionExpression(current) ||
|
|
343
|
+
ts.isSatisfiesExpression(current)) {
|
|
344
|
+
current = current.expression;
|
|
345
|
+
}
|
|
346
|
+
return current;
|
|
347
|
+
}
|
|
348
|
+
function propertyNameToString(name, ts) {
|
|
349
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
350
|
+
return name.text;
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
function safePropertyName(name) {
|
|
355
|
+
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : JSON.stringify(name);
|
|
356
|
+
}
|
|
357
|
+
function loadTypeScriptApi() {
|
|
358
|
+
if (TYPESCRIPT_API !== undefined) {
|
|
359
|
+
return TYPESCRIPT_API;
|
|
360
|
+
}
|
|
361
|
+
try {
|
|
362
|
+
TYPESCRIPT_API = PACKAGE_REQUIRE('typescript');
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
TYPESCRIPT_API = null;
|
|
366
|
+
}
|
|
367
|
+
return TYPESCRIPT_API;
|
|
368
|
+
}
|