@zenithbuild/cli 0.7.4 → 0.7.7
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 +48 -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 +67 -19
- package/dist/adapters/copy-hosted-page-runtime.d.ts +1 -0
- package/dist/adapters/copy-hosted-page-runtime.js +50 -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/hoisted-code-transforms.d.ts +4 -1
- package/dist/build/hoisted-code-transforms.js +5 -3
- package/dist/build/page-ir-normalization.d.ts +1 -1
- package/dist/build/page-ir-normalization.js +33 -3
- package/dist/build/page-loop-state.js +1 -1
- package/dist/build/page-loop.js +46 -2
- 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-build-session/helpers.d.ts +29 -0
- package/dist/dev-build-session/helpers.js +223 -0
- package/dist/dev-build-session/session.d.ts +24 -0
- package/dist/dev-build-session/session.js +204 -0
- package/dist/dev-build-session/state.d.ts +37 -0
- package/dist/dev-build-session/state.js +17 -0
- package/dist/dev-build-session.d.ts +1 -24
- package/dist/dev-build-session.js +1 -434
- package/dist/dev-server/css-state.d.ts +7 -0
- package/dist/dev-server/css-state.js +92 -0
- package/dist/dev-server/not-found.d.ts +23 -0
- package/dist/dev-server/not-found.js +129 -0
- package/dist/dev-server/request-handler.d.ts +1 -0
- package/dist/dev-server/request-handler.js +376 -0
- package/dist/dev-server/route-check.d.ts +9 -0
- package/dist/dev-server/route-check.js +100 -0
- package/dist/dev-server/watcher.d.ts +5 -0
- package/dist/dev-server/watcher.js +216 -0
- package/dist/dev-server.js +136 -883
- package/dist/download-result.d.ts +14 -0
- package/dist/download-result.js +148 -0
- package/dist/images/payload.js +4 -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 +70 -6
- package/dist/preview/create-preview-server.d.ts +18 -0
- package/dist/preview/create-preview-server.js +71 -0
- package/dist/preview/manifest.d.ts +42 -0
- package/dist/preview/manifest.js +57 -0
- package/dist/preview/paths.d.ts +3 -0
- package/dist/preview/paths.js +38 -0
- package/dist/preview/payload.d.ts +6 -0
- package/dist/preview/payload.js +34 -0
- package/dist/preview/request-handler.d.ts +1 -0
- package/dist/preview/request-handler.js +300 -0
- package/dist/preview/server-runner.d.ts +49 -0
- package/dist/preview/server-runner.js +220 -0
- package/dist/preview/server-script-runner-template.d.ts +1 -0
- package/dist/preview/server-script-runner-template.js +425 -0
- package/dist/preview.d.ts +5 -104
- package/dist/preview.js +7 -993
- 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 +49 -0
- package/dist/resource-response.js +160 -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/constants.d.ts +5 -0
- package/dist/server-contract/constants.js +5 -0
- package/dist/server-contract/export-validation.d.ts +5 -0
- package/dist/server-contract/export-validation.js +59 -0
- package/dist/server-contract/json-serializable.d.ts +1 -0
- package/dist/server-contract/json-serializable.js +52 -0
- package/dist/server-contract/resolve.d.ts +15 -0
- package/dist/server-contract/resolve.js +271 -0
- package/dist/server-contract/result-helpers.d.ts +51 -0
- package/dist/server-contract/result-helpers.js +59 -0
- package/dist/server-contract/route-result-validation.d.ts +2 -0
- package/dist/server-contract/route-result-validation.js +73 -0
- package/dist/server-contract/stage.d.ts +6 -0
- package/dist/server-contract/stage.js +22 -0
- package/dist/server-contract.d.ts +6 -54
- package/dist/server-contract.js +9 -301
- package/dist/server-error.d.ts +1 -1
- package/dist/server-error.js +2 -0
- package/dist/server-middleware.d.ts +10 -0
- package/dist/server-middleware.js +30 -0
- package/dist/server-output.d.ts +2 -1
- package/dist/server-output.js +72 -12
- package/dist/server-runtime/node-server.js +59 -7
- 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
|
@@ -1,434 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { resolve } from 'node:path';
|
|
4
|
-
import { buildComponentRegistry } from './resolve-components.js';
|
|
5
|
-
import { normalizeBasePath } from './base-path.js';
|
|
6
|
-
import { collectAssets, createCompilerWarningEmitter, runBundler } from './build/compiler-runtime.js';
|
|
7
|
-
import { buildPageEnvelopes } from './build/page-loop.js';
|
|
8
|
-
import { createPageLoopCaches } from './build/page-loop-state.js';
|
|
9
|
-
import { deriveProjectRootFromPagesDir, ensureZenithTypeDeclarations } from './build/type-declarations.js';
|
|
10
|
-
import { injectImageMaterializationIntoRouterManifest } from './images/router-manifest.js';
|
|
11
|
-
import { buildImageArtifacts } from './images/service.js';
|
|
12
|
-
import { materializeImageMarkupInHtmlFiles } from './images/materialize.js';
|
|
13
|
-
import { createImageRuntimePayload, injectImageRuntimePayloadIntoHtmlFiles } from './images/payload.js';
|
|
14
|
-
import { createStartupProfiler } from './startup-profile.js';
|
|
15
|
-
import { resolveBundlerBin } from './toolchain-paths.js';
|
|
16
|
-
import { resolveBuildAdapter } from './adapters/resolve-adapter.js';
|
|
17
|
-
import { supportsTargetRouteCheck } from './route-check-support.js';
|
|
18
|
-
import { createBundlerToolchain, createCompilerToolchain, ensureToolchainCompatibility, getActiveToolchainCandidate } from './toolchain-runner.js';
|
|
19
|
-
import { maybeWarnAboutZenithVersionMismatch } from './version-check.js';
|
|
20
|
-
import { generateManifest } from './manifest.js';
|
|
21
|
-
function createCompilerTotals() {
|
|
22
|
-
return {
|
|
23
|
-
pageMs: 0,
|
|
24
|
-
ownerMs: 0,
|
|
25
|
-
componentMs: 0,
|
|
26
|
-
pageCalls: 0,
|
|
27
|
-
ownerCalls: 0,
|
|
28
|
-
componentCalls: 0,
|
|
29
|
-
componentCacheHits: 0,
|
|
30
|
-
componentCacheMisses: 0
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
function createExpressionRewriteMetrics() {
|
|
34
|
-
return {
|
|
35
|
-
calls: 0,
|
|
36
|
-
compilerOwnedBindings: 0,
|
|
37
|
-
ambiguousBindings: 0
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
function toManifestEntryMap(manifest, pagesDir) {
|
|
41
|
-
const map = new Map();
|
|
42
|
-
for (const entry of manifest) {
|
|
43
|
-
map.set(resolve(pagesDir, entry.file), entry);
|
|
44
|
-
}
|
|
45
|
-
return map;
|
|
46
|
-
}
|
|
47
|
-
function orderEnvelopes(manifest, pagesDir, envelopeByFile) {
|
|
48
|
-
const ordered = [];
|
|
49
|
-
for (const entry of manifest) {
|
|
50
|
-
const envelope = envelopeByFile.get(resolve(pagesDir, entry.file));
|
|
51
|
-
if (!envelope) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
ordered.push(envelope);
|
|
55
|
-
}
|
|
56
|
-
return ordered;
|
|
57
|
-
}
|
|
58
|
-
function isCssOnlyChange(changedFiles) {
|
|
59
|
-
return changedFiles.length > 0 && changedFiles.every((filePath) => filePath.endsWith('.css'));
|
|
60
|
-
}
|
|
61
|
-
function stableJson(value) {
|
|
62
|
-
if (value === null || value === undefined) {
|
|
63
|
-
return 'null';
|
|
64
|
-
}
|
|
65
|
-
if (Array.isArray(value)) {
|
|
66
|
-
return `[${value.map((entry) => stableJson(entry)).join(',')}]`;
|
|
67
|
-
}
|
|
68
|
-
if (typeof value === 'object') {
|
|
69
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`).join(',')}}`;
|
|
70
|
-
}
|
|
71
|
-
return JSON.stringify(value);
|
|
72
|
-
}
|
|
73
|
-
function collectJsImportSpecifiers(source) {
|
|
74
|
-
const values = [];
|
|
75
|
-
const patterns = [
|
|
76
|
-
/\bimport\s+(?:[^'"\n;]*?\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
77
|
-
/\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
78
|
-
/\bexport\s+[^'"\n;]*?\s+from\s+['"]([^'"]+)['"]/g
|
|
79
|
-
];
|
|
80
|
-
for (const pattern of patterns) {
|
|
81
|
-
pattern.lastIndex = 0;
|
|
82
|
-
for (const match of source.matchAll(pattern)) {
|
|
83
|
-
const value = String(match[1] || '').trim();
|
|
84
|
-
if (value.length > 0 && !values.includes(value)) {
|
|
85
|
-
values.push(value);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return values.sort();
|
|
90
|
-
}
|
|
91
|
-
function isExternalRuntimeSpecifier(specifier) {
|
|
92
|
-
return !specifier.startsWith('.')
|
|
93
|
-
&& !specifier.startsWith('/')
|
|
94
|
-
&& !specifier.startsWith('@/')
|
|
95
|
-
&& !specifier.startsWith('\0zenith:')
|
|
96
|
-
&& !specifier.includes('zenith:');
|
|
97
|
-
}
|
|
98
|
-
function collectEnvelopeAssetContract(envelope) {
|
|
99
|
-
const cssImportSpecifiers = new Set();
|
|
100
|
-
const externalImportSpecifiers = new Set();
|
|
101
|
-
for (const entry of envelope.ir.hoisted?.imports || []) {
|
|
102
|
-
for (const specifier of collectJsImportSpecifiers(String(entry || ''))) {
|
|
103
|
-
if (specifier.endsWith('.css')) {
|
|
104
|
-
cssImportSpecifiers.add(specifier);
|
|
105
|
-
}
|
|
106
|
-
if (isExternalRuntimeSpecifier(specifier)) {
|
|
107
|
-
externalImportSpecifiers.add(specifier);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
for (const moduleEntry of envelope.ir.modules || []) {
|
|
112
|
-
for (const specifier of collectJsImportSpecifiers(String(moduleEntry?.source || ''))) {
|
|
113
|
-
if (specifier.endsWith('.css')) {
|
|
114
|
-
cssImportSpecifiers.add(specifier);
|
|
115
|
-
}
|
|
116
|
-
if (isExternalRuntimeSpecifier(specifier)) {
|
|
117
|
-
externalImportSpecifiers.add(specifier);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
for (const importEntry of envelope.ir.imports || []) {
|
|
122
|
-
const specifier = String(importEntry?.spec || '').trim();
|
|
123
|
-
if (!specifier) {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (specifier.endsWith('.css')) {
|
|
127
|
-
cssImportSpecifiers.add(specifier);
|
|
128
|
-
}
|
|
129
|
-
if (isExternalRuntimeSpecifier(specifier)) {
|
|
130
|
-
externalImportSpecifiers.add(specifier);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
componentHoistIds: Object.keys(envelope.ir.components_scripts || {}).sort(),
|
|
135
|
-
cssImportSpecifiers: [...cssImportSpecifiers].sort(),
|
|
136
|
-
externalImportSpecifiers: [...externalImportSpecifiers].sort()
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function collectTemplateClassSignature(envelope) {
|
|
140
|
-
const html = typeof envelope?.ir?.html === 'string' ? envelope.ir.html : '';
|
|
141
|
-
if (!html) {
|
|
142
|
-
return [];
|
|
143
|
-
}
|
|
144
|
-
const classes = new Set();
|
|
145
|
-
const classAttrRe = /\bclass\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
|
|
146
|
-
let match;
|
|
147
|
-
while ((match = classAttrRe.exec(html)) !== null) {
|
|
148
|
-
const rawValue = String(match[1] || match[2] || '');
|
|
149
|
-
for (const token of rawValue.split(/\s+/)) {
|
|
150
|
-
const value = token.trim();
|
|
151
|
-
if (value.length > 0) {
|
|
152
|
-
classes.add(value);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return [...classes].sort();
|
|
157
|
-
}
|
|
158
|
-
function buildPageOnlyFastPathSignature(envelope) {
|
|
159
|
-
return stableJson({
|
|
160
|
-
route: envelope.route,
|
|
161
|
-
router: envelope.router === true,
|
|
162
|
-
assetContract: collectEnvelopeAssetContract(envelope),
|
|
163
|
-
templateClassSignature: collectTemplateClassSignature(envelope),
|
|
164
|
-
styleBlocks: envelope.ir.style_blocks || [],
|
|
165
|
-
serverScript: envelope.ir.server_script || null,
|
|
166
|
-
prerender: envelope.ir.prerender === true,
|
|
167
|
-
hasGuard: envelope.ir.has_guard === true,
|
|
168
|
-
hasLoad: envelope.ir.has_load === true,
|
|
169
|
-
guardModuleRef: envelope.ir.guard_module_ref || null,
|
|
170
|
-
loadModuleRef: envelope.ir.load_module_ref || null
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
function buildGlobalGraphHash(envelopes) {
|
|
174
|
-
const nodesByHoistId = new Map();
|
|
175
|
-
const edgeSet = new Set();
|
|
176
|
-
for (const envelope of envelopes) {
|
|
177
|
-
for (const node of envelope.ir.graph_nodes || []) {
|
|
178
|
-
if (node && typeof node.hoist_id === 'string' && node.hoist_id.length > 0) {
|
|
179
|
-
nodesByHoistId.set(node.hoist_id, true);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
for (const edge of envelope.ir.graph_edges || []) {
|
|
183
|
-
if (typeof edge === 'string' && edge.length > 0) {
|
|
184
|
-
edgeSet.add(edge);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
let seed = '';
|
|
189
|
-
for (const hoistId of [...nodesByHoistId.keys()].sort()) {
|
|
190
|
-
seed += `node:${hoistId}\n`;
|
|
191
|
-
}
|
|
192
|
-
for (const edge of [...edgeSet].sort()) {
|
|
193
|
-
seed += `edge:${edge}\n`;
|
|
194
|
-
}
|
|
195
|
-
return createHash('sha256').update(seed).digest('hex');
|
|
196
|
-
}
|
|
197
|
-
function selectPageOnlyEntries(changedFiles, pagesDir, manifestEntryByPath) {
|
|
198
|
-
if (changedFiles.length === 0) {
|
|
199
|
-
return [];
|
|
200
|
-
}
|
|
201
|
-
const selected = new Map();
|
|
202
|
-
for (const filePath of changedFiles) {
|
|
203
|
-
const resolvedPath = resolve(filePath);
|
|
204
|
-
if (!resolvedPath.startsWith(pagesDir) || !resolvedPath.endsWith('.zen') || !existsSync(resolvedPath)) {
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
const entry = manifestEntryByPath.get(resolvedPath);
|
|
208
|
-
if (!entry) {
|
|
209
|
-
return [];
|
|
210
|
-
}
|
|
211
|
-
selected.set(entry.file, entry);
|
|
212
|
-
}
|
|
213
|
-
return [...selected.values()];
|
|
214
|
-
}
|
|
215
|
-
async function maybeRunVersionCheck({ state, startupProfile, projectRoot, logger, bundlerBin }) {
|
|
216
|
-
if (state.versionChecked) {
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const resolvedBundlerCandidate = getActiveToolchainCandidate(bundlerBin);
|
|
220
|
-
await startupProfile.measureAsync('version_mismatch_check', () => maybeWarnAboutZenithVersionMismatch({
|
|
221
|
-
projectRoot,
|
|
222
|
-
logger,
|
|
223
|
-
command: 'dev',
|
|
224
|
-
bundlerBinPath: resolvedBundlerCandidate?.path || resolveBundlerBin(projectRoot)
|
|
225
|
-
}));
|
|
226
|
-
state.versionChecked = true;
|
|
227
|
-
}
|
|
228
|
-
function buildCompilerWarningEmitter(logger) {
|
|
229
|
-
return createCompilerWarningEmitter((line) => {
|
|
230
|
-
if (logger && typeof logger.warn === 'function') {
|
|
231
|
-
logger.warn(line, { onceKey: `compiler-warning:${line}` });
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
console.warn(line);
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
export function createDevBuildSession(options) {
|
|
238
|
-
const { pagesDir, outDir, config = {}, logger = null } = options;
|
|
239
|
-
const resolvedPagesDir = resolve(pagesDir);
|
|
240
|
-
const projectRoot = deriveProjectRootFromPagesDir(resolvedPagesDir);
|
|
241
|
-
const srcDir = resolve(resolvedPagesDir, '..');
|
|
242
|
-
const compilerBin = createCompilerToolchain({ projectRoot, logger });
|
|
243
|
-
const bundlerBin = createBundlerToolchain({ projectRoot, logger });
|
|
244
|
-
const routerEnabled = config.router === true;
|
|
245
|
-
const { target } = resolveBuildAdapter(config);
|
|
246
|
-
const basePath = normalizeBasePath(config.basePath || '/');
|
|
247
|
-
const routeCheckEnabled = supportsTargetRouteCheck(target);
|
|
248
|
-
const compilerOpts = {
|
|
249
|
-
typescriptDefault: config.typescriptDefault === true,
|
|
250
|
-
experimentalEmbeddedMarkup: config.embeddedMarkupExpressions === true,
|
|
251
|
-
strictDomLints: config.strictDomLints === true
|
|
252
|
-
};
|
|
253
|
-
ensureToolchainCompatibility(bundlerBin);
|
|
254
|
-
const state = {
|
|
255
|
-
versionChecked: false,
|
|
256
|
-
registry: new Map(),
|
|
257
|
-
manifest: [],
|
|
258
|
-
manifestEntryByPath: new Map(),
|
|
259
|
-
envelopeByFile: new Map(),
|
|
260
|
-
pageOnlyFastPathSignatureByFile: new Map(),
|
|
261
|
-
globalGraphHash: '',
|
|
262
|
-
pageLoopCaches: createPageLoopCaches(),
|
|
263
|
-
hasSuccessfulBuild: false,
|
|
264
|
-
imageManifest: {},
|
|
265
|
-
imageRuntimePayload: createImageRuntimePayload(config.images, {}, 'passthrough', basePath)
|
|
266
|
-
};
|
|
267
|
-
async function syncImageState(startupProfile) {
|
|
268
|
-
const { manifest } = await startupProfile.measureAsync('build_image_artifacts', () => buildImageArtifacts({
|
|
269
|
-
projectRoot,
|
|
270
|
-
outDir,
|
|
271
|
-
config: config.images
|
|
272
|
-
}));
|
|
273
|
-
state.imageManifest = manifest;
|
|
274
|
-
state.imageRuntimePayload = createImageRuntimePayload(config.images, manifest, 'passthrough', basePath);
|
|
275
|
-
await startupProfile.measureAsync('materialize_image_markup', () => materializeImageMarkupInHtmlFiles({
|
|
276
|
-
distDir: outDir,
|
|
277
|
-
payload: state.imageRuntimePayload
|
|
278
|
-
}));
|
|
279
|
-
await startupProfile.measureAsync('inject_image_runtime_payload', () => injectImageRuntimePayloadIntoHtmlFiles(outDir, state.imageRuntimePayload));
|
|
280
|
-
}
|
|
281
|
-
async function runBundlerWithCachedEnvelopes(startupProfile, activeLogger, showBundlerInfo, bundlerOptions = {}) {
|
|
282
|
-
const orderedEnvelopes = bundlerOptions.envelopesOverride
|
|
283
|
-
|| orderEnvelopes(state.manifest, resolvedPagesDir, state.envelopeByFile);
|
|
284
|
-
if (!orderedEnvelopes || orderedEnvelopes.length === 0) {
|
|
285
|
-
throw new Error('Dev rebuild cache is incomplete; full rebuild required.');
|
|
286
|
-
}
|
|
287
|
-
await startupProfile.measureAsync('run_bundler', () => runBundler(orderedEnvelopes, outDir, projectRoot, activeLogger, showBundlerInfo, bundlerBin, {
|
|
288
|
-
routeCheck: routeCheckEnabled,
|
|
289
|
-
devStableAssets: true,
|
|
290
|
-
rebuildStrategy: bundlerOptions.rebuildStrategy || 'full',
|
|
291
|
-
changedRoutes: bundlerOptions.changedRoutes || [],
|
|
292
|
-
fastPath: bundlerOptions.fastPath === true,
|
|
293
|
-
globalGraphHash: bundlerOptions.globalGraphHash || ''
|
|
294
|
-
}), { envelopes: orderedEnvelopes.length });
|
|
295
|
-
await startupProfile.measureAsync('inject_image_materialization_manifest', () => injectImageMaterializationIntoRouterManifest(outDir, orderedEnvelopes), { envelopes: orderedEnvelopes.length });
|
|
296
|
-
const assets = await startupProfile.measureAsync('collect_assets', () => collectAssets(outDir));
|
|
297
|
-
return { assets, envelopeCount: orderedEnvelopes.length };
|
|
298
|
-
}
|
|
299
|
-
async function runFullBuild(activeLogger, showBundlerInfo) {
|
|
300
|
-
const startupProfile = createStartupProfiler('cli-build');
|
|
301
|
-
const compilerTotals = createCompilerTotals();
|
|
302
|
-
await maybeRunVersionCheck({
|
|
303
|
-
state,
|
|
304
|
-
startupProfile,
|
|
305
|
-
projectRoot,
|
|
306
|
-
logger: activeLogger,
|
|
307
|
-
bundlerBin
|
|
308
|
-
});
|
|
309
|
-
state.registry = startupProfile.measureSync('build_component_registry', () => buildComponentRegistry(srcDir));
|
|
310
|
-
state.manifest = await startupProfile.measureAsync('generate_manifest', () => generateManifest(resolvedPagesDir));
|
|
311
|
-
await startupProfile.measureAsync('ensure_zenith_type_declarations', () => ensureZenithTypeDeclarations({
|
|
312
|
-
manifest: state.manifest,
|
|
313
|
-
pagesDir: resolvedPagesDir
|
|
314
|
-
}));
|
|
315
|
-
state.pageLoopCaches = createPageLoopCaches();
|
|
316
|
-
const emitCompilerWarning = buildCompilerWarningEmitter(activeLogger);
|
|
317
|
-
const { envelopes, expressionRewriteMetrics } = await buildPageEnvelopes({
|
|
318
|
-
manifest: state.manifest,
|
|
319
|
-
pagesDir: resolvedPagesDir,
|
|
320
|
-
srcDir,
|
|
321
|
-
registry: state.registry,
|
|
322
|
-
compilerOpts,
|
|
323
|
-
compilerBin,
|
|
324
|
-
routerEnabled,
|
|
325
|
-
startupProfile,
|
|
326
|
-
compilerTotals,
|
|
327
|
-
emitCompilerWarning,
|
|
328
|
-
pageLoopCaches: state.pageLoopCaches
|
|
329
|
-
});
|
|
330
|
-
state.envelopeByFile = new Map(envelopes.map((entry) => [entry.file, entry]));
|
|
331
|
-
state.manifestEntryByPath = toManifestEntryMap(state.manifest, resolvedPagesDir);
|
|
332
|
-
state.pageOnlyFastPathSignatureByFile = new Map(envelopes.map((entry) => [entry.file, buildPageOnlyFastPathSignature(entry)]));
|
|
333
|
-
state.globalGraphHash = buildGlobalGraphHash(envelopes);
|
|
334
|
-
const { assets } = await runBundlerWithCachedEnvelopes(startupProfile, activeLogger, showBundlerInfo);
|
|
335
|
-
await syncImageState(startupProfile);
|
|
336
|
-
startupProfile.emit('build_complete', {
|
|
337
|
-
pages: state.manifest.length,
|
|
338
|
-
assets: assets.length,
|
|
339
|
-
compilerTotals,
|
|
340
|
-
expressionRewriteMetrics,
|
|
341
|
-
strategy: 'full'
|
|
342
|
-
});
|
|
343
|
-
state.hasSuccessfulBuild = true;
|
|
344
|
-
return { pages: state.manifest.length, assets, strategy: 'full' };
|
|
345
|
-
}
|
|
346
|
-
async function runBundleOnlyBuild(activeLogger, showBundlerInfo) {
|
|
347
|
-
const startupProfile = createStartupProfiler('cli-build');
|
|
348
|
-
const compilerTotals = createCompilerTotals();
|
|
349
|
-
const expressionRewriteMetrics = createExpressionRewriteMetrics();
|
|
350
|
-
const { assets } = await runBundlerWithCachedEnvelopes(startupProfile, activeLogger, showBundlerInfo, { rebuildStrategy: 'bundle-only' });
|
|
351
|
-
await syncImageState(startupProfile);
|
|
352
|
-
startupProfile.emit('build_complete', {
|
|
353
|
-
pages: state.manifest.length,
|
|
354
|
-
assets: assets.length,
|
|
355
|
-
compilerTotals,
|
|
356
|
-
expressionRewriteMetrics,
|
|
357
|
-
strategy: 'bundle-only'
|
|
358
|
-
});
|
|
359
|
-
return { pages: state.manifest.length, assets, strategy: 'bundle-only' };
|
|
360
|
-
}
|
|
361
|
-
async function runPageOnlyBuild(entries, activeLogger, showBundlerInfo) {
|
|
362
|
-
const startupProfile = createStartupProfiler('cli-build');
|
|
363
|
-
const compilerTotals = createCompilerTotals();
|
|
364
|
-
const emitCompilerWarning = buildCompilerWarningEmitter(activeLogger);
|
|
365
|
-
const { envelopes, expressionRewriteMetrics } = await buildPageEnvelopes({
|
|
366
|
-
manifest: entries,
|
|
367
|
-
pagesDir: resolvedPagesDir,
|
|
368
|
-
srcDir,
|
|
369
|
-
registry: state.registry,
|
|
370
|
-
compilerOpts,
|
|
371
|
-
compilerBin,
|
|
372
|
-
routerEnabled,
|
|
373
|
-
startupProfile,
|
|
374
|
-
compilerTotals,
|
|
375
|
-
emitCompilerWarning,
|
|
376
|
-
pageLoopCaches: state.pageLoopCaches
|
|
377
|
-
});
|
|
378
|
-
const previousFastPathSignatures = new Map(state.pageOnlyFastPathSignatureByFile);
|
|
379
|
-
const canUseFastPath = entries.every((entry, index) => {
|
|
380
|
-
const previous = previousFastPathSignatures.get(resolve(resolvedPagesDir, entry.file));
|
|
381
|
-
const next = buildPageOnlyFastPathSignature(envelopes[index]);
|
|
382
|
-
return typeof previous === 'string' && previous === next;
|
|
383
|
-
});
|
|
384
|
-
for (const envelope of envelopes) {
|
|
385
|
-
state.envelopeByFile.set(envelope.file, envelope);
|
|
386
|
-
state.pageOnlyFastPathSignatureByFile.set(envelope.file, buildPageOnlyFastPathSignature(envelope));
|
|
387
|
-
}
|
|
388
|
-
const orderedEnvelopes = orderEnvelopes(state.manifest, resolvedPagesDir, state.envelopeByFile);
|
|
389
|
-
if (!orderedEnvelopes || orderedEnvelopes.length === 0) {
|
|
390
|
-
throw new Error('Dev rebuild cache is incomplete; full rebuild required.');
|
|
391
|
-
}
|
|
392
|
-
state.globalGraphHash = buildGlobalGraphHash(orderedEnvelopes);
|
|
393
|
-
const { assets } = await runBundlerWithCachedEnvelopes(startupProfile, activeLogger, showBundlerInfo, {
|
|
394
|
-
rebuildStrategy: 'page-only',
|
|
395
|
-
changedRoutes: entries.map((entry) => entry.path),
|
|
396
|
-
fastPath: canUseFastPath,
|
|
397
|
-
envelopesOverride: canUseFastPath ? envelopes : orderedEnvelopes,
|
|
398
|
-
globalGraphHash: canUseFastPath ? state.globalGraphHash : ''
|
|
399
|
-
});
|
|
400
|
-
await syncImageState(startupProfile);
|
|
401
|
-
startupProfile.emit('build_complete', {
|
|
402
|
-
pages: state.manifest.length,
|
|
403
|
-
assets: assets.length,
|
|
404
|
-
compilerTotals,
|
|
405
|
-
expressionRewriteMetrics,
|
|
406
|
-
strategy: 'page-only',
|
|
407
|
-
rebuiltPages: entries.length
|
|
408
|
-
});
|
|
409
|
-
return { pages: state.manifest.length, assets, strategy: 'page-only', rebuiltPages: entries.length };
|
|
410
|
-
}
|
|
411
|
-
return {
|
|
412
|
-
async build(buildOptions = {}) {
|
|
413
|
-
const activeLogger = buildOptions.logger || logger;
|
|
414
|
-
const showBundlerInfo = buildOptions.showBundlerInfo !== false;
|
|
415
|
-
const changedFiles = Array.isArray(buildOptions.changedFiles)
|
|
416
|
-
? [...new Set(buildOptions.changedFiles.map((entry) => resolve(String(entry))))]
|
|
417
|
-
: [];
|
|
418
|
-
if (!state.hasSuccessfulBuild || changedFiles.length === 0) {
|
|
419
|
-
return runFullBuild(activeLogger, showBundlerInfo);
|
|
420
|
-
}
|
|
421
|
-
if (isCssOnlyChange(changedFiles)) {
|
|
422
|
-
return runBundleOnlyBuild(activeLogger, showBundlerInfo);
|
|
423
|
-
}
|
|
424
|
-
const pageOnlyEntries = selectPageOnlyEntries(changedFiles, resolvedPagesDir, state.manifestEntryByPath);
|
|
425
|
-
if (pageOnlyEntries.length > 0) {
|
|
426
|
-
return runPageOnlyBuild(pageOnlyEntries, activeLogger, showBundlerInfo);
|
|
427
|
-
}
|
|
428
|
-
return runFullBuild(activeLogger, showBundlerInfo);
|
|
429
|
-
},
|
|
430
|
-
getImageRuntimePayload() {
|
|
431
|
-
return state.imageRuntimePayload;
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
}
|
|
1
|
+
export { createDevBuildSession } from './dev-build-session/session.js';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
function delay(ms) {
|
|
4
|
+
return new Promise((resolveDelay) => setTimeout(resolveDelay, ms));
|
|
5
|
+
}
|
|
6
|
+
function pickCssAsset(assets) {
|
|
7
|
+
if (!Array.isArray(assets) || assets.length === 0) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
const cssAssets = assets
|
|
11
|
+
.filter((entry) => typeof entry === 'string' && entry.endsWith('.css'))
|
|
12
|
+
.map((entry) => entry.startsWith('/') ? entry : `/${entry}`);
|
|
13
|
+
if (cssAssets.length === 0) {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
const devStable = cssAssets.find((entry) => entry.endsWith('/styles.dev.css'));
|
|
17
|
+
if (devStable) {
|
|
18
|
+
return devStable;
|
|
19
|
+
}
|
|
20
|
+
const preferred = cssAssets.find((entry) => /\/styles(\.|\/|$)/.test(entry));
|
|
21
|
+
return preferred || cssAssets[0];
|
|
22
|
+
}
|
|
23
|
+
async function waitForCssFile(absolutePath, retries = 16, delayMs = 40) {
|
|
24
|
+
for (let i = 0; i <= retries; i++) {
|
|
25
|
+
try {
|
|
26
|
+
const info = await stat(absolutePath);
|
|
27
|
+
if (info.isFile()) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// keep retrying
|
|
33
|
+
}
|
|
34
|
+
if (i < retries) {
|
|
35
|
+
await delay(delayMs);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
export async function syncCssStateFromBuild({ buildResult, nextBuildId, outDir, state, trace }) {
|
|
41
|
+
state.currentCssHref = `/__zenith_dev/styles.css?buildId=${nextBuildId}`;
|
|
42
|
+
const candidate = pickCssAsset(buildResult?.assets);
|
|
43
|
+
if (!candidate) {
|
|
44
|
+
trace('css_sync_skipped', { reason: 'no_css_asset', buildId: nextBuildId });
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const absoluteCssPath = join(outDir, candidate);
|
|
48
|
+
const ready = await waitForCssFile(absoluteCssPath);
|
|
49
|
+
if (!ready) {
|
|
50
|
+
trace('css_sync_skipped', {
|
|
51
|
+
reason: 'css_not_ready',
|
|
52
|
+
buildId: nextBuildId,
|
|
53
|
+
cssAsset: candidate,
|
|
54
|
+
resolvedPath: absoluteCssPath
|
|
55
|
+
});
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
let cssContent = '';
|
|
59
|
+
try {
|
|
60
|
+
cssContent = await readFile(absoluteCssPath, 'utf8');
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
trace('css_sync_skipped', {
|
|
64
|
+
reason: 'css_read_failed',
|
|
65
|
+
buildId: nextBuildId,
|
|
66
|
+
cssAsset: candidate,
|
|
67
|
+
resolvedPath: absoluteCssPath
|
|
68
|
+
});
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (typeof cssContent !== 'string') {
|
|
72
|
+
trace('css_sync_skipped', {
|
|
73
|
+
reason: 'css_invalid_type',
|
|
74
|
+
buildId: nextBuildId,
|
|
75
|
+
cssAsset: candidate,
|
|
76
|
+
resolvedPath: absoluteCssPath
|
|
77
|
+
});
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
if (cssContent.length === 0) {
|
|
81
|
+
trace('css_sync_skipped', {
|
|
82
|
+
reason: 'css_empty',
|
|
83
|
+
buildId: nextBuildId,
|
|
84
|
+
cssAsset: candidate,
|
|
85
|
+
resolvedPath: absoluteCssPath
|
|
86
|
+
});
|
|
87
|
+
cssContent = '/* zenith-dev: empty css */';
|
|
88
|
+
}
|
|
89
|
+
state.currentCssAssetPath = candidate;
|
|
90
|
+
state.currentCssContent = cssContent;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function classifyDevPath(pathname: any): "dev_events" | "dev_state" | "dev_styles" | "asset" | "other";
|
|
2
|
+
export function traceNotFound(trace: any, req: any, url: any, details?: {}): void;
|
|
3
|
+
export function classifyNotFound(pathname: any): "page" | "asset" | "dev_internal" | "zenith_internal";
|
|
4
|
+
export function routeFileHint(pathname: any): string;
|
|
5
|
+
export function infer404Cause(category: any, buildStatus: any): "initial build failed" | "unknown Zenith dev endpoint" | "asset not emitted by latest build" | null;
|
|
6
|
+
export function looksLikeJsonRequest(req: any, pathname: any): boolean;
|
|
7
|
+
export function buildNotFoundPayload({ pathname, category, cause, buildId, buildStatus, configuredBasePath, currentCssHref }: {
|
|
8
|
+
pathname: any;
|
|
9
|
+
category: any;
|
|
10
|
+
cause: any;
|
|
11
|
+
buildId: any;
|
|
12
|
+
buildStatus: any;
|
|
13
|
+
configuredBasePath: any;
|
|
14
|
+
currentCssHref: any;
|
|
15
|
+
}): {
|
|
16
|
+
kind: string;
|
|
17
|
+
category: any;
|
|
18
|
+
requestedPath: any;
|
|
19
|
+
buildId: any;
|
|
20
|
+
buildStatus: any;
|
|
21
|
+
cause: any;
|
|
22
|
+
};
|
|
23
|
+
export function renderNotFoundHtml(payload: any): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { stripBasePath } from '../base-path.js';
|
|
2
|
+
export function classifyDevPath(pathname) {
|
|
3
|
+
if (pathname.startsWith('/__zenith_dev/events'))
|
|
4
|
+
return 'dev_events';
|
|
5
|
+
if (pathname.startsWith('/__zenith_dev/state'))
|
|
6
|
+
return 'dev_state';
|
|
7
|
+
if (pathname.startsWith('/__zenith_dev/styles.css'))
|
|
8
|
+
return 'dev_styles';
|
|
9
|
+
if (pathname.startsWith('/assets/'))
|
|
10
|
+
return 'asset';
|
|
11
|
+
return 'other';
|
|
12
|
+
}
|
|
13
|
+
export function traceNotFound(trace, req, url, details = {}) {
|
|
14
|
+
trace('http_404', {
|
|
15
|
+
method: req.method || 'GET',
|
|
16
|
+
url: `${url.pathname}${url.search}`,
|
|
17
|
+
classify: classifyDevPath(url.pathname),
|
|
18
|
+
...details
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function classifyNotFound(pathname) {
|
|
22
|
+
const lower = String(pathname || '').toLowerCase();
|
|
23
|
+
if (lower.startsWith('/__zenith_dev/'))
|
|
24
|
+
return 'dev_internal';
|
|
25
|
+
if (lower.startsWith('/__zenith/'))
|
|
26
|
+
return 'zenith_internal';
|
|
27
|
+
if (lower.startsWith('/_assets/')
|
|
28
|
+
|| lower.startsWith('/assets/')
|
|
29
|
+
|| lower.endsWith('.css')
|
|
30
|
+
|| lower.endsWith('.js')
|
|
31
|
+
|| lower.endsWith('.map')
|
|
32
|
+
|| lower.endsWith('.json')) {
|
|
33
|
+
return 'asset';
|
|
34
|
+
}
|
|
35
|
+
return 'page';
|
|
36
|
+
}
|
|
37
|
+
export function routeFileHint(pathname) {
|
|
38
|
+
const normalized = String(pathname || '/').replace(/\/+$/, '');
|
|
39
|
+
if (normalized === '' || normalized === '/') {
|
|
40
|
+
return 'src/pages/index.zen';
|
|
41
|
+
}
|
|
42
|
+
return `src/pages${normalized}.zen`;
|
|
43
|
+
}
|
|
44
|
+
export function infer404Cause(category, buildStatus) {
|
|
45
|
+
if (category === 'dev_internal' || category === 'zenith_internal') {
|
|
46
|
+
if (buildStatus === 'error') {
|
|
47
|
+
return 'initial build failed';
|
|
48
|
+
}
|
|
49
|
+
return 'unknown Zenith dev endpoint';
|
|
50
|
+
}
|
|
51
|
+
if (category === 'asset') {
|
|
52
|
+
if (buildStatus === 'error') {
|
|
53
|
+
return 'initial build failed';
|
|
54
|
+
}
|
|
55
|
+
return 'asset not emitted by latest build';
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
export function looksLikeJsonRequest(req, pathname) {
|
|
60
|
+
const accept = String(req.headers.accept || '').toLowerCase();
|
|
61
|
+
const secFetchDest = String(req.headers['sec-fetch-dest'] || '').toLowerCase();
|
|
62
|
+
if (accept.includes('application/json') || accept.includes('application/problem+json')) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (pathname.endsWith('.json')) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return secFetchDest === 'empty';
|
|
69
|
+
}
|
|
70
|
+
export function buildNotFoundPayload({ pathname, category, cause, buildId, buildStatus, configuredBasePath, currentCssHref }) {
|
|
71
|
+
const hintedPath = category === 'page'
|
|
72
|
+
? (stripBasePath(pathname, configuredBasePath) || pathname)
|
|
73
|
+
: pathname;
|
|
74
|
+
const payload = {
|
|
75
|
+
kind: 'zenith_dev_not_found',
|
|
76
|
+
category,
|
|
77
|
+
requestedPath: pathname,
|
|
78
|
+
buildId,
|
|
79
|
+
buildStatus,
|
|
80
|
+
cause: cause || ''
|
|
81
|
+
};
|
|
82
|
+
if (category === 'asset') {
|
|
83
|
+
payload.hint = buildStatus === 'error'
|
|
84
|
+
? 'Dev server is running but initial build failed; fix compile errors and refresh.'
|
|
85
|
+
: 'Check emitted assets in dist and verify the requested path.';
|
|
86
|
+
if (pathname.endsWith('.css')) {
|
|
87
|
+
payload.expectedCssHref = currentCssHref || null;
|
|
88
|
+
payload.hint = buildStatus === 'error'
|
|
89
|
+
? `Dev server is running but initial build failed; expected CSS at ${currentCssHref || '<none>'}.`
|
|
90
|
+
: `Requested CSS is missing; expected current href ${currentCssHref || '<none>'}.`;
|
|
91
|
+
}
|
|
92
|
+
return payload;
|
|
93
|
+
}
|
|
94
|
+
if (category === 'dev_internal' || category === 'zenith_internal') {
|
|
95
|
+
payload.hint = buildStatus === 'error'
|
|
96
|
+
? 'Dev server is running but initial build failed; restart after fixing compile errors.'
|
|
97
|
+
: 'Check Zenith dev endpoint path and dev client version.';
|
|
98
|
+
payload.docsLink = '/docs/documentation/contracts/hmr-v1-contract.md';
|
|
99
|
+
return payload;
|
|
100
|
+
}
|
|
101
|
+
const routeFile = routeFileHint(hintedPath);
|
|
102
|
+
payload.routeFile = routeFile;
|
|
103
|
+
payload.cause = `no route file found at ${routeFile}`;
|
|
104
|
+
payload.hint = `Create ${routeFile} or verify router manifest output.`;
|
|
105
|
+
return payload;
|
|
106
|
+
}
|
|
107
|
+
export function renderNotFoundHtml(payload) {
|
|
108
|
+
const escaped = (value) => String(value || '')
|
|
109
|
+
.replaceAll('&', '&')
|
|
110
|
+
.replaceAll('<', '<')
|
|
111
|
+
.replaceAll('>', '>');
|
|
112
|
+
const details = [
|
|
113
|
+
`Requested: ${payload.requestedPath}`,
|
|
114
|
+
`Category: ${payload.category}`,
|
|
115
|
+
`Build: ${payload.buildStatus} (id=${payload.buildId})`,
|
|
116
|
+
`Cause: ${payload.cause}`,
|
|
117
|
+
payload.expectedCssHref ? `Expected CSS href: ${payload.expectedCssHref}` : '',
|
|
118
|
+
`Hint: ${payload.hint || 'Inspect dev server output.'}`,
|
|
119
|
+
payload.docsLink ? `Docs: ${payload.docsLink}` : ''
|
|
120
|
+
].filter(Boolean).join('\n');
|
|
121
|
+
return [
|
|
122
|
+
'<!DOCTYPE html>',
|
|
123
|
+
'<html><head><meta charset="utf-8"><title>Zenith Dev 404</title></head>',
|
|
124
|
+
'<body style="font-family: ui-monospace, SFMono-Regular, Menlo, monospace; padding: 20px; background: #101216; color: #e6edf3;">',
|
|
125
|
+
'<h1 style="margin-top:0;">Zenith Dev 404</h1>',
|
|
126
|
+
`<pre style="white-space: pre-wrap; line-height: 1.5;">${escaped(details)}</pre>`,
|
|
127
|
+
'</body></html>'
|
|
128
|
+
].join('');
|
|
129
|
+
}
|