@zenithbuild/cli 0.7.3 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +18 -13
  2. package/dist/adapters/adapter-netlify.d.ts +1 -1
  3. package/dist/adapters/adapter-netlify.js +56 -13
  4. package/dist/adapters/adapter-node.js +8 -0
  5. package/dist/adapters/adapter-static-export.d.ts +5 -0
  6. package/dist/adapters/adapter-static-export.js +115 -0
  7. package/dist/adapters/adapter-types.d.ts +3 -1
  8. package/dist/adapters/adapter-types.js +5 -2
  9. package/dist/adapters/adapter-vercel.d.ts +1 -1
  10. package/dist/adapters/adapter-vercel.js +70 -13
  11. package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
  12. package/dist/adapters/copy-hosted-page-runtime.js +49 -0
  13. package/dist/adapters/resolve-adapter.js +4 -0
  14. package/dist/adapters/route-rules.d.ts +5 -0
  15. package/dist/adapters/route-rules.js +9 -0
  16. package/dist/adapters/validate-hosted-resource-routes.d.ts +1 -0
  17. package/dist/adapters/validate-hosted-resource-routes.js +13 -0
  18. package/dist/auth/route-auth.d.ts +6 -0
  19. package/dist/auth/route-auth.js +236 -0
  20. package/dist/build/compiler-runtime.d.ts +10 -9
  21. package/dist/build/compiler-runtime.js +58 -2
  22. package/dist/build/compiler-signal-expression.d.ts +1 -0
  23. package/dist/build/compiler-signal-expression.js +155 -0
  24. package/dist/build/expression-rewrites.d.ts +1 -6
  25. package/dist/build/expression-rewrites.js +61 -65
  26. package/dist/build/page-component-loop.d.ts +3 -13
  27. package/dist/build/page-component-loop.js +21 -46
  28. package/dist/build/page-ir-normalization.d.ts +0 -8
  29. package/dist/build/page-ir-normalization.js +13 -234
  30. package/dist/build/page-loop-state.d.ts +6 -9
  31. package/dist/build/page-loop-state.js +9 -8
  32. package/dist/build/page-loop.js +27 -22
  33. package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
  34. package/dist/build/scoped-identifier-rewrite.js +28 -128
  35. package/dist/build/server-script.d.ts +3 -1
  36. package/dist/build/server-script.js +35 -5
  37. package/dist/build-output-manifest.d.ts +3 -2
  38. package/dist/build-output-manifest.js +3 -0
  39. package/dist/build.js +32 -18
  40. package/dist/component-instance-ir.js +158 -52
  41. package/dist/dev-build-session.js +20 -6
  42. package/dist/dev-server.js +152 -55
  43. package/dist/download-result.d.ts +14 -0
  44. package/dist/download-result.js +148 -0
  45. package/dist/framework-components/Image.zen +1 -1
  46. package/dist/images/materialization-plan.d.ts +1 -0
  47. package/dist/images/materialization-plan.js +6 -0
  48. package/dist/images/materialize.d.ts +5 -3
  49. package/dist/images/materialize.js +24 -109
  50. package/dist/images/router-manifest.d.ts +1 -0
  51. package/dist/images/router-manifest.js +49 -0
  52. package/dist/images/service.d.ts +13 -1
  53. package/dist/images/service.js +45 -15
  54. package/dist/index.js +8 -2
  55. package/dist/manifest.d.ts +15 -1
  56. package/dist/manifest.js +27 -7
  57. package/dist/preview.d.ts +13 -4
  58. package/dist/preview.js +261 -101
  59. package/dist/request-body.d.ts +1 -0
  60. package/dist/request-body.js +7 -0
  61. package/dist/request-origin.d.ts +2 -0
  62. package/dist/request-origin.js +45 -0
  63. package/dist/resource-manifest.d.ts +16 -0
  64. package/dist/resource-manifest.js +53 -0
  65. package/dist/resource-response.d.ts +34 -0
  66. package/dist/resource-response.js +71 -0
  67. package/dist/resource-route-module.d.ts +15 -0
  68. package/dist/resource-route-module.js +129 -0
  69. package/dist/route-check-support.d.ts +1 -0
  70. package/dist/route-check-support.js +4 -0
  71. package/dist/server-contract.d.ts +29 -6
  72. package/dist/server-contract.js +304 -42
  73. package/dist/server-error.d.ts +4 -0
  74. package/dist/server-error.js +36 -0
  75. package/dist/server-output.d.ts +4 -1
  76. package/dist/server-output.js +71 -10
  77. package/dist/server-runtime/node-server.js +67 -31
  78. package/dist/server-runtime/route-render.d.ts +27 -3
  79. package/dist/server-runtime/route-render.js +94 -53
  80. package/dist/server-script-composition.d.ts +13 -5
  81. package/dist/server-script-composition.js +29 -11
  82. package/dist/static-export-paths.d.ts +3 -0
  83. package/dist/static-export-paths.js +160 -0
  84. package/package.json +6 -3
@@ -0,0 +1,13 @@
1
+ const HOSTED_RESOURCE_DOWNLOAD_RE = /\b(?:ctx\.)?download\s*\(/;
2
+ export function validateHostedResourceRoutes(manifest, targetName) {
3
+ for (const route of Array.isArray(manifest) ? manifest : []) {
4
+ if (route?.route_kind !== 'resource') {
5
+ continue;
6
+ }
7
+ const source = typeof route.server_script === 'string' ? route.server_script : '';
8
+ if (HOSTED_RESOURCE_DOWNLOAD_RE.test(source)) {
9
+ throw new Error(`[Zenith:Build] target "${targetName}" does not support resource downloads in this milestone. ` +
10
+ `Route "${route.path}" (${route.file}) must run on dev, preview, or target "node".`);
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,6 @@
1
+ export function consumeStagedSetCookies(ctx: any): any[];
2
+ export function attachRouteAuth(ctx: any, options?: {}): any;
3
+ export const SESSION_COOKIE_NAME: "zenith_session";
4
+ export const SESSION_SECRET_ENV: "ZENITH_SESSION_SECRET";
5
+ export const STAGED_SET_COOKIES_KEY: "__zenith_staged_set_cookies";
6
+ export const AUTH_CONTROL_FLOW_FLAG: "__zenith_auth_control_flow";
@@ -0,0 +1,236 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+ import { assertJsonSerializable } from '../server-contract.js';
3
+ export const SESSION_COOKIE_NAME = 'zenith_session';
4
+ export const SESSION_SECRET_ENV = 'ZENITH_SESSION_SECRET';
5
+ export const STAGED_SET_COOKIES_KEY = '__zenith_staged_set_cookies';
6
+ export const AUTH_CONTROL_FLOW_FLAG = '__zenith_auth_control_flow';
7
+ const SESSION_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 7;
8
+ const SESSION_COOKIE_MAX_BYTES = 3800;
9
+ const SESSION_SCHEMA_VERSION = 1;
10
+ function createAuthError(message) {
11
+ return new Error(`[Zenith] ${message}`);
12
+ }
13
+ function base64urlEncode(input) {
14
+ return Buffer.from(input)
15
+ .toString('base64')
16
+ .replace(/\+/g, '-')
17
+ .replace(/\//g, '_')
18
+ .replace(/=+$/g, '');
19
+ }
20
+ function base64urlDecode(input) {
21
+ const normalized = String(input || '')
22
+ .replace(/-/g, '+')
23
+ .replace(/_/g, '/');
24
+ const padded = normalized + '='.repeat((4 - (normalized.length % 4 || 4)) % 4);
25
+ return Buffer.from(padded, 'base64').toString('utf8');
26
+ }
27
+ function isPlainObject(value) {
28
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
29
+ return false;
30
+ }
31
+ const proto = Object.getPrototypeOf(value);
32
+ return proto === null || proto === Object.prototype || proto?.constructor?.name === 'Object';
33
+ }
34
+ function readSessionSecret() {
35
+ const secret = typeof process?.env?.[SESSION_SECRET_ENV] === 'string'
36
+ ? process.env[SESSION_SECRET_ENV].trim()
37
+ : '';
38
+ if (!secret) {
39
+ throw createAuthError(`ctx.auth requires ${SESSION_SECRET_ENV} to be set`);
40
+ }
41
+ return secret;
42
+ }
43
+ function signPayload(payload, secret) {
44
+ return createHmac('sha256', secret).update(payload).digest('base64url');
45
+ }
46
+ function createSignedSessionValue(session, secret) {
47
+ if (!isPlainObject(session)) {
48
+ throw createAuthError('ctx.auth.signIn(sessionObject) requires a JSON-safe plain object');
49
+ }
50
+ assertJsonSerializable(session, 'ctx.auth.signIn(sessionObject)');
51
+ const envelope = {
52
+ v: SESSION_SCHEMA_VERSION,
53
+ exp: Date.now() + SESSION_COOKIE_MAX_AGE_SECONDS * 1000,
54
+ session
55
+ };
56
+ const json = JSON.stringify(envelope);
57
+ const encodedPayload = base64urlEncode(json);
58
+ const signature = signPayload(encodedPayload, secret);
59
+ const token = `${encodedPayload}.${signature}`;
60
+ if (Buffer.byteLength(token, 'utf8') > SESSION_COOKIE_MAX_BYTES) {
61
+ throw createAuthError('ctx.auth.signIn(sessionObject) produced an oversized session cookie');
62
+ }
63
+ return token;
64
+ }
65
+ function parseSessionValue(rawValue, secret) {
66
+ if (typeof rawValue !== 'string' || rawValue.length === 0) {
67
+ return null;
68
+ }
69
+ const dot = rawValue.lastIndexOf('.');
70
+ if (dot <= 0 || dot === rawValue.length - 1) {
71
+ return null;
72
+ }
73
+ const encodedPayload = rawValue.slice(0, dot);
74
+ const receivedSignature = rawValue.slice(dot + 1);
75
+ const expectedSignature = signPayload(encodedPayload, secret);
76
+ const received = Buffer.from(receivedSignature);
77
+ const expected = Buffer.from(expectedSignature);
78
+ if (received.length !== expected.length || !timingSafeEqual(received, expected)) {
79
+ return null;
80
+ }
81
+ try {
82
+ const envelope = JSON.parse(base64urlDecode(encodedPayload));
83
+ if (!envelope || typeof envelope !== 'object' || envelope.v !== SESSION_SCHEMA_VERSION) {
84
+ return null;
85
+ }
86
+ if (!Number.isFinite(envelope.exp) || envelope.exp <= Date.now()) {
87
+ return null;
88
+ }
89
+ if (!isPlainObject(envelope.session)) {
90
+ return null;
91
+ }
92
+ assertJsonSerializable(envelope.session, 'ctx.auth session payload');
93
+ return envelope.session;
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ function shouldUseSecureCookies(requestUrl) {
100
+ try {
101
+ const url = requestUrl instanceof URL ? requestUrl : new URL(String(requestUrl || 'http://localhost/'));
102
+ return url.protocol === 'https:';
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ }
108
+ function buildCookieAttributes(requestUrl) {
109
+ const attributes = ['Path=/', 'HttpOnly', 'SameSite=Lax'];
110
+ if (shouldUseSecureCookies(requestUrl)) {
111
+ attributes.push('Secure');
112
+ }
113
+ return attributes;
114
+ }
115
+ function buildSessionSetCookie(value, requestUrl) {
116
+ const attributes = buildCookieAttributes(requestUrl);
117
+ attributes.push(`Max-Age=${SESSION_COOKIE_MAX_AGE_SECONDS}`);
118
+ attributes.push(`Expires=${new Date(Date.now() + SESSION_COOKIE_MAX_AGE_SECONDS * 1000).toUTCString()}`);
119
+ return `${SESSION_COOKIE_NAME}=${encodeURIComponent(value)}; ${attributes.join('; ')}`;
120
+ }
121
+ function buildSessionClearCookie(requestUrl) {
122
+ const attributes = buildCookieAttributes(requestUrl);
123
+ attributes.push('Max-Age=0');
124
+ attributes.push('Expires=Thu, 01 Jan 1970 00:00:00 GMT');
125
+ return `${SESSION_COOKIE_NAME}=; ${attributes.join('; ')}`;
126
+ }
127
+ function stageSetCookie(ctx, value) {
128
+ if (!Array.isArray(ctx[STAGED_SET_COOKIES_KEY])) {
129
+ Object.defineProperty(ctx, STAGED_SET_COOKIES_KEY, {
130
+ value: [],
131
+ enumerable: false,
132
+ configurable: true
133
+ });
134
+ }
135
+ ctx[STAGED_SET_COOKIES_KEY].push(value);
136
+ }
137
+ function toAuthControlFlow(result) {
138
+ const error = new Error('auth control flow');
139
+ error[AUTH_CONTROL_FLOW_FLAG] = true;
140
+ error.result = result;
141
+ return error;
142
+ }
143
+ function normalizeRequirePolicy(options, redirect, deny) {
144
+ if (!options || typeof options !== 'object' || Array.isArray(options)) {
145
+ throw createAuthError('ctx.auth.requireSession(...) requires an explicit redirect or deny policy object');
146
+ }
147
+ const hasRedirect = typeof options.redirectTo === 'string' && options.redirectTo.trim().length > 0;
148
+ const hasDeny = Number.isInteger(options.deny);
149
+ if (hasRedirect === hasDeny) {
150
+ throw createAuthError('ctx.auth.requireSession(...) requires exactly one of redirectTo or deny');
151
+ }
152
+ if (hasRedirect) {
153
+ const status = options.status === undefined ? 302 : options.status;
154
+ if (status !== 302 && status !== 303 && status !== 307) {
155
+ throw createAuthError('ctx.auth.requireSession({ redirectTo, status }) only supports 302, 303, or 307');
156
+ }
157
+ return redirect(options.redirectTo, status);
158
+ }
159
+ if (options.deny !== 401 && options.deny !== 403 && options.deny !== 404) {
160
+ throw createAuthError('ctx.auth.requireSession({ deny, message }) only supports 401, 403, or 404');
161
+ }
162
+ if (options.message !== undefined && typeof options.message !== 'string') {
163
+ throw createAuthError('ctx.auth.requireSession({ deny, message }) requires message to be a string when provided');
164
+ }
165
+ return deny(options.deny, options.message);
166
+ }
167
+ export function consumeStagedSetCookies(ctx) {
168
+ if (!ctx || typeof ctx !== 'object' || !Array.isArray(ctx[STAGED_SET_COOKIES_KEY])) {
169
+ return [];
170
+ }
171
+ return ctx[STAGED_SET_COOKIES_KEY].slice();
172
+ }
173
+ export function attachRouteAuth(ctx, options = {}) {
174
+ if (!ctx || typeof ctx !== 'object') {
175
+ throw createAuthError('attachRouteAuth(ctx) requires a route context object');
176
+ }
177
+ const requestUrl = options.requestUrl instanceof URL
178
+ ? options.requestUrl
179
+ : new URL(String(options.requestUrl || ctx.url || 'http://localhost/'));
180
+ const guardOnly = options.guardOnly === true;
181
+ const redirect = options.redirect;
182
+ const deny = options.deny;
183
+ if (typeof redirect !== 'function' || typeof deny !== 'function') {
184
+ throw createAuthError('attachRouteAuth(ctx) requires redirect() and deny() constructors');
185
+ }
186
+ let activeSession;
187
+ let activeSessionInitialized = false;
188
+ function readActiveSession() {
189
+ if (activeSessionInitialized) {
190
+ return activeSession;
191
+ }
192
+ const secret = readSessionSecret();
193
+ const rawCookieValue = typeof ctx.cookies?.[SESSION_COOKIE_NAME] === 'string'
194
+ ? ctx.cookies[SESSION_COOKIE_NAME]
195
+ : '';
196
+ activeSession = parseSessionValue(rawCookieValue, secret);
197
+ activeSessionInitialized = true;
198
+ return activeSession;
199
+ }
200
+ Object.defineProperty(ctx, STAGED_SET_COOKIES_KEY, {
201
+ value: [],
202
+ enumerable: false,
203
+ configurable: true
204
+ });
205
+ ctx.auth = {
206
+ async getSession() {
207
+ return readActiveSession();
208
+ },
209
+ async requireSession(policy) {
210
+ const session = await this.getSession();
211
+ if (session) {
212
+ return session;
213
+ }
214
+ throw toAuthControlFlow(normalizeRequirePolicy(policy, redirect, deny));
215
+ },
216
+ async signIn(sessionObject) {
217
+ if (guardOnly) {
218
+ throw createAuthError('ctx.auth.signIn(...) is unavailable during advisory route-check execution');
219
+ }
220
+ const secret = readSessionSecret();
221
+ const cookieValue = createSignedSessionValue(sessionObject, secret);
222
+ stageSetCookie(ctx, buildSessionSetCookie(cookieValue, requestUrl));
223
+ activeSession = sessionObject;
224
+ activeSessionInitialized = true;
225
+ },
226
+ async signOut() {
227
+ if (guardOnly) {
228
+ throw createAuthError('ctx.auth.signOut() is unavailable during advisory route-check execution');
229
+ }
230
+ stageSetCookie(ctx, buildSessionClearCookie(requestUrl));
231
+ activeSession = null;
232
+ activeSessionInitialized = true;
233
+ }
234
+ };
235
+ return ctx.auth;
236
+ }
@@ -42,17 +42,18 @@ export function createTimedCompilerRunner(startupProfile: ReturnType<typeof impo
42
42
  * @param {object | null} [logger]
43
43
  * @param {boolean} [showInfo]
44
44
  * @param {string|object} [bundlerBin]
45
- * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string }} [bundlerOptions]
45
+ * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string, routeCheck?: boolean, imageRuntimePayload?: object }} [bundlerOptions]
46
46
  * @returns {Promise<void>}
47
47
  */
48
- export function runBundler(envelope: object | object[], outDir: string, projectRoot: string, logger?: object | null, showInfo?: boolean, bundlerBin?: string | object, bundlerOptions?: {
49
- devStableAssets?: boolean;
50
- rebuildStrategy?: "full" | "bundle-only" | "page-only";
51
- changedRoutes?: string[];
52
- fastPath?: boolean;
53
- globalGraphHash?: string;
54
- basePath?: string;
55
- }): Promise<void>;
48
+ /**
49
+ * Merge compiler-owned static image materialization rows from marker bindings + rewritten props literals.
50
+ * @param {object} pageIr
51
+ * @param {string[]} literals
52
+ * @param {object} [compilerRunOptions]
53
+ * @returns {object}
54
+ */
55
+ export function mergePageImageMaterialization(pageIr: object, literals: string[], compilerRunOptions?: object): object;
56
+ export function runBundler(envelope: any, outDir: any, projectRoot: any, logger?: null, showInfo?: boolean, bundlerBin?: string, bundlerOptions?: {}): Promise<any>;
56
57
  /**
57
58
  * @param {string} rootDir
58
59
  * @returns {Promise<string[]>}
@@ -171,9 +171,56 @@ export function createTimedCompilerRunner(startupProfile, compilerTotals) {
171
171
  * @param {object | null} [logger]
172
172
  * @param {boolean} [showInfo]
173
173
  * @param {string|object} [bundlerBin]
174
- * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string }} [bundlerOptions]
174
+ * @param {{ devStableAssets?: boolean, rebuildStrategy?: 'full'|'bundle-only'|'page-only', changedRoutes?: string[], fastPath?: boolean, globalGraphHash?: string, basePath?: string, routeCheck?: boolean, imageRuntimePayload?: object }} [bundlerOptions]
175
175
  * @returns {Promise<void>}
176
176
  */
177
+ /**
178
+ * Merge compiler-owned static image materialization rows from marker bindings + rewritten props literals.
179
+ * @param {object} pageIr
180
+ * @param {string[]} literals
181
+ * @param {object} [compilerRunOptions]
182
+ * @returns {object}
183
+ */
184
+ export function mergePageImageMaterialization(pageIr, literals, compilerRunOptions = {}) {
185
+ if (!Array.isArray(literals) || literals.length === 0) {
186
+ return pageIr;
187
+ }
188
+ const compilerToolchain = compilerRunOptions.compilerToolchain
189
+ || (compilerRunOptions.compilerBin && typeof compilerRunOptions.compilerBin === 'object'
190
+ ? compilerRunOptions.compilerBin
191
+ : null);
192
+ const compilerBin = !compilerToolchain && typeof compilerRunOptions.compilerBin === 'string'
193
+ ? compilerRunOptions.compilerBin
194
+ : null;
195
+ const payload = JSON.stringify({
196
+ marker_bindings: pageIr.marker_bindings,
197
+ literals
198
+ });
199
+ const args = ['--merge-image-materialization'];
200
+ const opts = {
201
+ encoding: 'utf8',
202
+ maxBuffer: COMPILER_SPAWN_MAX_BUFFER,
203
+ input: payload
204
+ };
205
+ const result = compilerToolchain
206
+ ? runToolchainSync(compilerToolchain, args, opts).result
207
+ : (compilerBin
208
+ ? spawnSync(compilerBin, args, opts)
209
+ : runToolchainSync(createCompilerToolchain({
210
+ logger: compilerRunOptions.logger || null
211
+ }), args, opts).result);
212
+ if (result.error) {
213
+ throw new Error(`merge image materialization spawn failed: ${result.error.message}`);
214
+ }
215
+ if (result.status !== 0) {
216
+ throw new Error(`merge image materialization failed with exit code ${result.status}\n${result.stderr || ''}`);
217
+ }
218
+ const parsed = JSON.parse(result.stdout);
219
+ return {
220
+ ...pageIr,
221
+ image_materialization: parsed.image_materialization
222
+ };
223
+ }
177
224
  export function runBundler(envelope, outDir, projectRoot, logger = null, showInfo = true, bundlerBin = resolveBundlerBin(projectRoot), bundlerOptions = {}) {
178
225
  return new Promise((resolvePromise, rejectPromise) => {
179
226
  const useStructuredLogger = Boolean(logger && typeof logger.childLine === 'function');
@@ -192,6 +239,9 @@ export function runBundler(envelope, outDir, projectRoot, logger = null, showInf
192
239
  if (typeof bundlerOptions.basePath === 'string' && bundlerOptions.basePath.length > 0) {
193
240
  bundlerArgs.push('--base-path', bundlerOptions.basePath);
194
241
  }
242
+ if (bundlerOptions.routeCheck === true) {
243
+ bundlerArgs.push('--route-check');
244
+ }
195
245
  if (bundlerOptions.devStableAssets === true) {
196
246
  bundlerArgs.push('--dev-stable-assets');
197
247
  }
@@ -234,7 +284,13 @@ export function runBundler(envelope, outDir, projectRoot, logger = null, showInf
234
284
  }
235
285
  rejectPromise(new Error(`Bundler failed with exit code ${code}`));
236
286
  });
237
- child.stdin.write(JSON.stringify(envelope));
287
+ const bundlerPayload = bundlerOptions.imageRuntimePayload
288
+ ? {
289
+ inputs: Array.isArray(envelope) ? envelope : [envelope],
290
+ image_runtime_payload: bundlerOptions.imageRuntimePayload
291
+ }
292
+ : envelope;
293
+ child.stdin.write(JSON.stringify(bundlerPayload));
238
294
  child.stdin.end();
239
295
  });
240
296
  }
@@ -0,0 +1 @@
1
+ export function rewriteCompilerSignalMapReferences(compiledExpr: any, buildReplacement: any): string | null;
@@ -0,0 +1,155 @@
1
+ import { loadTypeScriptApi } from './compiler-runtime.js';
2
+ import { normalizeTypeScriptExpression } from './typescript-expression-utils.js';
3
+ function collectBindingNames(ts, name, target) {
4
+ if (ts.isIdentifier(name)) {
5
+ target.add(name.text);
6
+ return;
7
+ }
8
+ if (ts.isObjectBindingPattern(name) || ts.isArrayBindingPattern(name)) {
9
+ for (const element of name.elements) {
10
+ if (ts.isBindingElement(element)) {
11
+ collectBindingNames(ts, element.name, target);
12
+ }
13
+ }
14
+ }
15
+ }
16
+ function collectDirectBlockBindings(ts, block, target) {
17
+ const statements = Array.isArray(block?.statements) ? block.statements : [];
18
+ for (const statement of statements) {
19
+ if (ts.isVariableStatement(statement)) {
20
+ for (const declaration of statement.declarationList.declarations) {
21
+ collectBindingNames(ts, declaration.name, target);
22
+ }
23
+ continue;
24
+ }
25
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) && statement.name) {
26
+ target.add(statement.name.text);
27
+ }
28
+ }
29
+ }
30
+ function isNestedBlockScope(ts, node) {
31
+ return (ts.isBlock(node) || ts.isModuleBlock(node)) && !ts.isSourceFile(node.parent);
32
+ }
33
+ function resolveCompilerSignalIndex(ts, node, localBindings) {
34
+ if (!ts.isCallExpression(node)) {
35
+ return null;
36
+ }
37
+ if (node.arguments.length !== 1) {
38
+ return null;
39
+ }
40
+ const expression = node.expression;
41
+ if (!ts.isPropertyAccessExpression(expression) || expression.name.text !== 'get') {
42
+ return null;
43
+ }
44
+ if (!ts.isIdentifier(expression.expression) || expression.expression.text !== 'signalMap') {
45
+ return null;
46
+ }
47
+ if (localBindings.has('signalMap')) {
48
+ return null;
49
+ }
50
+ const [indexArg] = node.arguments;
51
+ if (!ts.isNumericLiteral(indexArg)) {
52
+ return null;
53
+ }
54
+ const signalIndex = Number.parseInt(indexArg.text, 10);
55
+ return Number.isInteger(signalIndex) ? signalIndex : null;
56
+ }
57
+ export function rewriteCompilerSignalMapReferences(compiledExpr, buildReplacement) {
58
+ const source = typeof compiledExpr === 'string' ? compiledExpr.trim() : '';
59
+ if (!source) {
60
+ return null;
61
+ }
62
+ if (!source.includes('signalMap.get(') || typeof buildReplacement !== 'function') {
63
+ return normalizeTypeScriptExpression(source);
64
+ }
65
+ const ts = loadTypeScriptApi();
66
+ if (!ts) {
67
+ return normalizeTypeScriptExpression(source);
68
+ }
69
+ let sourceFile;
70
+ try {
71
+ sourceFile = ts.createSourceFile('zenith-compiled-expression.ts', `const __zenith_expr__ = (${source});`, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
72
+ }
73
+ catch {
74
+ return normalizeTypeScriptExpression(source);
75
+ }
76
+ const nextScopeBindings = (node, localBindings) => {
77
+ if (ts.isSourceFile(node)) {
78
+ return localBindings;
79
+ }
80
+ if (ts.isFunctionLike(node)) {
81
+ const next = new Set(localBindings);
82
+ if (node.name && ts.isIdentifier(node.name) && !ts.isSourceFile(node.parent)) {
83
+ next.add(node.name.text);
84
+ }
85
+ for (const param of node.parameters) {
86
+ collectBindingNames(ts, param.name, next);
87
+ }
88
+ return next;
89
+ }
90
+ if (isNestedBlockScope(ts, node)) {
91
+ const next = new Set(localBindings);
92
+ collectDirectBlockBindings(ts, node, next);
93
+ return next;
94
+ }
95
+ if (ts.isCatchClause(node) && node.variableDeclaration) {
96
+ const next = new Set(localBindings);
97
+ collectBindingNames(ts, node.variableDeclaration.name, next);
98
+ return next;
99
+ }
100
+ if ((ts.isForStatement(node) || ts.isForInStatement(node) || ts.isForOfStatement(node))
101
+ && node.initializer
102
+ && ts.isVariableDeclarationList(node.initializer)) {
103
+ const next = new Set(localBindings);
104
+ for (const declaration of node.initializer.declarations) {
105
+ collectBindingNames(ts, declaration.name, next);
106
+ }
107
+ return next;
108
+ }
109
+ return localBindings;
110
+ };
111
+ const transformer = (context) => {
112
+ const visit = (node, localBindings) => {
113
+ const scopeBindings = nextScopeBindings(node, localBindings);
114
+ if (ts.isCallExpression(node) && node.arguments.length === 0) {
115
+ const expression = node.expression;
116
+ if (ts.isPropertyAccessExpression(expression) && expression.name.text === 'get') {
117
+ const signalIndex = resolveCompilerSignalIndex(ts, expression.expression, scopeBindings);
118
+ if (Number.isInteger(signalIndex)) {
119
+ const replacement = buildReplacement({ ts, signalIndex, valueRead: true });
120
+ if (replacement) {
121
+ return replacement;
122
+ }
123
+ }
124
+ }
125
+ }
126
+ const signalIndex = resolveCompilerSignalIndex(ts, node, scopeBindings);
127
+ if (Number.isInteger(signalIndex)) {
128
+ const replacement = buildReplacement({ ts, signalIndex, valueRead: false });
129
+ if (replacement) {
130
+ return replacement;
131
+ }
132
+ }
133
+ return ts.visitEachChild(node, (child) => visit(child, scopeBindings), context);
134
+ };
135
+ return (node) => ts.visitNode(node, (child) => visit(child, new Set()));
136
+ };
137
+ const result = ts.transform(sourceFile, [transformer]);
138
+ try {
139
+ const printed = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed })
140
+ .printFile(result.transformed[0])
141
+ .trimEnd();
142
+ const prefix = 'const __zenith_expr__ = (';
143
+ const suffix = ');';
144
+ if (!printed.startsWith(prefix) || !printed.endsWith(suffix)) {
145
+ return normalizeTypeScriptExpression(source);
146
+ }
147
+ return normalizeTypeScriptExpression(printed.slice(prefix.length, printed.length - suffix.length));
148
+ }
149
+ catch {
150
+ return normalizeTypeScriptExpression(source);
151
+ }
152
+ finally {
153
+ result.dispose();
154
+ }
155
+ }
@@ -1,10 +1,5 @@
1
1
  /**
2
- * @param {string} compPath
3
- * @param {string} componentSource
4
2
  * @param {object} compIr
5
- * @param {object} compilerOpts
6
- * @param {string|object} compilerBin
7
- * @param {Map<string, string[]> | null} [templateExpressionCache]
8
3
  * @param {Record<string, number> | null} [rewriteMetrics]
9
4
  * @returns {{
10
5
  * map: Map<string, string>,
@@ -22,7 +17,7 @@
22
17
  * sequence: Array<{ raw: string, rewritten: string, binding: object | null }>
23
18
  * }}
24
19
  */
25
- export function buildComponentExpressionRewrite(compPath: string, componentSource: string, compIr: object, compilerOpts: object, compilerBin: string | object, templateExpressionCache?: Map<string, string[]> | null, rewriteMetrics?: Record<string, number> | null): {
20
+ export function buildComponentExpressionRewrite(compIr: object, rewriteMetrics?: Record<string, number> | null): {
26
21
  map: Map<string, string>;
27
22
  bindings: Map<string, {
28
23
  compiled_expr: string | null;