@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.
- package/README.md +18 -13
- package/dist/adapters/adapter-netlify.d.ts +1 -1
- package/dist/adapters/adapter-netlify.js +56 -13
- package/dist/adapters/adapter-node.js +8 -0
- package/dist/adapters/adapter-static-export.d.ts +5 -0
- package/dist/adapters/adapter-static-export.js +115 -0
- package/dist/adapters/adapter-types.d.ts +3 -1
- package/dist/adapters/adapter-types.js +5 -2
- package/dist/adapters/adapter-vercel.d.ts +1 -1
- package/dist/adapters/adapter-vercel.js +70 -13
- package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
- package/dist/adapters/copy-hosted-page-runtime.js +49 -0
- package/dist/adapters/resolve-adapter.js +4 -0
- package/dist/adapters/route-rules.d.ts +5 -0
- package/dist/adapters/route-rules.js +9 -0
- package/dist/adapters/validate-hosted-resource-routes.d.ts +1 -0
- package/dist/adapters/validate-hosted-resource-routes.js +13 -0
- package/dist/auth/route-auth.d.ts +6 -0
- package/dist/auth/route-auth.js +236 -0
- package/dist/build/compiler-runtime.d.ts +10 -9
- package/dist/build/compiler-runtime.js +58 -2
- package/dist/build/compiler-signal-expression.d.ts +1 -0
- package/dist/build/compiler-signal-expression.js +155 -0
- package/dist/build/expression-rewrites.d.ts +1 -6
- package/dist/build/expression-rewrites.js +61 -65
- package/dist/build/page-component-loop.d.ts +3 -13
- package/dist/build/page-component-loop.js +21 -46
- package/dist/build/page-ir-normalization.d.ts +0 -8
- package/dist/build/page-ir-normalization.js +13 -234
- package/dist/build/page-loop-state.d.ts +6 -9
- package/dist/build/page-loop-state.js +9 -8
- package/dist/build/page-loop.js +27 -22
- package/dist/build/scoped-identifier-rewrite.d.ts +37 -44
- package/dist/build/scoped-identifier-rewrite.js +28 -128
- package/dist/build/server-script.d.ts +3 -1
- package/dist/build/server-script.js +35 -5
- package/dist/build-output-manifest.d.ts +3 -2
- package/dist/build-output-manifest.js +3 -0
- package/dist/build.js +32 -18
- package/dist/component-instance-ir.js +158 -52
- package/dist/dev-build-session.js +20 -6
- package/dist/dev-server.js +152 -55
- package/dist/download-result.d.ts +14 -0
- package/dist/download-result.js +148 -0
- package/dist/framework-components/Image.zen +1 -1
- package/dist/images/materialization-plan.d.ts +1 -0
- package/dist/images/materialization-plan.js +6 -0
- package/dist/images/materialize.d.ts +5 -3
- package/dist/images/materialize.js +24 -109
- package/dist/images/router-manifest.d.ts +1 -0
- package/dist/images/router-manifest.js +49 -0
- package/dist/images/service.d.ts +13 -1
- package/dist/images/service.js +45 -15
- package/dist/index.js +8 -2
- package/dist/manifest.d.ts +15 -1
- package/dist/manifest.js +27 -7
- package/dist/preview.d.ts +13 -4
- package/dist/preview.js +261 -101
- package/dist/request-body.d.ts +1 -0
- package/dist/request-body.js +7 -0
- package/dist/request-origin.d.ts +2 -0
- package/dist/request-origin.js +45 -0
- package/dist/resource-manifest.d.ts +16 -0
- package/dist/resource-manifest.js +53 -0
- package/dist/resource-response.d.ts +34 -0
- package/dist/resource-response.js +71 -0
- package/dist/resource-route-module.d.ts +15 -0
- package/dist/resource-route-module.js +129 -0
- package/dist/route-check-support.d.ts +1 -0
- package/dist/route-check-support.js +4 -0
- package/dist/server-contract.d.ts +29 -6
- package/dist/server-contract.js +304 -42
- package/dist/server-error.d.ts +4 -0
- package/dist/server-error.js +36 -0
- package/dist/server-output.d.ts +4 -1
- package/dist/server-output.js +71 -10
- package/dist/server-runtime/node-server.js +67 -31
- package/dist/server-runtime/route-render.d.ts +27 -3
- package/dist/server-runtime/route-render.js +94 -53
- package/dist/server-script-composition.d.ts +13 -5
- package/dist/server-script-composition.js +29 -11
- package/dist/static-export-paths.d.ts +3 -0
- package/dist/static-export-paths.js +160 -0
- 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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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(
|
|
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;
|