@zenithbuild/cli 0.7.4 → 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 +5 -3
- package/dist/adapters/adapter-netlify.d.ts +1 -1
- package/dist/adapters/adapter-netlify.js +56 -14
- 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 -14
- 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 +1 -1
- package/dist/build/compiler-runtime.js +8 -2
- package/dist/build/page-loop-state.js +1 -1
- package/dist/build/server-script.d.ts +2 -1
- package/dist/build/server-script.js +7 -3
- package/dist/build-output-manifest.d.ts +3 -2
- package/dist/build-output-manifest.js +3 -0
- package/dist/build.js +29 -17
- package/dist/dev-server.js +79 -25
- package/dist/download-result.d.ts +14 -0
- package/dist/download-result.js +148 -0
- package/dist/images/service.d.ts +13 -1
- package/dist/images/service.js +45 -15
- package/dist/manifest.d.ts +15 -1
- package/dist/manifest.js +24 -5
- package/dist/preview.d.ts +11 -3
- package/dist/preview.js +188 -62
- package/dist/request-body.d.ts +0 -1
- package/dist/request-body.js +0 -6
- 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.js +1 -1
- package/dist/server-contract.d.ts +24 -16
- package/dist/server-contract.js +217 -25
- package/dist/server-error.d.ts +1 -1
- package/dist/server-error.js +2 -0
- package/dist/server-output.d.ts +2 -1
- package/dist/server-output.js +59 -11
- package/dist/server-runtime/node-server.js +34 -4
- package/dist/server-runtime/route-render.d.ts +25 -1
- package/dist/server-runtime/route-render.js +81 -29
- package/dist/server-script-composition.d.ts +4 -2
- package/dist/server-script-composition.js +6 -3
- package/dist/static-export-paths.d.ts +3 -0
- package/dist/static-export-paths.js +160 -0
- package/package.json +3 -3
|
@@ -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,7 +42,7 @@ 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, routeCheck?: boolean }} [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
48
|
/**
|
|
@@ -171,7 +171,7 @@ 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, routeCheck?: boolean }} [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
177
|
/**
|
|
@@ -284,7 +284,13 @@ export function runBundler(envelope, outDir, projectRoot, logger = null, showInf
|
|
|
284
284
|
}
|
|
285
285
|
rejectPromise(new Error(`Bundler failed with exit code ${code}`));
|
|
286
286
|
});
|
|
287
|
-
|
|
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));
|
|
288
294
|
child.stdin.end();
|
|
289
295
|
});
|
|
290
296
|
}
|
|
@@ -48,7 +48,7 @@ export function preparePageIrForMerge(pageIr) {
|
|
|
48
48
|
}
|
|
49
49
|
export function applyServerEnvelopeToPageIr({ pageIr, composedServer, hasGuard, hasLoad, hasAction, entry, srcDir, sourceFile }) {
|
|
50
50
|
if (composedServer.serverScript) {
|
|
51
|
-
const { has_action: _unusedHasAction, ...serverScript } = composedServer.serverScript;
|
|
51
|
+
const { has_action: _unusedHasAction, export_paths: _unusedExportPaths, ...serverScript } = composedServer.serverScript;
|
|
52
52
|
pageIr.server_script = serverScript;
|
|
53
53
|
pageIr.prerender = composedServer.serverScript.prerender === true;
|
|
54
54
|
if (pageIr.ssr_data === undefined) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @param {string} source
|
|
3
3
|
* @param {string} sourceFile
|
|
4
4
|
* @param {object} [compilerOpts]
|
|
5
|
-
* @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null }}
|
|
5
|
+
* @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null }}
|
|
6
6
|
*/
|
|
7
7
|
export function extractServerScript(source: string, sourceFile: string, compilerOpts?: object): {
|
|
8
8
|
source: string;
|
|
@@ -13,6 +13,7 @@ export function extractServerScript(source: string, sourceFile: string, compiler
|
|
|
13
13
|
has_load: boolean;
|
|
14
14
|
has_action: boolean;
|
|
15
15
|
source_path: string;
|
|
16
|
+
export_paths?: string[];
|
|
16
17
|
} | null;
|
|
17
18
|
};
|
|
18
19
|
/**
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { findNextKnownComponentTag } from '../component-tag-parser.js';
|
|
3
|
+
import { extractStaticExportPaths } from '../static-export-paths.js';
|
|
3
4
|
/**
|
|
4
5
|
* @param {string} source
|
|
5
6
|
* @param {string} sourceFile
|
|
6
7
|
* @param {object} [compilerOpts]
|
|
7
|
-
* @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string } | null }}
|
|
8
|
+
* @returns {{ source: string, serverScript: { source: string, prerender: boolean, has_guard: boolean, has_load: boolean, has_action: boolean, source_path: string, export_paths?: string[] } | null }}
|
|
8
9
|
*/
|
|
9
10
|
export function extractServerScript(source, sourceFile, compilerOpts = {}) {
|
|
10
11
|
const scriptRe = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
@@ -155,6 +156,7 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
|
|
|
155
156
|
}
|
|
156
157
|
prerender = rawValue.startsWith('true');
|
|
157
158
|
}
|
|
159
|
+
const exportPaths = extractStaticExportPaths(serverSource, sourceFile) || [];
|
|
158
160
|
const start = match.index ?? -1;
|
|
159
161
|
if (start < 0) {
|
|
160
162
|
return {
|
|
@@ -165,7 +167,8 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
|
|
|
165
167
|
has_guard: hasGuard,
|
|
166
168
|
has_load: hasLoad,
|
|
167
169
|
has_action: hasAction,
|
|
168
|
-
source_path: sourceFile
|
|
170
|
+
source_path: sourceFile,
|
|
171
|
+
export_paths: exportPaths
|
|
169
172
|
}
|
|
170
173
|
};
|
|
171
174
|
}
|
|
@@ -179,7 +182,8 @@ export function extractServerScript(source, sourceFile, compilerOpts = {}) {
|
|
|
179
182
|
has_guard: hasGuard,
|
|
180
183
|
has_load: hasLoad,
|
|
181
184
|
has_action: hasAction,
|
|
182
|
-
source_path: sourceFile
|
|
185
|
+
source_path: sourceFile,
|
|
186
|
+
export_paths: exportPaths
|
|
183
187
|
}
|
|
184
188
|
};
|
|
185
189
|
}
|
|
@@ -11,14 +11,15 @@ export function writeBuildOutputManifest({ coreOutputDir, staticDir, target, rou
|
|
|
11
11
|
base_path: string;
|
|
12
12
|
content_hash: any;
|
|
13
13
|
routes: {
|
|
14
|
+
html: any;
|
|
15
|
+
assets: any[];
|
|
16
|
+
export_paths?: any[] | undefined;
|
|
14
17
|
path: any;
|
|
15
18
|
file: any;
|
|
16
19
|
path_kind: any;
|
|
17
20
|
render_mode: any;
|
|
18
21
|
requires_hydration: boolean;
|
|
19
22
|
params: any[];
|
|
20
|
-
html: any;
|
|
21
|
-
assets: any[];
|
|
22
23
|
}[];
|
|
23
24
|
assets: {
|
|
24
25
|
js: any[];
|
|
@@ -67,6 +67,9 @@ export async function writeBuildOutputManifest({ coreOutputDir, staticDir, targe
|
|
|
67
67
|
render_mode: entry.render_mode,
|
|
68
68
|
requires_hydration: /<script\b[^>]*type="module"/i.test(html),
|
|
69
69
|
params: [...entry.params],
|
|
70
|
+
...(Array.isArray(entry.export_paths) && entry.export_paths.length > 0
|
|
71
|
+
? { export_paths: [...entry.export_paths] }
|
|
72
|
+
: {}),
|
|
70
73
|
html: htmlPath,
|
|
71
74
|
assets
|
|
72
75
|
});
|
package/dist/build.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { mkdir, rm } from 'node:fs/promises';
|
|
1
|
+
import { cp, mkdir, rm } from 'node:fs/promises';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { resolveBuildAdapter } from './adapters/resolve-adapter.js';
|
|
4
4
|
import { normalizeBasePath } from './base-path.js';
|
|
5
5
|
import { rewriteSoftNavigationHrefBasePathInHtmlFiles } from './base-path-html.js';
|
|
6
6
|
import { writeBuildOutputManifest } from './build-output-manifest.js';
|
|
7
7
|
import { generateManifest } from './manifest.js';
|
|
8
|
+
import { writeResourceRouteManifest } from './resource-manifest.js';
|
|
8
9
|
import { buildComponentRegistry } from './resolve-components.js';
|
|
9
10
|
import { collectAssets, createCompilerWarningEmitter, runBundler } from './build/compiler-runtime.js';
|
|
10
11
|
import { buildPageEnvelopes } from './build/page-loop.js';
|
|
11
12
|
import { deriveProjectRootFromPagesDir, ensureZenithTypeDeclarations } from './build/type-declarations.js';
|
|
12
|
-
import { materializeImageMarkupInHtmlFiles } from './images/materialize.js';
|
|
13
13
|
import { buildImageArtifacts } from './images/service.js';
|
|
14
14
|
import { injectImageMaterializationIntoRouterManifest } from './images/router-manifest.js';
|
|
15
15
|
import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } from './images/payload.js';
|
|
@@ -50,6 +50,7 @@ export async function build(options) {
|
|
|
50
50
|
const projectRoot = deriveProjectRootFromPagesDir(pagesDir);
|
|
51
51
|
const coreOutputDir = join(projectRoot, '.zenith-output');
|
|
52
52
|
const staticOutputDir = join(coreOutputDir, 'static');
|
|
53
|
+
const imageStageDir = join(coreOutputDir, 'image-materialization-stage');
|
|
53
54
|
const srcDir = resolve(pagesDir, '..');
|
|
54
55
|
const compilerBin = createCompilerToolchain({ projectRoot, logger });
|
|
55
56
|
const bundlerBin = createBundlerToolchain({ projectRoot, logger });
|
|
@@ -75,11 +76,12 @@ export async function build(options) {
|
|
|
75
76
|
}
|
|
76
77
|
const registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
|
|
77
78
|
const manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(pagesDir, '.zen', { compilerOpts }));
|
|
79
|
+
const pageManifest = manifest.filter((entry) => entry?.route_kind !== 'resource');
|
|
78
80
|
if (mode !== 'legacy') {
|
|
79
81
|
adapter.validateRoutes(manifest);
|
|
80
82
|
}
|
|
81
83
|
await startupProfile.measureAsync('ensure_zenith_type_declarations', () => ensureZenithTypeDeclarations({
|
|
82
|
-
manifest,
|
|
84
|
+
manifest: pageManifest,
|
|
83
85
|
pagesDir
|
|
84
86
|
}));
|
|
85
87
|
await startupProfile.measureAsync('reset_core_output', () => rm(coreOutputDir, { recursive: true, force: true }));
|
|
@@ -92,7 +94,7 @@ export async function build(options) {
|
|
|
92
94
|
console.warn(line);
|
|
93
95
|
});
|
|
94
96
|
const { envelopes, expressionRewriteMetrics } = await buildPageEnvelopes({
|
|
95
|
-
manifest,
|
|
97
|
+
manifest: pageManifest,
|
|
96
98
|
pagesDir,
|
|
97
99
|
srcDir,
|
|
98
100
|
registry,
|
|
@@ -103,27 +105,37 @@ export async function build(options) {
|
|
|
103
105
|
compilerTotals,
|
|
104
106
|
emitCompilerWarning
|
|
105
107
|
});
|
|
106
|
-
if (envelopes.length > 0) {
|
|
107
|
-
await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, { basePath, routeCheck: routeCheckEnabled }), { envelopes: envelopes.length });
|
|
108
|
-
await startupProfile.measureAsync('inject_image_materialization_manifest', () => injectImageMaterializationIntoRouterManifest(staticOutputDir, envelopes), { envelopes: envelopes.length });
|
|
109
|
-
}
|
|
110
|
-
await startupProfile.measureAsync('rewrite_soft_navigation_base_path', () => rewriteSoftNavigationHrefBasePathInHtmlFiles(staticOutputDir, basePath));
|
|
111
108
|
const { manifest: imageManifest } = await startupProfile.measureAsync('build_image_artifacts', () => buildImageArtifacts({
|
|
112
109
|
projectRoot,
|
|
113
|
-
outDir:
|
|
110
|
+
outDir: imageStageDir,
|
|
114
111
|
config: config.images
|
|
115
112
|
}));
|
|
116
113
|
const imageRuntimePayload = createImageRuntimePayload(config.images, imageManifest, 'passthrough', basePath);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
if (envelopes.length > 0) {
|
|
115
|
+
await startupProfile.measureAsync('run_bundler', () => runBundler(envelopes, staticOutputDir, projectRoot, logger, showBundlerInfo, bundlerBin, {
|
|
116
|
+
basePath,
|
|
117
|
+
routeCheck: routeCheckEnabled,
|
|
118
|
+
imageRuntimePayload
|
|
119
|
+
}), { envelopes: envelopes.length });
|
|
120
|
+
await startupProfile.measureAsync('inject_image_materialization_manifest', () => injectImageMaterializationIntoRouterManifest(staticOutputDir, envelopes), { envelopes: envelopes.length });
|
|
121
|
+
}
|
|
122
|
+
await startupProfile.measureAsync('write_resource_manifest', () => writeResourceRouteManifest(staticOutputDir, manifest, basePath));
|
|
123
|
+
await startupProfile.measureAsync('rewrite_soft_navigation_base_path', () => rewriteSoftNavigationHrefBasePathInHtmlFiles(staticOutputDir, basePath));
|
|
124
|
+
await startupProfile.measureAsync('stage_image_artifacts_into_static_output', async () => {
|
|
125
|
+
if (Object.keys(imageManifest).length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
await cp(join(imageStageDir, '_zenith'), join(staticOutputDir, '_zenith'), {
|
|
129
|
+
recursive: true,
|
|
130
|
+
force: true
|
|
131
|
+
});
|
|
132
|
+
});
|
|
121
133
|
await startupProfile.measureAsync('inject_image_runtime_payload', () => injectImageRuntimePayloadIntoHtmlFiles(staticOutputDir, imageRuntimePayload));
|
|
122
134
|
const buildManifest = await startupProfile.measureAsync('write_core_manifest', () => writeBuildOutputManifest({
|
|
123
135
|
coreOutputDir,
|
|
124
136
|
staticDir: staticOutputDir,
|
|
125
137
|
target,
|
|
126
|
-
routeManifest:
|
|
138
|
+
routeManifest: pageManifest,
|
|
127
139
|
basePath
|
|
128
140
|
}));
|
|
129
141
|
await startupProfile.measureAsync('write_server_output', () => writeServerOutput({
|
|
@@ -136,11 +148,11 @@ export async function build(options) {
|
|
|
136
148
|
await startupProfile.measureAsync('adapt_output', () => adapter.adapt({ coreOutput: coreOutputDir, outDir, manifest: buildManifest, config }));
|
|
137
149
|
const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
|
|
138
150
|
startupProfile.emit('build_complete', {
|
|
139
|
-
pages:
|
|
151
|
+
pages: pageManifest.length,
|
|
140
152
|
assets: assets.length,
|
|
141
153
|
target,
|
|
142
154
|
compilerTotals,
|
|
143
155
|
expressionRewriteMetrics
|
|
144
156
|
});
|
|
145
|
-
return { pages:
|
|
157
|
+
return { pages: pageManifest.length, assets };
|
|
146
158
|
}
|