@zenithbuild/cli 0.6.13 → 0.7.0

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.
Files changed (64) hide show
  1. package/dist/build/compiler-runtime.d.ts +59 -0
  2. package/dist/build/compiler-runtime.js +277 -0
  3. package/dist/build/expression-rewrites.d.ts +88 -0
  4. package/dist/build/expression-rewrites.js +372 -0
  5. package/dist/build/hoisted-code-transforms.d.ts +44 -0
  6. package/dist/build/hoisted-code-transforms.js +316 -0
  7. package/dist/build/merge-component-ir.d.ts +16 -0
  8. package/dist/build/merge-component-ir.js +257 -0
  9. package/dist/build/page-component-loop.d.ts +92 -0
  10. package/dist/build/page-component-loop.js +257 -0
  11. package/dist/build/page-ir-normalization.d.ts +23 -0
  12. package/dist/build/page-ir-normalization.js +370 -0
  13. package/dist/build/page-loop-metrics.d.ts +100 -0
  14. package/dist/build/page-loop-metrics.js +131 -0
  15. package/dist/build/page-loop-state.d.ts +261 -0
  16. package/dist/build/page-loop-state.js +92 -0
  17. package/dist/build/page-loop.d.ts +33 -0
  18. package/dist/build/page-loop.js +217 -0
  19. package/dist/build/scoped-identifier-rewrite.d.ts +112 -0
  20. package/dist/build/scoped-identifier-rewrite.js +245 -0
  21. package/dist/build/server-script.d.ts +41 -0
  22. package/dist/build/server-script.js +210 -0
  23. package/dist/build/type-declarations.d.ts +16 -0
  24. package/dist/build/type-declarations.js +158 -0
  25. package/dist/build/typescript-expression-utils.d.ts +23 -0
  26. package/dist/build/typescript-expression-utils.js +272 -0
  27. package/dist/build.d.ts +10 -18
  28. package/dist/build.js +74 -2261
  29. package/dist/component-instance-ir.d.ts +2 -2
  30. package/dist/component-instance-ir.js +146 -39
  31. package/dist/component-occurrences.js +63 -15
  32. package/dist/config.d.ts +66 -0
  33. package/dist/config.js +86 -0
  34. package/dist/debug-script.d.ts +1 -0
  35. package/dist/debug-script.js +8 -0
  36. package/dist/dev-build-session.d.ts +23 -0
  37. package/dist/dev-build-session.js +421 -0
  38. package/dist/dev-server.js +405 -58
  39. package/dist/framework-components/Image.zen +316 -0
  40. package/dist/images/materialize.d.ts +17 -0
  41. package/dist/images/materialize.js +200 -0
  42. package/dist/images/payload.d.ts +18 -0
  43. package/dist/images/payload.js +65 -0
  44. package/dist/images/runtime.d.ts +4 -0
  45. package/dist/images/runtime.js +254 -0
  46. package/dist/images/service.d.ts +4 -0
  47. package/dist/images/service.js +302 -0
  48. package/dist/images/shared.d.ts +58 -0
  49. package/dist/images/shared.js +306 -0
  50. package/dist/index.js +2 -17
  51. package/dist/manifest.js +45 -0
  52. package/dist/preview.d.ts +4 -1
  53. package/dist/preview.js +59 -6
  54. package/dist/resolve-components.js +20 -3
  55. package/dist/server-contract.js +3 -2
  56. package/dist/server-script-composition.d.ts +39 -0
  57. package/dist/server-script-composition.js +133 -0
  58. package/dist/startup-profile.d.ts +10 -0
  59. package/dist/startup-profile.js +62 -0
  60. package/dist/toolchain-paths.d.ts +1 -0
  61. package/dist/toolchain-paths.js +31 -0
  62. package/dist/version-check.d.ts +2 -1
  63. package/dist/version-check.js +12 -5
  64. package/package.json +5 -4
@@ -0,0 +1,210 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { findNextKnownComponentTag } from '../component-tag-parser.js';
3
+ /**
4
+ * @param {string} source
5
+ * @param {string} sourceFile
6
+ * @param {object} [compilerOpts]
7
+ * @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, source_path: string } | null }}
8
+ */
9
+ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
10
+ const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
11
+ const serverMatches = [];
12
+ const reservedServerExportRe = /\bexport\s+const\s+(?:data|prerender|guard|load|ssr_data|props|ssr)\b|\bexport\s+(?:async\s+)?function\s+(?:load|guard)\s*\(|\bexport\s+const\s+(?:load|guard)\s*=/;
13
+ for (const match of source.matchAll(scriptRe)) {
14
+ const attrs = String(match[1] || '');
15
+ const body = String(match[2] || '');
16
+ const isServer = /\bserver\b/i.test(attrs);
17
+ if (!isServer && reservedServerExportRe.test(body)) {
18
+ throw new Error(`Zenith server script contract violation:\n` +
19
+ ` File: ${sourceFile}\n` +
20
+ ` Reason: guard/load/data exports are only allowed in <script server lang="ts"> or adjacent .guard.ts / .load.ts files\n` +
21
+ ` Example: move the export into <script server lang="ts">`);
22
+ }
23
+ if (isServer) {
24
+ serverMatches.push(match);
25
+ }
26
+ }
27
+ if (serverMatches.length === 0) {
28
+ return { source, serverScript: null };
29
+ }
30
+ if (serverMatches.length > 1) {
31
+ throw new Error(`Zenith server script contract violation:\n` +
32
+ ` File: ${sourceFile}\n` +
33
+ ` Reason: multiple <script server> blocks are not supported\n` +
34
+ ` Example: keep exactly one <script server>...</script> block`);
35
+ }
36
+ const match = serverMatches[0];
37
+ const full = match[0] || '';
38
+ const attrs = String(match[1] || '');
39
+ const hasLangTs = /\blang\s*=\s*["']ts["']/i.test(attrs);
40
+ const hasLangJs = /\blang\s*=\s*["'](?:js|javascript)["']/i.test(attrs);
41
+ const hasAnyLang = /\blang\s*=/i.test(attrs);
42
+ const isTypescriptDefault = compilerOpts && compilerOpts.typescriptDefault === true;
43
+ if (!hasLangTs) {
44
+ if (!isTypescriptDefault || hasLangJs || hasAnyLang) {
45
+ throw new Error(`Zenith server script contract violation:\n` +
46
+ ` File: ${sourceFile}\n` +
47
+ ` Reason: Zenith requires TypeScript server scripts. Add lang="ts" (or enable typescriptDefault).\n` +
48
+ ` Example: <script server lang="ts">`);
49
+ }
50
+ }
51
+ const serverSource = String(match[2] || '').trim();
52
+ if (!serverSource) {
53
+ throw new Error(`Zenith server script contract violation:\n` +
54
+ ` File: ${sourceFile}\n` +
55
+ ` Reason: <script server> block is empty\n` +
56
+ ` Example: export const data = { ... }`);
57
+ }
58
+ const loadFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+load\s*\(([^)]*)\)/);
59
+ const loadConstParenMatch = serverSource.match(/\bexport\s+const\s+load\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
60
+ const loadConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+load\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
61
+ const hasLoad = Boolean(loadFnMatch || loadConstParenMatch || loadConstSingleArgMatch);
62
+ const loadMatchCount = Number(Boolean(loadFnMatch)) +
63
+ Number(Boolean(loadConstParenMatch)) +
64
+ Number(Boolean(loadConstSingleArgMatch));
65
+ if (loadMatchCount > 1) {
66
+ throw new Error(`Zenith server script contract violation:\n` +
67
+ ` File: ${sourceFile}\n` +
68
+ ` Reason: multiple load exports detected\n` +
69
+ ` Example: keep exactly one export const load = async (ctx) => ({ ... })`);
70
+ }
71
+ const guardFnMatch = serverSource.match(/\bexport\s+(?:async\s+)?function\s+guard\s*\(([^)]*)\)/);
72
+ const guardConstParenMatch = serverSource.match(/\bexport\s+const\s+guard\s*=\s*(?:async\s*)?\(([^)]*)\)\s*=>/);
73
+ const guardConstSingleArgMatch = serverSource.match(/\bexport\s+const\s+guard\s*=\s*(?:async\s*)?([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=>/);
74
+ const hasGuard = Boolean(guardFnMatch || guardConstParenMatch || guardConstSingleArgMatch);
75
+ const guardMatchCount = Number(Boolean(guardFnMatch)) +
76
+ Number(Boolean(guardConstParenMatch)) +
77
+ Number(Boolean(guardConstSingleArgMatch));
78
+ if (guardMatchCount > 1) {
79
+ throw new Error(`Zenith server script contract violation:\n` +
80
+ ` File: ${sourceFile}\n` +
81
+ ` Reason: multiple guard exports detected\n` +
82
+ ` Example: keep exactly one export const guard = async (ctx) => ({ ... })`);
83
+ }
84
+ const hasData = /\bexport\s+const\s+data\b/.test(serverSource);
85
+ const hasSsrData = /\bexport\s+const\s+ssr_data\b/.test(serverSource);
86
+ const hasSsr = /\bexport\s+const\s+ssr\b/.test(serverSource);
87
+ const hasProps = /\bexport\s+const\s+props\b/.test(serverSource);
88
+ if (hasData && hasLoad) {
89
+ throw new Error(`Zenith server script contract violation:\n` +
90
+ ` File: ${sourceFile}\n` +
91
+ ` Reason: export either data or load(ctx), not both\n` +
92
+ ` Example: remove data and return payload from load(ctx)`);
93
+ }
94
+ if ((hasData || hasLoad) && (hasSsrData || hasSsr || hasProps)) {
95
+ throw new Error(`Zenith server script contract violation:\n` +
96
+ ` File: ${sourceFile}\n` +
97
+ ` Reason: data/load cannot be combined with legacy ssr_data/ssr/props exports\n` +
98
+ ` Example: use only export const data or export const load`);
99
+ }
100
+ if (hasLoad) {
101
+ const singleArg = String(loadConstSingleArgMatch?.[1] || '').trim();
102
+ const paramsText = String((loadFnMatch || loadConstParenMatch)?.[1] || '').trim();
103
+ const arity = singleArg ? 1 : paramsText.length === 0 ? 0 : paramsText.split(',').length;
104
+ if (arity !== 1) {
105
+ throw new Error(`Zenith server script contract violation:\n` +
106
+ ` File: ${sourceFile}\n` +
107
+ ` Reason: load(ctx) must accept exactly one argument\n` +
108
+ ` Example: export const load = async (ctx) => ({ ... })`);
109
+ }
110
+ }
111
+ if (hasGuard) {
112
+ const singleArg = String(guardConstSingleArgMatch?.[1] || '').trim();
113
+ const paramsText = String((guardFnMatch || guardConstParenMatch)?.[1] || '').trim();
114
+ const arity = singleArg ? 1 : paramsText.length === 0 ? 0 : paramsText.split(',').length;
115
+ if (arity !== 1) {
116
+ throw new Error(`Zenith server script contract violation:\n` +
117
+ ` File: ${sourceFile}\n` +
118
+ ` Reason: guard(ctx) must accept exactly one argument\n` +
119
+ ` Example: export const guard = async (ctx) => ({ ... })`);
120
+ }
121
+ }
122
+ const prerenderMatch = serverSource.match(/\bexport\s+const\s+prerender\s*=\s*([^\n;]+)/);
123
+ let prerender = false;
124
+ if (prerenderMatch) {
125
+ const rawValue = String(prerenderMatch[1] || '').trim();
126
+ if (!/^(true|false)\b/.test(rawValue)) {
127
+ throw new Error(`Zenith server script contract violation:\n` +
128
+ ` File: ${sourceFile}\n` +
129
+ ` Reason: prerender must be a boolean literal\n` +
130
+ ` Example: export const prerender = true`);
131
+ }
132
+ prerender = rawValue.startsWith('true');
133
+ }
134
+ const start = match.index ?? -1;
135
+ if (start < 0) {
136
+ return {
137
+ source,
138
+ serverScript: {
139
+ source: serverSource,
140
+ prerender,
141
+ has_guard: hasGuard,
142
+ has_load: hasLoad,
143
+ source_path: sourceFile
144
+ }
145
+ };
146
+ }
147
+ const end = start + full.length;
148
+ const stripped = `${source.slice(0, start)}${source.slice(end)}`;
149
+ return {
150
+ source: stripped,
151
+ serverScript: {
152
+ source: serverSource,
153
+ prerender,
154
+ has_guard: hasGuard,
155
+ has_load: hasLoad,
156
+ source_path: sourceFile
157
+ }
158
+ };
159
+ }
160
+ /**
161
+ * @param {string} source
162
+ * @param {Map<string, string>} registry
163
+ * @param {string | null} [ownerPath]
164
+ * @returns {Map<string, Array<{ attrs: string, ownerPath: string | null }>>}
165
+ */
166
+ export function collectComponentUsageAttrs(source, registry, ownerPath = null) {
167
+ const out = new Map();
168
+ let cursor = 0;
169
+ while (cursor < source.length) {
170
+ const tag = findNextKnownComponentTag(source, registry, cursor);
171
+ if (!tag) {
172
+ break;
173
+ }
174
+ const name = tag.name;
175
+ const attrs = String(tag.attrs || '').trim();
176
+ if (!out.has(name)) {
177
+ out.set(name, []);
178
+ }
179
+ out.get(name).push({ attrs, ownerPath });
180
+ cursor = tag.end;
181
+ }
182
+ return out;
183
+ }
184
+ /**
185
+ * @param {string} source
186
+ * @param {Map<string, string>} registry
187
+ * @param {string | null} [ownerPath]
188
+ * @param {Set<string>} [visitedFiles]
189
+ * @param {Map<string, Array<{ attrs: string, ownerPath: string | null }>>} [out]
190
+ * @returns {Map<string, Array<{ attrs: string, ownerPath: string | null }>>}
191
+ */
192
+ export function collectRecursiveComponentUsageAttrs(source, registry, ownerPath = null, visitedFiles = new Set(), out = new Map()) {
193
+ const local = collectComponentUsageAttrs(source, registry, ownerPath);
194
+ for (const [name, attrsList] of local.entries()) {
195
+ if (!out.has(name)) {
196
+ out.set(name, []);
197
+ }
198
+ out.get(name).push(...attrsList);
199
+ }
200
+ for (const name of local.keys()) {
201
+ const compPath = registry.get(name);
202
+ if (!compPath || visitedFiles.has(compPath)) {
203
+ continue;
204
+ }
205
+ visitedFiles.add(compPath);
206
+ const componentSource = readFileSync(compPath, 'utf8');
207
+ collectRecursiveComponentUsageAttrs(componentSource, registry, compPath, visitedFiles, out);
208
+ }
209
+ return out;
210
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @param {string} pagesDir
3
+ * @returns {string}
4
+ */
5
+ export function deriveProjectRootFromPagesDir(pagesDir: string): string;
6
+ /**
7
+ * @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
8
+ * @returns {Promise<void>}
9
+ */
10
+ export function ensureZenithTypeDeclarations(input: {
11
+ manifest: Array<{
12
+ path: string;
13
+ file: string;
14
+ }>;
15
+ pagesDir: string;
16
+ }): Promise<void>;
@@ -0,0 +1,158 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { mkdir } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ /**
5
+ * @param {string} targetPath
6
+ * @param {string} next
7
+ */
8
+ function writeIfChanged(targetPath, next) {
9
+ const previous = existsSync(targetPath) ? readFileSync(targetPath, 'utf8') : null;
10
+ if (previous === next) {
11
+ return;
12
+ }
13
+ writeFileSync(targetPath, next, 'utf8');
14
+ }
15
+ /**
16
+ * @param {string} routePath
17
+ * @returns {string}
18
+ */
19
+ function routeParamsType(routePath) {
20
+ const segments = String(routePath || '').split('/').filter(Boolean);
21
+ const fields = [];
22
+ for (const segment of segments) {
23
+ if (segment.startsWith(':')) {
24
+ fields.push(`${segment.slice(1)}: string`);
25
+ continue;
26
+ }
27
+ if (segment.startsWith('*')) {
28
+ const raw = segment.slice(1);
29
+ const name = raw.endsWith('?') ? raw.slice(0, -1) : raw;
30
+ fields.push(`${name}: string`);
31
+ }
32
+ }
33
+ if (fields.length === 0) {
34
+ return '{}';
35
+ }
36
+ return `{ ${fields.join(', ')} }`;
37
+ }
38
+ /**
39
+ * @param {Array<{ path: string, file: string }>} manifest
40
+ * @returns {string}
41
+ */
42
+ function renderZenithRouteDts(manifest) {
43
+ const lines = [
44
+ '// Auto-generated by Zenith CLI. Do not edit manually.',
45
+ 'export {};',
46
+ '',
47
+ 'declare global {',
48
+ ' namespace Zenith {',
49
+ ' interface RouteParamsMap {'
50
+ ];
51
+ const sortedManifest = [...manifest].sort((a, b) => a.path.localeCompare(b.path));
52
+ for (const entry of sortedManifest) {
53
+ lines.push(` ${JSON.stringify(entry.path)}: ${routeParamsType(entry.path)};`);
54
+ }
55
+ lines.push(' }');
56
+ lines.push('');
57
+ lines.push(' type ParamsFor<P extends keyof RouteParamsMap> = RouteParamsMap[P];');
58
+ lines.push(' }');
59
+ lines.push('}');
60
+ lines.push('');
61
+ return `${lines.join('\n')}\n`;
62
+ }
63
+ /**
64
+ * @returns {string}
65
+ */
66
+ function renderZenithEnvDts() {
67
+ return [
68
+ '// Auto-generated by Zenith CLI. Do not edit manually.',
69
+ 'export {};',
70
+ '',
71
+ 'declare global {',
72
+ ' namespace Zenith {',
73
+ ' type Params = Record<string, string>;',
74
+ '',
75
+ ' interface ErrorState {',
76
+ ' status?: number;',
77
+ ' code?: string;',
78
+ ' message: string;',
79
+ ' }',
80
+ '',
81
+ ' type PageData = Record<string, unknown> & { __zenith_error?: ErrorState };',
82
+ '',
83
+ ' interface RouteMeta {',
84
+ ' id: string;',
85
+ ' file: string;',
86
+ ' pattern: string;',
87
+ ' }',
88
+ '',
89
+ ' interface LoadContext {',
90
+ ' params: Params;',
91
+ ' url: URL;',
92
+ ' request: Request;',
93
+ ' route: RouteMeta;',
94
+ ' }',
95
+ '',
96
+ ' type Load<T extends PageData = PageData> = (ctx: LoadContext) => Promise<T> | T;',
97
+ '',
98
+ ' interface Fragment {',
99
+ ' __zenith_fragment: true;',
100
+ ' mount: (anchor: Node | null) => void;',
101
+ ' unmount: () => void;',
102
+ ' }',
103
+ '',
104
+ ' type Renderable =',
105
+ ' | string',
106
+ ' | number',
107
+ ' | boolean',
108
+ ' | null',
109
+ ' | undefined',
110
+ ' | Renderable[]',
111
+ ' | Fragment;',
112
+ ' }',
113
+ '}',
114
+ ''
115
+ ].join('\n');
116
+ }
117
+ /**
118
+ * @param {string} pagesDir
119
+ * @returns {string}
120
+ */
121
+ export function deriveProjectRootFromPagesDir(pagesDir) {
122
+ const normalized = resolve(pagesDir);
123
+ const parent = dirname(normalized);
124
+ if (basename(parent) === 'src') {
125
+ return dirname(parent);
126
+ }
127
+ return parent;
128
+ }
129
+ /**
130
+ * @param {{ manifest: Array<{ path: string, file: string }>, pagesDir: string }} input
131
+ * @returns {Promise<void>}
132
+ */
133
+ export async function ensureZenithTypeDeclarations(input) {
134
+ const projectRoot = deriveProjectRootFromPagesDir(input.pagesDir);
135
+ const zenithDir = resolve(projectRoot, '.zenith');
136
+ await mkdir(zenithDir, { recursive: true });
137
+ const envPath = join(zenithDir, 'zenith-env.d.ts');
138
+ const routesPath = join(zenithDir, 'zenith-routes.d.ts');
139
+ writeIfChanged(envPath, renderZenithEnvDts());
140
+ writeIfChanged(routesPath, renderZenithRouteDts(input.manifest));
141
+ const tsconfigPath = resolve(projectRoot, 'tsconfig.json');
142
+ if (!existsSync(tsconfigPath)) {
143
+ return;
144
+ }
145
+ try {
146
+ const raw = readFileSync(tsconfigPath, 'utf8');
147
+ const parsed = JSON.parse(raw);
148
+ const include = Array.isArray(parsed.include) ? [...parsed.include] : [];
149
+ if (!include.includes('.zenith/**/*.d.ts')) {
150
+ include.push('.zenith/**/*.d.ts');
151
+ parsed.include = include;
152
+ writeIfChanged(tsconfigPath, `${JSON.stringify(parsed, null, 2)}\n`);
153
+ }
154
+ }
155
+ catch {
156
+ // Non-JSON tsconfig variants are left untouched.
157
+ }
158
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @param {string} key
3
+ * @returns {string}
4
+ */
5
+ export function renderObjectKey(key: string): string;
6
+ /**
7
+ * @param {string} source
8
+ * @returns {string[]}
9
+ */
10
+ export function extractDeclaredIdentifiers(source: string): string[];
11
+ /**
12
+ * @param {string} expr
13
+ * @returns {string}
14
+ */
15
+ export function normalizeTypeScriptExpression(expr: string): string;
16
+ export function expandScopedShorthandPropertiesInSource(source: any): string;
17
+ /**
18
+ * @param {string} expr
19
+ * @param {Map<string, string>} scopeMap
20
+ * @param {Set<string> | null | undefined} scopeAmbiguous
21
+ * @returns {string}
22
+ */
23
+ export function rewriteIdentifiersWithinExpression(expr: string, scopeMap: Map<string, string>, scopeAmbiguous: Set<string> | null | undefined): string;
@@ -0,0 +1,272 @@
1
+ import { loadTypeScriptApi } from './compiler-runtime.js';
2
+ /**
3
+ * @param {string} key
4
+ * @returns {string}
5
+ */
6
+ export function renderObjectKey(key) {
7
+ if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)) {
8
+ return key;
9
+ }
10
+ return JSON.stringify(key);
11
+ }
12
+ function deriveScopedIdentifierAlias(value) {
13
+ const ident = String(value || '').trim();
14
+ if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(ident)) {
15
+ return null;
16
+ }
17
+ const parts = ident.split('_').filter(Boolean);
18
+ const candidate = parts.length > 1 ? parts[parts.length - 1] : ident;
19
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(candidate) ? candidate : ident;
20
+ }
21
+ /**
22
+ * @param {string} source
23
+ * @returns {string[]}
24
+ */
25
+ export function extractDeclaredIdentifiers(source) {
26
+ const text = String(source || '').trim();
27
+ if (!text) {
28
+ return [];
29
+ }
30
+ const ts = loadTypeScriptApi();
31
+ if (ts) {
32
+ const sourceFile = ts.createSourceFile('zenith-hoisted-declaration.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
33
+ const identifiers = [];
34
+ const collectBindingNames = (name) => {
35
+ if (ts.isIdentifier(name)) {
36
+ identifiers.push(name.text);
37
+ return;
38
+ }
39
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
40
+ for (const element of name.elements) {
41
+ if (ts.isBindingElement(element)) {
42
+ collectBindingNames(element.name);
43
+ }
44
+ }
45
+ }
46
+ };
47
+ for (const statement of sourceFile.statements) {
48
+ if (!ts.isVariableStatement(statement)) {
49
+ continue;
50
+ }
51
+ for (const declaration of statement.declarationList.declarations) {
52
+ collectBindingNames(declaration.name);
53
+ }
54
+ }
55
+ if (identifiers.length > 0) {
56
+ return identifiers;
57
+ }
58
+ }
59
+ const fallback = [];
60
+ const match = text.match(/^\s*(?:const|let|var)\s+([\s\S]+?);?\s*$/);
61
+ if (!match) {
62
+ return fallback;
63
+ }
64
+ const declarationList = match[1];
65
+ const identifierRe = /(?:^|,)\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?::[^=,]+)?=/g;
66
+ let found;
67
+ while ((found = identifierRe.exec(declarationList)) !== null) {
68
+ fallback.push(found[1]);
69
+ }
70
+ return fallback;
71
+ }
72
+ /**
73
+ * @param {string} expr
74
+ * @returns {string}
75
+ */
76
+ export function normalizeTypeScriptExpression(expr) {
77
+ const source = String(expr || '').trim();
78
+ if (!source) {
79
+ return source;
80
+ }
81
+ const ts = loadTypeScriptApi();
82
+ if (!ts) {
83
+ return source;
84
+ }
85
+ const wrapped = `const __zenith_expr__ = (${source});`;
86
+ let transpiled;
87
+ try {
88
+ transpiled = ts.transpileModule(wrapped, {
89
+ fileName: 'zenith-expression.ts',
90
+ compilerOptions: {
91
+ module: ts.ModuleKind.ESNext,
92
+ target: ts.ScriptTarget.ESNext,
93
+ newLine: ts.NewLineKind.LineFeed,
94
+ },
95
+ reportDiagnostics: false,
96
+ }).outputText;
97
+ }
98
+ catch {
99
+ return source;
100
+ }
101
+ let sourceFile;
102
+ try {
103
+ sourceFile = ts.createSourceFile('zenith-expression.js', transpiled, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
104
+ }
105
+ catch {
106
+ return source;
107
+ }
108
+ for (const statement of sourceFile.statements) {
109
+ if (!ts.isVariableStatement(statement)) {
110
+ continue;
111
+ }
112
+ const declaration = statement.declarationList.declarations.find((entry) => ts.isIdentifier(entry.name) && entry.name.text === '__zenith_expr__');
113
+ const initializer = declaration?.initializer;
114
+ if (!initializer) {
115
+ continue;
116
+ }
117
+ const root = ts.isParenthesizedExpression(initializer) ? initializer.expression : initializer;
118
+ return ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
119
+ .printNode(ts.EmitHint.Unspecified, root, sourceFile)
120
+ .trim();
121
+ }
122
+ return source;
123
+ }
124
+ export function expandScopedShorthandPropertiesInSource(source) {
125
+ const text = String(source || '');
126
+ if (!text.trim()) {
127
+ return text;
128
+ }
129
+ const ts = loadTypeScriptApi();
130
+ if (!ts) {
131
+ return text;
132
+ }
133
+ let sourceFile;
134
+ try {
135
+ sourceFile = ts.createSourceFile('zenith-shorthand-fix.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
136
+ }
137
+ catch {
138
+ return text;
139
+ }
140
+ const transformer = (context) => {
141
+ const visit = (node) => {
142
+ if (ts.isShorthandPropertyAssignment(node)) {
143
+ const alias = deriveScopedIdentifierAlias(node.name.text);
144
+ if (typeof alias === 'string' && alias.length > 0 && alias !== node.name.text) {
145
+ return ts.factory.createPropertyAssignment(ts.factory.createIdentifier(alias), ts.factory.createIdentifier(node.name.text));
146
+ }
147
+ }
148
+ return ts.visitEachChild(node, visit, context);
149
+ };
150
+ return (node) => ts.visitNode(node, visit);
151
+ };
152
+ const result = ts.transform(sourceFile, [transformer]);
153
+ try {
154
+ return ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
155
+ .printFile(result.transformed[0])
156
+ .trimEnd();
157
+ }
158
+ catch {
159
+ return text;
160
+ }
161
+ finally {
162
+ result.dispose();
163
+ }
164
+ }
165
+ /**
166
+ * @param {string} expr
167
+ * @param {Map<string, string>} scopeMap
168
+ * @param {Set<string> | null | undefined} scopeAmbiguous
169
+ * @returns {string}
170
+ */
171
+ export function rewriteIdentifiersWithinExpression(expr, scopeMap, scopeAmbiguous) {
172
+ const ts = loadTypeScriptApi();
173
+ if (!(scopeMap instanceof Map) || !ts) {
174
+ return normalizeTypeScriptExpression(expr);
175
+ }
176
+ const wrapped = `const __zenith_expr__ = (${expr});`;
177
+ let sourceFile;
178
+ try {
179
+ sourceFile = ts.createSourceFile('zenith-expression.ts', wrapped, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
180
+ }
181
+ catch {
182
+ return expr;
183
+ }
184
+ const statement = sourceFile.statements[0];
185
+ if (!statement || !ts.isVariableStatement(statement)) {
186
+ return expr;
187
+ }
188
+ const initializer = statement.declarationList.declarations[0]?.initializer;
189
+ const root = initializer && ts.isParenthesizedExpression(initializer) ? initializer.expression : initializer;
190
+ if (!root) {
191
+ return expr;
192
+ }
193
+ const replacements = [];
194
+ const collectBoundNames = (name, target) => {
195
+ if (ts.isIdentifier(name)) {
196
+ target.add(name.text);
197
+ return;
198
+ }
199
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
200
+ for (const element of name.elements) {
201
+ if (ts.isBindingElement(element)) {
202
+ collectBoundNames(element.name, target);
203
+ }
204
+ }
205
+ }
206
+ };
207
+ const shouldSkipIdentifier = (node, localBindings) => {
208
+ if (localBindings.has(node.text)) {
209
+ return true;
210
+ }
211
+ const parent = node.parent;
212
+ if (!parent) {
213
+ return false;
214
+ }
215
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
216
+ return true;
217
+ }
218
+ if (ts.isPropertyAssignment(parent) && parent.name === node) {
219
+ return true;
220
+ }
221
+ if (ts.isShorthandPropertyAssignment(parent)) {
222
+ return true;
223
+ }
224
+ if (ts.isBindingElement(parent) && parent.name === node) {
225
+ return true;
226
+ }
227
+ if (ts.isParameter(parent) && parent.name === node) {
228
+ return true;
229
+ }
230
+ return false;
231
+ };
232
+ const visit = (node, localBindings) => {
233
+ let nextBindings = localBindings;
234
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
235
+ nextBindings = new Set(localBindings);
236
+ if (node.name && ts.isIdentifier(node.name)) {
237
+ nextBindings.add(node.name.text);
238
+ }
239
+ for (const param of node.parameters) {
240
+ collectBoundNames(param.name, nextBindings);
241
+ }
242
+ }
243
+ if (ts.isIdentifier(node) && !shouldSkipIdentifier(node, nextBindings)) {
244
+ const rewritten = scopeMap.get(node.text);
245
+ if (typeof rewritten === 'string' &&
246
+ rewritten.length > 0 &&
247
+ rewritten !== node.text &&
248
+ !(scopeAmbiguous instanceof Set && scopeAmbiguous.has(node.text))) {
249
+ replacements.push({
250
+ start: node.getStart(sourceFile),
251
+ end: node.getEnd(),
252
+ text: rewritten
253
+ });
254
+ }
255
+ }
256
+ ts.forEachChild(node, (child) => visit(child, nextBindings));
257
+ };
258
+ visit(root, new Set());
259
+ if (replacements.length === 0) {
260
+ return normalizeTypeScriptExpression(expr);
261
+ }
262
+ let rewritten = wrapped;
263
+ for (const replacement of replacements.sort((a, b) => b.start - a.start)) {
264
+ rewritten = `${rewritten.slice(0, replacement.start)}${replacement.text}${rewritten.slice(replacement.end)}`;
265
+ }
266
+ const prefix = 'const __zenith_expr__ = (';
267
+ const suffix = ');';
268
+ if (!rewritten.startsWith(prefix) || !rewritten.endsWith(suffix)) {
269
+ return normalizeTypeScriptExpression(expr);
270
+ }
271
+ return normalizeTypeScriptExpression(rewritten.slice(prefix.length, rewritten.length - suffix.length));
272
+ }