@vmz/vmz 0.0.4 → 0.1.1
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/dist/build-assemble.d.ts +52 -0
- package/dist/build-assemble.js +192 -0
- package/dist/cdn-policy.d.ts +22 -4
- package/dist/cdn-policy.js +109 -13
- package/dist/cli.js +122 -27
- package/dist/content-addressed-assets.js +2 -1
- package/dist/delivery-profile.d.ts +84 -0
- package/dist/delivery-profile.js +348 -0
- package/dist/dev-session.d.ts +6 -0
- package/dist/dev-session.js +167 -40
- package/dist/document-build.js +33 -32
- package/dist/document-cmd.js +2 -9
- package/dist/document-enrich.js +8 -0
- package/dist/document-integrate.js +10 -17
- package/dist/embedded-packaging.d.ts +22 -0
- package/dist/embedded-packaging.js +109 -0
- package/dist/index.d.ts +26 -1
- package/dist/index.js +69 -2
- package/dist/locale-check.js +39 -66
- package/dist/locale-cmd.js +12 -44
- package/dist/locale-route-emit.d.ts +37 -0
- package/dist/locale-route-emit.js +109 -0
- package/dist/locale-router.d.ts +20 -0
- package/dist/locale-router.js +68 -0
- package/dist/log.d.ts +2 -2
- package/dist/log.js +11 -3
- package/dist/mini-host.d.ts +47 -0
- package/dist/mini-host.js +202 -0
- package/dist/native-addon.d.ts +9 -0
- package/dist/native-addon.js +84 -0
- package/dist/pack-client-packages.d.ts +25 -0
- package/dist/pack-client-packages.js +399 -0
- package/dist/pack.d.ts +58 -0
- package/dist/pack.js +123 -0
- package/dist/plugin-host.d.ts +1 -1
- package/dist/plugin-host.js +2 -2
- package/dist/pretty-json.d.ts +19 -0
- package/dist/pretty-json.js +43 -0
- package/dist/production-observability.js +3 -2
- package/dist/production-test-pack.d.ts +0 -14
- package/dist/production-test-pack.js +27 -31
- package/dist/release-pack.js +17 -21
- package/dist/route-path.d.ts +35 -0
- package/dist/route-path.js +77 -0
- package/dist/server-artifact.d.ts +140 -0
- package/dist/server-artifact.js +204 -0
- package/dist/server-language-backend.d.ts +89 -0
- package/dist/server-language-backend.js +121 -0
- package/dist/site-delivery.js +3 -2
- package/dist/static-emit.d.ts +10 -1
- package/dist/static-emit.js +192 -97
- package/dist/test-cmd.js +2 -1
- package/dist/wechat-packaging.d.ts +22 -0
- package/dist/wechat-packaging.js +59 -0
- package/package.json +12 -12
package/dist/dev-session.js
CHANGED
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Rebuilds go through the N-API `Workspace` — never spawn `cargo` / `vmz-tools`.
|
|
6
6
|
* session: only dirty leaves are marked; Workspace emits affected deployment units.
|
|
7
|
+
*
|
|
8
|
+
* Reload policy (Vite-like — author never hand-restarts):
|
|
9
|
+
* 1. Prefer in-process soft reload (`POST /__vmz/reload`) with transitive `?t=` via serve-host hook.
|
|
10
|
+
* 2. Soft reload only re-imports affected pages unless shared `lib/` / full rebuild.
|
|
11
|
+
* 3. Soft reload failure → **auto-respawn** serve-host (fresh ESM graph), not "keep broken host".
|
|
7
12
|
*/
|
|
8
13
|
import { spawn } from 'node:child_process';
|
|
9
14
|
import { existsSync } from 'node:fs';
|
|
10
15
|
import path from 'node:path';
|
|
11
16
|
import { buildIntegratedDocuments, projectHasDocuments } from './document-integrate.js';
|
|
12
|
-
import { createWorkspace } from './index.js';
|
|
17
|
+
import { createWorkspace, resolveNativePath } from './index.js';
|
|
13
18
|
import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
|
|
14
19
|
import { log } from './log.js';
|
|
15
20
|
import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
@@ -20,6 +25,7 @@ import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
|
20
25
|
* @property {string} [host]
|
|
21
26
|
* @property {number} [port]
|
|
22
27
|
* @property {number} [pollMs]
|
|
28
|
+
* @property {'browser' | 'mini-program-wechat'} [target]
|
|
23
29
|
* @property {AbortSignal} [signal]
|
|
24
30
|
* @property {typeof createWorkspace} [createWorkspaceFn]
|
|
25
31
|
* @property {(opts: { project: string, outDir: string, host: string, port: number }) => import('node:child_process').ChildProcess} [spawnHostFn]
|
|
@@ -34,6 +40,7 @@ export function createDevSession(options) {
|
|
|
34
40
|
const host = options.host ?? '127.0.0.1';
|
|
35
41
|
const port = options.port ?? 5173;
|
|
36
42
|
const pollMs = Math.max(50, options.pollMs ?? 300);
|
|
43
|
+
const wechatPreview = options.target === 'mini-program-wechat';
|
|
37
44
|
const createWs = options.createWorkspaceFn ?? createWorkspace;
|
|
38
45
|
const spawnHost = options.spawnHostFn ?? defaultSpawnHost;
|
|
39
46
|
const softReload = options.softReloadFn ?? defaultSoftReload;
|
|
@@ -51,8 +58,8 @@ export function createDevSession(options) {
|
|
|
51
58
|
}
|
|
52
59
|
function emitLocales() {
|
|
53
60
|
const localeEmit = emitLocaleRuntimeModules(project, outDir);
|
|
61
|
+
log.diagnostics(localeEmit.diagnostics ?? []);
|
|
54
62
|
if (!localeEmit.ok || localeHasErrors({ diagnostics: localeEmit.diagnostics })) {
|
|
55
|
-
log.diagnostics(localeEmit.diagnostics ?? []);
|
|
56
63
|
log.error('locale runtime emit failed');
|
|
57
64
|
return false;
|
|
58
65
|
}
|
|
@@ -71,13 +78,90 @@ export function createDevSession(options) {
|
|
|
71
78
|
log.info(`${label} ok (${mode}; chunks=[${chunks}]; ${(report.emitted ?? []).length} emitted)`);
|
|
72
79
|
return true;
|
|
73
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Rewrite `dist/wechat` so WeChat DevTools (already open) recompiles WXSS.
|
|
83
|
+
* Preview is vendor compile, not the browser serve-host.
|
|
84
|
+
*/
|
|
85
|
+
function packWechatPreview() {
|
|
86
|
+
if (typeof ws.lowerMiniprogramWechatPackaging !== 'function') {
|
|
87
|
+
log.error('wechat pack: workspace missing lowerMiniprogramWechatPackaging');
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
let report;
|
|
91
|
+
try {
|
|
92
|
+
const raw = ws.lowerMiniprogramWechatPackaging();
|
|
93
|
+
report = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
log.error(`wechat pack failed: ${err}`);
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
log.diagnostics(report.diagnostics ?? []);
|
|
100
|
+
if (report.status !== 'ready') {
|
|
101
|
+
log.error(`wechat pack ${report.status || 'failed'}`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const packRoot = report.packRoot || 'dist/wechat';
|
|
105
|
+
log.info(`wechat pack ok → ${path.join(project, packRoot)} (WeChat DevTools compiles WXSS here)`);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
async function waitHostReady(timeoutMs = 8000) {
|
|
109
|
+
const start = Date.now();
|
|
110
|
+
while (Date.now() - start < timeoutMs) {
|
|
111
|
+
try {
|
|
112
|
+
const res = await fetch(`http://${host}:${port}/__vmz/ready`);
|
|
113
|
+
if (res.ok)
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
/* not up yet */
|
|
118
|
+
}
|
|
119
|
+
await sleep(50);
|
|
120
|
+
}
|
|
121
|
+
throw new Error(`serve-host not ready on ${host}:${port}`);
|
|
122
|
+
}
|
|
123
|
+
async function waitHostGone(timeoutMs = 3000) {
|
|
124
|
+
const start = Date.now();
|
|
125
|
+
while (Date.now() - start < timeoutMs) {
|
|
126
|
+
try {
|
|
127
|
+
await fetch(`http://${host}:${port}/__vmz/health`);
|
|
128
|
+
await sleep(40);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function respawnHost(reason) {
|
|
136
|
+
log.warn(`respawning serve-host (${reason})…`);
|
|
137
|
+
killChild(child);
|
|
138
|
+
child = null;
|
|
139
|
+
await waitHostGone();
|
|
140
|
+
child = spawnHost({ project, outDir, host, port });
|
|
141
|
+
await waitHostReady();
|
|
142
|
+
log.info('serve-host respawned — browser SSE will reconnect and reload');
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Soft reload first; on failure auto-respawn so authors never hand-restart.
|
|
146
|
+
* @param {object} payload
|
|
147
|
+
*/
|
|
148
|
+
async function reloadAfterBuild(payload) {
|
|
149
|
+
try {
|
|
150
|
+
await softReload(host, port, payload);
|
|
151
|
+
return 'soft';
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
log.warn(`soft reload failed (${err}) — auto-respawning serve-host`);
|
|
155
|
+
await respawnHost('soft-reload-failed');
|
|
156
|
+
return 'respawn';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
74
159
|
async function start() {
|
|
75
160
|
const src = path.join(project, 'src');
|
|
76
161
|
if (!existsSync(src)) {
|
|
77
162
|
throw new Error(`vmz dev: missing src/ under ${project}`);
|
|
78
163
|
}
|
|
79
164
|
log.info('initial build (N-API workspace, full)…');
|
|
80
|
-
// Empty dirty → full project build (session).
|
|
81
165
|
const initial = rebuild();
|
|
82
166
|
if (!printReport(initial, 'build')) {
|
|
83
167
|
throw new Error('vmz dev: initial build failed');
|
|
@@ -88,17 +172,32 @@ export function createDevSession(options) {
|
|
|
88
172
|
throw new Error('vmz dev: integrated document build failed');
|
|
89
173
|
}
|
|
90
174
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
175
|
+
if (wechatPreview) {
|
|
176
|
+
if (!packWechatPreview()) {
|
|
177
|
+
throw new Error('vmz dev: wechat pack failed');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const hostJs = path.join(outDir, 'vmz-serve-host.mjs');
|
|
182
|
+
if (!existsSync(hostJs)) {
|
|
183
|
+
throw new Error(`vmz dev: missing ${hostJs}`);
|
|
184
|
+
}
|
|
185
|
+
child = spawnHost({ project, outDir, host, port });
|
|
186
|
+
await waitHostReady().catch((err) => {
|
|
187
|
+
log.warn(String(err));
|
|
188
|
+
});
|
|
94
189
|
}
|
|
95
|
-
child = spawnHost({ project, outDir, host, port });
|
|
96
190
|
const docsRoot = path.join(project, 'documents');
|
|
97
191
|
const localesRoot = path.join(project, 'locales');
|
|
98
192
|
const watchRoots = [src].concat(existsSync(docsRoot) ? [docsRoot] : []).concat(existsSync(localesRoot) ? [localesRoot] : []);
|
|
99
|
-
|
|
193
|
+
if (wechatPreview) {
|
|
194
|
+
log.info(`dev → WeChat DevTools (watching ${watchRoots.join(', ')}; keep dist/wechat open)`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
log.info(`dev → http://${host}:${port} (watching ${watchRoots.join(', ')})`);
|
|
198
|
+
}
|
|
100
199
|
/** @type {Map<string, Map<string, string>>} */
|
|
101
|
-
|
|
200
|
+
const fingerprints = new Map();
|
|
102
201
|
for (const root of watchRoots) {
|
|
103
202
|
fingerprints.set(root, fileFingerprintMap(root));
|
|
104
203
|
}
|
|
@@ -107,21 +206,35 @@ export function createDevSession(options) {
|
|
|
107
206
|
void stop();
|
|
108
207
|
};
|
|
109
208
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
209
|
+
/**
|
|
210
|
+
* Wait until src/ stops changing (multi-file agent edits).
|
|
211
|
+
*/
|
|
212
|
+
async function coalesceSrcBurst() {
|
|
213
|
+
let guard = 0;
|
|
214
|
+
while (guard++ < 20) {
|
|
215
|
+
await sleep(220);
|
|
216
|
+
const prev = fingerprints.get(src) || new Map();
|
|
217
|
+
const next = fileFingerprintMap(src);
|
|
218
|
+
const diff = diffFingerprints(prev, next);
|
|
219
|
+
if (!diff.changed.length && !diff.deleted.length)
|
|
220
|
+
break;
|
|
221
|
+
fingerprints.set(src, next);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
110
224
|
try {
|
|
111
225
|
while (!stopped && !signal?.aborted) {
|
|
112
226
|
await sleep(pollMs);
|
|
113
227
|
if (stopped || signal?.aborted)
|
|
114
228
|
break;
|
|
115
|
-
if (child && child.exitCode != null) {
|
|
229
|
+
if (!wechatPreview && child && child.exitCode != null) {
|
|
116
230
|
log.warn(`serve-host exited (${child.exitCode}) — respawning…`);
|
|
117
231
|
child = spawnHost({ project, outDir, host, port });
|
|
232
|
+
await waitHostReady().catch(() => { });
|
|
118
233
|
continue;
|
|
119
234
|
}
|
|
120
235
|
/** @type {{ srcChanged: string[], srcDeleted: string[], docsDirty: boolean, localesDirty: boolean }} */
|
|
121
236
|
let batch = { srcChanged: [], srcDeleted: [], docsDirty: false, localesDirty: false };
|
|
122
237
|
try {
|
|
123
|
-
// Probe only — keep prior fingerprints until debounce resample
|
|
124
|
-
// (same contract as pre-docs watcher: empty second pass would miss soft reload).
|
|
125
238
|
for (const root of watchRoots) {
|
|
126
239
|
const prev = fingerprints.get(root) || new Map();
|
|
127
240
|
const next = fileFingerprintMap(root);
|
|
@@ -145,8 +258,12 @@ export function createDevSession(options) {
|
|
|
145
258
|
}
|
|
146
259
|
if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty && !batch.localesDirty)
|
|
147
260
|
continue;
|
|
148
|
-
|
|
149
|
-
|
|
261
|
+
if (batch.srcChanged.length + batch.srcDeleted.length > 1) {
|
|
262
|
+
await coalesceSrcBurst();
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
await sleep(200);
|
|
266
|
+
}
|
|
150
267
|
batch = { srcChanged: [], srcDeleted: [], docsDirty: false, localesDirty: false };
|
|
151
268
|
for (const root of watchRoots) {
|
|
152
269
|
const prev = fingerprints.get(root) || new Map();
|
|
@@ -180,7 +297,6 @@ export function createDevSession(options) {
|
|
|
180
297
|
continue;
|
|
181
298
|
}
|
|
182
299
|
if (batch.docsDirty || projectHasDocuments(project)) {
|
|
183
|
-
// App rebuild may refresh designs CSS consumed by documents.
|
|
184
300
|
const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
|
|
185
301
|
if (!docs.ok) {
|
|
186
302
|
log.warn('document mount rebuild failed — keeping previous docs');
|
|
@@ -189,22 +305,26 @@ export function createDevSession(options) {
|
|
|
189
305
|
needFullReload = true;
|
|
190
306
|
}
|
|
191
307
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
308
|
+
if (wechatPreview) {
|
|
309
|
+
if (!packWechatPreview()) {
|
|
310
|
+
log.warn('wechat pack failed — keeping previous dist/wechat');
|
|
311
|
+
}
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const kind = await reloadAfterBuild({
|
|
315
|
+
affectedChunks: report.affectedChunks ?? [],
|
|
316
|
+
seedChunks: report.seedChunks ?? [],
|
|
317
|
+
emitted: report.emitted ?? [],
|
|
318
|
+
full: Boolean(report.full) || needFullReload,
|
|
319
|
+
islandHmr: Boolean(report.islandHmr) && !needFullReload,
|
|
320
|
+
});
|
|
321
|
+
log.info(kind === 'respawn'
|
|
322
|
+
? 'reload ok (respawned serve-host)'
|
|
323
|
+
: needFullReload
|
|
200
324
|
? 'soft reload ok (full page; docs)'
|
|
201
325
|
: report.islandHmr
|
|
202
326
|
? 'soft reload ok (island HMR)'
|
|
203
|
-
: 'soft reload ok
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
log.warn(`soft reload failed (${err}) — keeping previous serve-host (fix and save)`);
|
|
207
|
-
}
|
|
327
|
+
: 'soft reload ok');
|
|
208
328
|
continue;
|
|
209
329
|
}
|
|
210
330
|
if (batch.localesDirty) {
|
|
@@ -213,15 +333,19 @@ export function createDevSession(options) {
|
|
|
213
333
|
log.warn('locale runtime emit failed — keeping previous modules');
|
|
214
334
|
continue;
|
|
215
335
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
336
|
+
if (wechatPreview) {
|
|
337
|
+
if (!packWechatPreview()) {
|
|
338
|
+
log.warn('wechat pack failed — keeping previous dist/wechat');
|
|
339
|
+
}
|
|
340
|
+
if (!batch.docsDirty)
|
|
341
|
+
continue;
|
|
219
342
|
}
|
|
220
|
-
|
|
221
|
-
|
|
343
|
+
else {
|
|
344
|
+
const kind = await reloadAfterBuild({ full: true, islandHmr: false, emitted: [] });
|
|
345
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; locales)' : 'soft reload ok (full page; locales)');
|
|
346
|
+
if (!batch.docsDirty)
|
|
347
|
+
continue;
|
|
222
348
|
}
|
|
223
|
-
if (!batch.docsDirty)
|
|
224
|
-
continue;
|
|
225
349
|
}
|
|
226
350
|
if (batch.docsDirty) {
|
|
227
351
|
log.info('documents change detected — rebuilding document mount…');
|
|
@@ -230,12 +354,14 @@ export function createDevSession(options) {
|
|
|
230
354
|
log.warn('document mount rebuild failed — keeping previous docs');
|
|
231
355
|
continue;
|
|
232
356
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
357
|
+
if (wechatPreview) {
|
|
358
|
+
if (!packWechatPreview()) {
|
|
359
|
+
log.warn('wechat pack failed — keeping previous dist/wechat');
|
|
360
|
+
}
|
|
236
361
|
}
|
|
237
|
-
|
|
238
|
-
|
|
362
|
+
else {
|
|
363
|
+
const kind = await reloadAfterBuild({ full: true, islandHmr: false, emitted: [] });
|
|
364
|
+
log.info(kind === 'respawn' ? 'reload ok (respawned; docs)' : 'soft reload ok (full page; docs)');
|
|
239
365
|
}
|
|
240
366
|
}
|
|
241
367
|
}
|
|
@@ -287,6 +413,7 @@ function defaultSpawnHost(opts) {
|
|
|
287
413
|
VMZ_PORT: String(opts.port),
|
|
288
414
|
VMZ_HOST: opts.host,
|
|
289
415
|
VMZ_DEV: '1',
|
|
416
|
+
VMZ_NATIVE_NODE: resolveNativePath(),
|
|
290
417
|
},
|
|
291
418
|
stdio: ['ignore', 'inherit', 'inherit'],
|
|
292
419
|
});
|
package/dist/document-build.js
CHANGED
|
@@ -12,6 +12,8 @@ import { artifactHrefFromHtml, buildDocumentIslands, buildDocumentSearch, collec
|
|
|
12
12
|
import { resolveMarkdownEngine } from './document-markdown.js';
|
|
13
13
|
import { DOCUMENT_VIEW_SCHEMA } from './document-schema.js';
|
|
14
14
|
import { createWorkspace } from './index.js';
|
|
15
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
16
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
15
17
|
/**
|
|
16
18
|
* @param {{ projectRoot: string, outDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
17
19
|
*/
|
|
@@ -113,7 +115,7 @@ export async function buildDocuments(opts) {
|
|
|
113
115
|
const viewRel = path.posix.join('views', page.identity.locale, `${page.identity.pageKey === 'index' ? 'index' : page.identity.pageKey}.view.json`);
|
|
114
116
|
const viewAbs = path.join(outDir, viewRel);
|
|
115
117
|
fs.mkdirSync(path.dirname(viewAbs), { recursive: true });
|
|
116
|
-
|
|
118
|
+
writePrettyJsonFile(viewAbs, view);
|
|
117
119
|
const html = renderStaticHtml({
|
|
118
120
|
title: info.title,
|
|
119
121
|
locale: page.identity.locale,
|
|
@@ -147,10 +149,10 @@ export async function buildDocuments(opts) {
|
|
|
147
149
|
islands: 'document.islands.json',
|
|
148
150
|
},
|
|
149
151
|
};
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
writePrettyJsonFile(path.join(outDir, 'document.manifest.json'), manifestOut);
|
|
153
|
+
writePrettyJsonFile(path.join(outDir, 'document.evidence.json'), evidence.evidence);
|
|
154
|
+
writePrettyJsonFile(path.join(outDir, 'document.search.json'), search);
|
|
155
|
+
writePrettyJsonFile(path.join(outDir, 'document.islands.json'), islands);
|
|
154
156
|
return { ok: true, manifest: manifestOut, outDir, pages: written, search, islands };
|
|
155
157
|
}
|
|
156
158
|
/**
|
|
@@ -164,12 +166,12 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
|
|
|
164
166
|
/** @type {string[]} */
|
|
165
167
|
const cssHrefs = [];
|
|
166
168
|
if (hostChrome) {
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
+
// Integrated documents are served with pretty directory URLs. Root
|
|
170
|
+
// absolute assets remain correct for both emitted files and rewrites.
|
|
171
|
+
cssHrefs.push('/vmz.css');
|
|
169
172
|
}
|
|
170
173
|
if (designsHref)
|
|
171
|
-
cssHrefs.push(prefix + designsHref);
|
|
172
|
-
const cssLink = cssHrefs.map((href) => ` <link rel="stylesheet" href="${esc(href)}" />`).join('\n') + (cssHrefs.length ? '\n' : '');
|
|
174
|
+
cssHrefs.push(hostChrome ? `/${designsHref}` : prefix + designsHref);
|
|
173
175
|
const navItems = nav
|
|
174
176
|
.map((n) => {
|
|
175
177
|
const href = relativeHref(htmlRel, n.href, route);
|
|
@@ -187,51 +189,50 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
|
|
|
187
189
|
${navItems}
|
|
188
190
|
</ul>
|
|
189
191
|
</nav>`;
|
|
192
|
+
/** @type {string} */
|
|
193
|
+
let bodyInner;
|
|
190
194
|
if (hostChrome) {
|
|
191
195
|
const header = hostChrome.header.replace(/(<a\s+href="\/d\/?")([^>]*>文档<\/a>)/, '$1 aria-current="page"$2');
|
|
192
|
-
|
|
193
|
-
<html lang="${esc(locale)}">
|
|
194
|
-
<head>
|
|
195
|
-
<meta charset="utf-8" />
|
|
196
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
197
|
-
<title>${esc(title)}</title>
|
|
198
|
-
${cssLink}</head>
|
|
199
|
-
<body data-vmz-hydrate="island-only">
|
|
200
|
-
<div class="site site--docs">
|
|
196
|
+
bodyInner = ` <div class="site site--docs">
|
|
201
197
|
<a class="skip-link" href="#main">Skip to content</a>
|
|
202
198
|
${header}
|
|
199
|
+
<div class="doc-body">
|
|
200
|
+
<aside class="doc-sidebar">
|
|
203
201
|
${docsNav}
|
|
204
202
|
${searchShellHtml}
|
|
205
|
-
|
|
203
|
+
</aside>
|
|
204
|
+
<div class="doc-content">
|
|
206
205
|
${toc}<main id="main">
|
|
207
206
|
${bodyHtml}
|
|
208
207
|
${playgroundShellHtml}
|
|
209
208
|
</main>
|
|
209
|
+
</div>
|
|
210
210
|
</div>
|
|
211
211
|
${hostChrome.footer}
|
|
212
212
|
</div>
|
|
213
|
-
</body>
|
|
214
|
-
</html>
|
|
215
213
|
`;
|
|
216
214
|
}
|
|
217
|
-
|
|
218
|
-
<
|
|
219
|
-
<head>
|
|
220
|
-
<meta charset="utf-8" />
|
|
221
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
222
|
-
<title>${esc(title)}</title>
|
|
223
|
-
${cssLink}</head>
|
|
224
|
-
<body data-vmz-hydrate="island-only">
|
|
225
|
-
<a class="skip-link" href="#main">Skip to content</a>
|
|
215
|
+
else {
|
|
216
|
+
bodyInner = ` <a class="skip-link" href="#main">Skip to content</a>
|
|
226
217
|
${docsNav}
|
|
227
218
|
${searchShellHtml}
|
|
228
219
|
${toc}<main id="main">
|
|
229
220
|
${bodyHtml}
|
|
230
221
|
${playgroundShellHtml}
|
|
231
222
|
</main>
|
|
232
|
-
</body>
|
|
233
|
-
</html>
|
|
234
223
|
`;
|
|
224
|
+
}
|
|
225
|
+
const native = requireNativeAddon();
|
|
226
|
+
if (typeof native.generateHtmlShell !== 'function') {
|
|
227
|
+
throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
|
|
228
|
+
}
|
|
229
|
+
return native.generateHtmlShell({
|
|
230
|
+
title,
|
|
231
|
+
lang: locale,
|
|
232
|
+
cssHrefs,
|
|
233
|
+
bodyHtml: bodyInner,
|
|
234
|
+
bodyAttrs: ['data-vmz-hydrate', 'island-only'],
|
|
235
|
+
});
|
|
235
236
|
}
|
|
236
237
|
/**
|
|
237
238
|
* Integrated DocumentMount: reuse host SiteHeader / SiteFooter .vmz templates.
|
package/dist/document-cmd.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* `vmz document` / `vmz docs` CLI .
|
|
4
4
|
*/
|
|
5
|
-
import fs from 'node:fs';
|
|
6
5
|
import path from 'node:path';
|
|
7
6
|
import { buildDocuments } from './document-build.js';
|
|
8
7
|
import { checkDocuments, manifestHasErrors } from './document-check.js';
|
|
@@ -12,6 +11,7 @@ import { resolveMarkdownEngine } from './document-markdown.js';
|
|
|
12
11
|
import { createWorkspace } from './index.js';
|
|
13
12
|
import { log } from './log.js';
|
|
14
13
|
import { parseArgs } from './cli.js';
|
|
14
|
+
import { emitPrettyJson } from './pretty-json.js';
|
|
15
15
|
function printDocumentHelp() {
|
|
16
16
|
console.log(`vmz document — project /documents domain
|
|
17
17
|
|
|
@@ -95,14 +95,7 @@ async function cmdDocumentCheck(args) {
|
|
|
95
95
|
}
|
|
96
96
|
const jsonOut = args.json;
|
|
97
97
|
if (jsonOut) {
|
|
98
|
-
|
|
99
|
-
if (typeof jsonOut === 'string') {
|
|
100
|
-
fs.writeFileSync(jsonOut, text + '\n', 'utf8');
|
|
101
|
-
log.info(`wrote ${jsonOut}`);
|
|
102
|
-
}
|
|
103
|
-
else {
|
|
104
|
-
console.log(text);
|
|
105
|
-
}
|
|
98
|
+
emitPrettyJson(jsonOut, manifest, { logWrote: (p) => log.info(`wrote ${p}`) });
|
|
106
99
|
}
|
|
107
100
|
else {
|
|
108
101
|
for (const d of manifest.diagnostics) {
|
package/dist/document-enrich.js
CHANGED
|
@@ -199,6 +199,14 @@ function resolveDocHref(href, fromPageKey, locale, routeBase, pageKeySet) {
|
|
|
199
199
|
pk = normalizePageKey(pk);
|
|
200
200
|
const keys = pageKeySet.get(locale) || new Set();
|
|
201
201
|
if (!keys.has(pk)) {
|
|
202
|
+
// A directory index and a leaf page share the same normalized PageKey shape.
|
|
203
|
+
// Prefer the regular sibling resolution above, then retry relative to the
|
|
204
|
+
// PageKey itself so `guide/optimizations/index.md` keeps its directory.
|
|
205
|
+
const indexJoined = path.posix.normalize(path.posix.join(fromPageKey || '.', pathPart));
|
|
206
|
+
const indexPk = normalizePageKey(indexJoined.replace(/^\.\//, ''));
|
|
207
|
+
if (keys.has(indexPk)) {
|
|
208
|
+
return { ok: true, locale, pageKey: indexPk, anchor, anchors: [] };
|
|
209
|
+
}
|
|
202
210
|
return { ok: false, reason: `no PageKey ${pk} in ${locale}` };
|
|
203
211
|
}
|
|
204
212
|
return { ok: true, locale, pageKey: pk, anchor, anchors: [] };
|
|
@@ -8,6 +8,7 @@ import path from 'node:path';
|
|
|
8
8
|
import { buildDocuments } from './document-build.js';
|
|
9
9
|
import { resolveDocumentsRoot } from './document-check.js';
|
|
10
10
|
import { log } from './log.js';
|
|
11
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
11
12
|
/**
|
|
12
13
|
* @param {string} projectRoot
|
|
13
14
|
*/
|
|
@@ -66,23 +67,15 @@ function writeMountRootRedirects(manifest, outDir) {
|
|
|
66
67
|
const relDir = base.replace(/^\//, '');
|
|
67
68
|
const abs = path.join(outDir, relDir, 'index.html');
|
|
68
69
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<p><a href="${escapeAttr(target)}">Continue to ${escapeAttr(defaultLocale)} docs</a></p>
|
|
79
|
-
</body>
|
|
80
|
-
</html>
|
|
81
|
-
`;
|
|
70
|
+
const native = requireNativeAddon();
|
|
71
|
+
if (typeof native.generateRedirectHtml !== 'function') {
|
|
72
|
+
throw new Error('vmz native addon missing generateRedirectHtml — rebuild with `pnpm napi:build`');
|
|
73
|
+
}
|
|
74
|
+
const html = native.generateRedirectHtml({
|
|
75
|
+
lang: defaultLocale,
|
|
76
|
+
target,
|
|
77
|
+
title: 'Documents',
|
|
78
|
+
});
|
|
82
79
|
fs.writeFileSync(abs, html, 'utf8');
|
|
83
80
|
}
|
|
84
81
|
}
|
|
85
|
-
/** @param {string} s */
|
|
86
|
-
function escapeAttr(s) {
|
|
87
|
-
return String(s).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
|
88
|
-
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rust-embedded packaging adapter: resource index + baseline closure.
|
|
3
|
+
* Packaging only — does not invent route / MIME / fallback semantics.
|
|
4
|
+
*/
|
|
5
|
+
export declare const EMBEDDED_RESOURCE_INDEX_SCHEMA = "vmz.embedded.resource_index.v0";
|
|
6
|
+
/**
|
|
7
|
+
* Walk outDir and build path → digest → relative blob path map.
|
|
8
|
+
* Copies files into `dist/_vmz/embedded-baseline/` (whole release, no file-level mix).
|
|
9
|
+
* @param {string} outDir
|
|
10
|
+
* @param {{ siteId?: string, contractDigest?: string | null }} [opts]
|
|
11
|
+
*/
|
|
12
|
+
export declare function emitEmbeddedPackaging(outDir: any, opts?: {}): {
|
|
13
|
+
index: {
|
|
14
|
+
schema: string;
|
|
15
|
+
siteId: any;
|
|
16
|
+
contractDigest: any;
|
|
17
|
+
objectCount: number;
|
|
18
|
+
objects: any[];
|
|
19
|
+
};
|
|
20
|
+
indexPath: string;
|
|
21
|
+
baselineDir: string;
|
|
22
|
+
};
|