@vmz/vmz 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/build-assemble.js +3 -4
  2. package/dist/cdn-policy.js +4 -5
  3. package/dist/cli.js +15 -50
  4. package/dist/content-addressed-assets.d.ts +2 -8
  5. package/dist/content-addressed-assets.js +34 -110
  6. package/dist/delivery-profile.d.ts +1 -11
  7. package/dist/delivery-profile.js +1 -70
  8. package/dist/dev-session.d.ts +0 -1
  9. package/dist/dev-session.js +64 -220
  10. package/dist/document-build.js +34 -61
  11. package/dist/document-cmd.js +9 -2
  12. package/dist/document-enrich.d.ts +1 -2
  13. package/dist/document-enrich.js +4 -15
  14. package/dist/document-integrate.js +21 -28
  15. package/dist/embedded-packaging.js +1 -2
  16. package/dist/index.d.ts +1 -15
  17. package/dist/index.js +3 -58
  18. package/dist/locale-check.js +61 -28
  19. package/dist/locale-cmd.js +42 -10
  20. package/dist/locale-route-emit.d.ts +1 -4
  21. package/dist/locale-route-emit.js +32 -7
  22. package/dist/locale-router.js +8 -26
  23. package/dist/pack.js +2 -3
  24. package/dist/production-observability.js +2 -3
  25. package/dist/production-test-pack.js +3 -4
  26. package/dist/release-pack.js +18 -5
  27. package/dist/server-artifact.js +3 -4
  28. package/dist/site-delivery.js +3 -4
  29. package/dist/static-emit.js +78 -116
  30. package/dist/test-cmd.js +1 -2
  31. package/package.json +12 -12
  32. package/dist/dev-watch-roots.d.ts +0 -67
  33. package/dist/dev-watch-roots.js +0 -220
  34. package/dist/document-host-chrome.d.ts +0 -28
  35. package/dist/document-host-chrome.js +0 -128
  36. package/dist/native-addon.d.ts +0 -9
  37. package/dist/native-addon.js +0 -84
  38. package/dist/pretty-json.d.ts +0 -19
  39. package/dist/pretty-json.js +0 -43
  40. package/dist/route-path.d.ts +0 -35
  41. package/dist/route-path.js +0 -77
  42. package/dist/wechat-packaging.d.ts +0 -22
  43. package/dist/wechat-packaging.js +0 -59
@@ -14,10 +14,9 @@ import { spawn } from 'node:child_process';
14
14
  import { existsSync } from 'node:fs';
15
15
  import path from 'node:path';
16
16
  import { buildIntegratedDocuments, projectHasDocuments } from './document-integrate.js';
17
- import { createWorkspace, resolveNativePath } from './index.js';
17
+ import { createWorkspace } from './index.js';
18
18
  import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
19
19
  import { log } from './log.js';
20
- import { coalesceRootBurst, collectDevWatchRoots, isDependencyPath, mergeDirtySets } from './dev-watch-roots.js';
21
20
  import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
22
21
  /**
23
22
  * @typedef {object} DevSessionOptions
@@ -26,7 +25,6 @@ import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
26
25
  * @property {string} [host]
27
26
  * @property {number} [port]
28
27
  * @property {number} [pollMs]
29
- * @property {'browser' | 'mini-program-wechat'} [target]
30
28
  * @property {AbortSignal} [signal]
31
29
  * @property {typeof createWorkspace} [createWorkspaceFn]
32
30
  * @property {(opts: { project: string, outDir: string, host: string, port: number }) => import('node:child_process').ChildProcess} [spawnHostFn]
@@ -41,7 +39,6 @@ export function createDevSession(options) {
41
39
  const host = options.host ?? '127.0.0.1';
42
40
  const port = options.port ?? 5173;
43
41
  const pollMs = Math.max(50, options.pollMs ?? 300);
44
- const wechatPreview = options.target === 'mini-program-wechat';
45
42
  const createWs = options.createWorkspaceFn ?? createWorkspace;
46
43
  const spawnHost = options.spawnHostFn ?? defaultSpawnHost;
47
44
  const softReload = options.softReloadFn ?? defaultSoftReload;
@@ -79,33 +76,6 @@ export function createDevSession(options) {
79
76
  log.info(`${label} ok (${mode}; chunks=[${chunks}]; ${(report.emitted ?? []).length} emitted)`);
80
77
  return true;
81
78
  }
82
- /**
83
- * Rewrite `dist/wechat` so WeChat DevTools (already open) recompiles WXSS.
84
- * Preview is vendor compile, not the browser serve-host.
85
- */
86
- function packWechatPreview() {
87
- if (typeof ws.lowerMiniprogramWechatPackaging !== 'function') {
88
- log.error('wechat pack: workspace missing lowerMiniprogramWechatPackaging');
89
- return false;
90
- }
91
- let report;
92
- try {
93
- const raw = ws.lowerMiniprogramWechatPackaging();
94
- report = typeof raw === 'string' ? JSON.parse(raw) : raw;
95
- }
96
- catch (err) {
97
- log.error(`wechat pack failed: ${err}`);
98
- return false;
99
- }
100
- log.diagnostics(report.diagnostics ?? []);
101
- if (report.status !== 'ready') {
102
- log.error(`wechat pack ${report.status || 'failed'}`);
103
- return false;
104
- }
105
- const packRoot = report.packRoot || 'dist/wechat';
106
- log.info(`wechat pack ok → ${path.join(project, packRoot)} (WeChat DevTools compiles WXSS here)`);
107
- return true;
108
- }
109
79
  async function waitHostReady(timeoutMs = 8000) {
110
80
  const start = Date.now();
111
81
  while (Date.now() - start < timeoutMs) {
@@ -173,37 +143,18 @@ export function createDevSession(options) {
173
143
  throw new Error('vmz dev: integrated document build failed');
174
144
  }
175
145
  }
176
- if (wechatPreview) {
177
- if (!packWechatPreview()) {
178
- throw new Error('vmz dev: wechat pack failed');
179
- }
180
- }
181
- else {
182
- const hostJs = path.join(outDir, 'vmz-serve-host.mjs');
183
- if (!existsSync(hostJs)) {
184
- throw new Error(`vmz dev: missing ${hostJs}`);
185
- }
186
- child = spawnHost({ project, outDir, host, port });
187
- await waitHostReady().catch((err) => {
188
- log.warn(String(err));
189
- });
146
+ const hostJs = path.join(outDir, 'vmz-serve-host.mjs');
147
+ if (!existsSync(hostJs)) {
148
+ throw new Error(`vmz dev: missing ${hostJs}`);
190
149
  }
150
+ child = spawnHost({ project, outDir, host, port });
151
+ await waitHostReady().catch((err) => {
152
+ log.warn(String(err));
153
+ });
191
154
  const docsRoot = path.join(project, 'documents');
192
155
  const localesRoot = path.join(project, 'locales');
193
- const watched = collectDevWatchRoots({ project, outDir });
194
- /** @type {string[]} */
195
- const watchRoots = [...watched.roots];
196
- /** @type {string[]} */
197
- let dependencyRoots = [...watched.dependencyRoots];
198
- if (wechatPreview) {
199
- log.info(`dev → WeChat DevTools (watching ${watchRoots.join(', ')}; keep dist/wechat open)`);
200
- }
201
- else {
202
- log.info(`dev → http://${host}:${port} (watching ${watchRoots.join(', ')})`);
203
- }
204
- if (dependencyRoots.length) {
205
- log.info(`dev dependency watch roots (${dependencyRoots.length}): ${dependencyRoots.join(', ')}`);
206
- }
156
+ const watchRoots = [src].concat(existsSync(docsRoot) ? [docsRoot] : []).concat(existsSync(localesRoot) ? [localesRoot] : []);
157
+ log.info(`dev http://${host}:${port} (watching ${watchRoots.join(', ')})`);
207
158
  /** @type {Map<string, Map<string, string>>} */
208
159
  const fingerprints = new Map();
209
160
  for (const root of watchRoots) {
@@ -215,48 +166,18 @@ export function createDevSession(options) {
215
166
  };
216
167
  signal?.addEventListener('abort', onAbort, { once: true });
217
168
  /**
218
- * Scan all watch roots into a batch. Does not update fingerprints.
169
+ * Wait until src/ stops changing (multi-file agent edits).
219
170
  */
220
- function scanBatch() {
221
- /** @type {{ srcChanged: string[], srcDeleted: string[], depChanged: string[], depDeleted: string[], docsDirty: boolean, localesDirty: boolean }} */
222
- const batch = {
223
- srcChanged: [],
224
- srcDeleted: [],
225
- depChanged: [],
226
- depDeleted: [],
227
- docsDirty: false,
228
- localesDirty: false,
229
- };
230
- for (const root of watchRoots) {
231
- const prev = fingerprints.get(root) || new Map();
232
- const next = fileFingerprintMap(root);
171
+ async function coalesceSrcBurst() {
172
+ let guard = 0;
173
+ while (guard++ < 20) {
174
+ await sleep(220);
175
+ const prev = fingerprints.get(src) || new Map();
176
+ const next = fileFingerprintMap(src);
233
177
  const diff = diffFingerprints(prev, next);
234
178
  if (!diff.changed.length && !diff.deleted.length)
235
- continue;
236
- if (root === src) {
237
- batch.srcChanged.push(...diff.changed);
238
- batch.srcDeleted.push(...diff.deleted);
239
- }
240
- else if (root === localesRoot) {
241
- batch.localesDirty = true;
242
- }
243
- else if (root === docsRoot) {
244
- batch.docsDirty = true;
245
- }
246
- else if (dependencyRoots.includes(root)) {
247
- batch.depChanged.push(...diff.changed);
248
- batch.depDeleted.push(...diff.deleted);
249
- }
250
- else {
251
- // designs or other application roots → treat like docs (full reload)
252
- batch.docsDirty = true;
253
- }
254
- }
255
- return batch;
256
- }
257
- function commitFingerprints() {
258
- for (const root of watchRoots) {
259
- fingerprints.set(root, fileFingerprintMap(root));
179
+ break;
180
+ fingerprints.set(src, next);
260
181
  }
261
182
  }
262
183
  try {
@@ -264,118 +185,64 @@ export function createDevSession(options) {
264
185
  await sleep(pollMs);
265
186
  if (stopped || signal?.aborted)
266
187
  break;
267
- if (!wechatPreview && child && child.exitCode != null) {
188
+ if (child && child.exitCode != null) {
268
189
  log.warn(`serve-host exited (${child.exitCode}) — respawning…`);
269
190
  child = spawnHost({ project, outDir, host, port });
270
191
  await waitHostReady().catch(() => { });
271
192
  continue;
272
193
  }
273
- let batch;
194
+ /** @type {{ srcChanged: string[], srcDeleted: string[], docsDirty: boolean, localesDirty: boolean }} */
195
+ let batch = { srcChanged: [], srcDeleted: [], docsDirty: false, localesDirty: false };
274
196
  try {
275
- batch = scanBatch();
197
+ for (const root of watchRoots) {
198
+ const prev = fingerprints.get(root) || new Map();
199
+ const next = fileFingerprintMap(root);
200
+ const diff = diffFingerprints(prev, next);
201
+ if (root === src) {
202
+ batch.srcChanged = diff.changed;
203
+ batch.srcDeleted = diff.deleted;
204
+ }
205
+ else if (root === localesRoot) {
206
+ if (diff.changed.length || diff.deleted.length)
207
+ batch.localesDirty = true;
208
+ }
209
+ else if (diff.changed.length || diff.deleted.length) {
210
+ batch.docsDirty = true;
211
+ }
212
+ }
276
213
  }
277
214
  catch (err) {
278
215
  log.warn('watch error:', err);
279
216
  continue;
280
217
  }
281
- const srcDirty = batch.srcChanged.length + batch.srcDeleted.length;
282
- const depDirty = batch.depChanged.length + batch.depDeleted.length;
283
- if (!srcDirty && !depDirty && !batch.docsDirty && !batch.localesDirty)
218
+ if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty && !batch.localesDirty)
284
219
  continue;
285
- // Coalesce multi-file bursts without dropping the initial dirty set.
286
- if (srcDirty > 1) {
287
- const coalesced = await coalesceRootBurst(src, fingerprints, {
288
- changed: batch.srcChanged,
289
- deleted: batch.srcDeleted,
290
- });
291
- batch.srcChanged = coalesced.changed;
292
- batch.srcDeleted = coalesced.deleted;
220
+ if (batch.srcChanged.length + batch.srcDeleted.length > 1) {
221
+ await coalesceSrcBurst();
293
222
  }
294
- if (depDirty > 1) {
295
- // Coalesce each dirty dependency root independently, then merge.
296
- /** @type {{ changed: string[], deleted: string[] }} */
297
- let acc = { changed: [...batch.depChanged], deleted: [...batch.depDeleted] };
298
- for (const root of dependencyRoots) {
299
- const prev = fingerprints.get(root) || new Map();
300
- const next = fileFingerprintMap(root);
301
- const peek = diffFingerprints(prev, next);
302
- if (peek.changed.length + peek.deleted.length <= 1 && !acc.changed.some((f) => isDependencyPath(f, [root]))) {
303
- continue;
304
- }
305
- const coalesced = await coalesceRootBurst(root, fingerprints, {
306
- changed: acc.changed.filter((f) => isDependencyPath(f, [root])),
307
- deleted: acc.deleted.filter((f) => isDependencyPath(f, [root])),
308
- });
309
- const others = {
310
- changed: acc.changed.filter((f) => !isDependencyPath(f, [root])),
311
- deleted: acc.deleted.filter((f) => !isDependencyPath(f, [root])),
312
- };
313
- acc = mergeDirtySets(others, coalesced);
314
- }
315
- batch.depChanged = acc.changed;
316
- batch.depDeleted = acc.deleted;
317
- }
318
- else if (srcDirty === 1 || depDirty === 1) {
223
+ else {
319
224
  await sleep(200);
320
225
  }
321
- // Refresh residual dirty after settle (without discarding coalesced sets).
322
- const residual = scanBatch();
323
- {
324
- const srcMerged = mergeDirtySets({ changed: batch.srcChanged, deleted: batch.srcDeleted }, { changed: residual.srcChanged, deleted: residual.srcDeleted });
325
- batch.srcChanged = srcMerged.changed;
326
- batch.srcDeleted = srcMerged.deleted;
327
- const depMerged = mergeDirtySets({ changed: batch.depChanged, deleted: batch.depDeleted }, { changed: residual.depChanged, deleted: residual.depDeleted });
328
- batch.depChanged = depMerged.changed;
329
- batch.depDeleted = depMerged.deleted;
330
- batch.docsDirty = batch.docsDirty || residual.docsDirty;
331
- batch.localesDirty = batch.localesDirty || residual.localesDirty;
332
- }
333
- commitFingerprints();
334
- if (!batch.srcChanged.length &&
335
- !batch.srcDeleted.length &&
336
- !batch.depChanged.length &&
337
- !batch.depDeleted.length &&
338
- !batch.docsDirty &&
339
- !batch.localesDirty) {
340
- continue;
341
- }
342
- // Dependency changes: conservative full rebuild + full reload (v0.1.5).
343
- if (batch.depChanged.length || batch.depDeleted.length) {
344
- log.info(`dependency change detected (${batch.depChanged.length} update, ${batch.depDeleted.length} delete) — full rebuild…`);
345
- const report = rebuild();
346
- if (!printReport(report, 'rebuild')) {
347
- log.warn('rebuild failed — keeping previous server');
348
- continue;
226
+ batch = { srcChanged: [], srcDeleted: [], docsDirty: false, localesDirty: false };
227
+ for (const root of watchRoots) {
228
+ const prev = fingerprints.get(root) || new Map();
229
+ const next = fileFingerprintMap(root);
230
+ const diff = diffFingerprints(prev, next);
231
+ fingerprints.set(root, next);
232
+ if (root === src) {
233
+ batch.srcChanged = diff.changed;
234
+ batch.srcDeleted = diff.deleted;
349
235
  }
350
- // Refresh watch roots in case the graph gained new packages.
351
- const refreshed = collectDevWatchRoots({ project, outDir });
352
- for (const r of refreshed.roots) {
353
- if (!watchRoots.includes(r)) {
354
- watchRoots.push(r);
355
- fingerprints.set(r, fileFingerprintMap(r));
356
- }
357
- }
358
- dependencyRoots = [...refreshed.dependencyRoots];
359
- if (batch.docsDirty || projectHasDocuments(project)) {
360
- const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
361
- if (!docs.ok)
362
- log.warn('document mount rebuild failed — keeping previous docs');
236
+ else if (root === localesRoot) {
237
+ if (diff.changed.length || diff.deleted.length)
238
+ batch.localesDirty = true;
363
239
  }
364
- if (wechatPreview) {
365
- if (!packWechatPreview())
366
- log.warn('wechat pack failed — keeping previous dist/wechat');
367
- continue;
240
+ else if (diff.changed.length || diff.deleted.length) {
241
+ batch.docsDirty = true;
368
242
  }
369
- const kind = await reloadAfterBuild({
370
- affectedChunks: report.affectedChunks ?? [],
371
- seedChunks: report.seedChunks ?? [],
372
- emitted: report.emitted ?? [],
373
- full: true,
374
- islandHmr: false,
375
- });
376
- log.info(kind === 'respawn' ? 'reload ok (respawned; deps)' : 'soft reload ok (full page; deps)');
377
- continue;
378
243
  }
244
+ if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty && !batch.localesDirty)
245
+ continue;
379
246
  let needFullReload = batch.docsDirty;
380
247
  if (batch.srcChanged.length || batch.srcDeleted.length) {
381
248
  log.info(`change detected (${batch.srcChanged.length} update, ${batch.srcDeleted.length} delete) — affected rebuild…`);
@@ -397,12 +264,6 @@ export function createDevSession(options) {
397
264
  needFullReload = true;
398
265
  }
399
266
  }
400
- if (wechatPreview) {
401
- if (!packWechatPreview()) {
402
- log.warn('wechat pack failed — keeping previous dist/wechat');
403
- }
404
- continue;
405
- }
406
267
  const kind = await reloadAfterBuild({
407
268
  affectedChunks: report.affectedChunks ?? [],
408
269
  seedChunks: report.seedChunks ?? [],
@@ -425,19 +286,10 @@ export function createDevSession(options) {
425
286
  log.warn('locale runtime emit failed — keeping previous modules');
426
287
  continue;
427
288
  }
428
- if (wechatPreview) {
429
- if (!packWechatPreview()) {
430
- log.warn('wechat pack failed — keeping previous dist/wechat');
431
- }
432
- if (!batch.docsDirty)
433
- continue;
434
- }
435
- else {
436
- const kind = await reloadAfterBuild({ full: true, islandHmr: false, emitted: [] });
437
- log.info(kind === 'respawn' ? 'reload ok (respawned; locales)' : 'soft reload ok (full page; locales)');
438
- if (!batch.docsDirty)
439
- continue;
440
- }
289
+ const kind = await reloadAfterBuild({ full: true, islandHmr: false, emitted: [] });
290
+ log.info(kind === 'respawn' ? 'reload ok (respawned; locales)' : 'soft reload ok (full page; locales)');
291
+ if (!batch.docsDirty)
292
+ continue;
441
293
  }
442
294
  if (batch.docsDirty) {
443
295
  log.info('documents change detected — rebuilding document mount…');
@@ -446,15 +298,8 @@ export function createDevSession(options) {
446
298
  log.warn('document mount rebuild failed — keeping previous docs');
447
299
  continue;
448
300
  }
449
- if (wechatPreview) {
450
- if (!packWechatPreview()) {
451
- log.warn('wechat pack failed — keeping previous dist/wechat');
452
- }
453
- }
454
- else {
455
- const kind = await reloadAfterBuild({ full: true, islandHmr: false, emitted: [] });
456
- log.info(kind === 'respawn' ? 'reload ok (respawned; docs)' : 'soft reload ok (full page; docs)');
457
- }
301
+ const kind = await reloadAfterBuild({ full: true, islandHmr: false, emitted: [] });
302
+ log.info(kind === 'respawn' ? 'reload ok (respawned; docs)' : 'soft reload ok (full page; docs)');
458
303
  }
459
304
  }
460
305
  }
@@ -505,7 +350,6 @@ function defaultSpawnHost(opts) {
505
350
  VMZ_PORT: String(opts.port),
506
351
  VMZ_HOST: opts.host,
507
352
  VMZ_DEV: '1',
508
- VMZ_NATIVE_NODE: resolveNativePath(),
509
353
  },
510
354
  stdio: ['ignore', 'inherit', 'inherit'],
511
355
  });
@@ -8,13 +8,10 @@ import { checkDocuments, manifestHasErrors } from './document-check.js';
8
8
  import { resolveDocumentDesignsCss } from './document-designs.js';
9
9
  import { enrichDocumentContent, pageHtmlRel } from './document-enrich.js';
10
10
  import { enrichDocumentEvidence } from './document-evidence.js';
11
- import { docsRouteNone, loadLocaleCommonMessages, loadLocalesRouting, localeNonePickerScript, readSiteGithubUrl, renderHostChromeTemplate, } from './document-host-chrome.js';
12
11
  import { artifactHrefFromHtml, buildDocumentIslands, buildDocumentSearch, collectFenceBodies, renderIslandShellsHtml, } from './document-interactive.js';
13
12
  import { resolveMarkdownEngine } from './document-markdown.js';
14
13
  import { DOCUMENT_VIEW_SCHEMA } from './document-schema.js';
15
14
  import { createWorkspace } from './index.js';
16
- import { requireNativeAddon } from './native-addon.js';
17
- import { writePrettyJsonFile } from './pretty-json.js';
18
15
  /**
19
16
  * @param {{ projectRoot: string, outDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
20
17
  */
@@ -23,12 +20,10 @@ export async function buildDocuments(opts) {
23
20
  const outDir = path.resolve(opts.outDir || path.join(projectRoot, 'dist', 'documents'));
24
21
  const strict = Boolean(opts.strict);
25
22
  const manifest = checkDocuments({ projectRoot, strict });
26
- const routing = loadLocalesRouting(projectRoot) || { strategy: 'prefix' };
27
23
  const engine = await resolveMarkdownEngine({ engines: opts.engines, projectRoot });
28
24
  const enriched = enrichDocumentContent(manifest, {
29
25
  analyzeMarkdown: engine.analyzeMarkdown,
30
26
  projectRoot,
31
- routing,
32
27
  });
33
28
  manifest.diagnostics = enriched.diagnostics;
34
29
  const evidence = await enrichDocumentEvidence(manifest, {
@@ -63,8 +58,8 @@ export async function buildDocuments(opts) {
63
58
  });
64
59
  manifest.search = search;
65
60
  manifest.islands = islands;
66
- const hostChromeRaw = resolveHostSiteChromeRaw(projectRoot);
67
- const useHostShell = Boolean(hostChromeRaw) && (manifest.mounts || []).some((m) => m.mode === 'integrated');
61
+ const hostChrome = resolveHostSiteChrome(projectRoot);
62
+ const useHostShell = Boolean(hostChrome) && (manifest.mounts || []).some((m) => m.mode === 'integrated');
68
63
  fs.mkdirSync(outDir, { recursive: true });
69
64
  const designs = resolveDocumentDesignsCss(projectRoot);
70
65
  /** @type {string | null} */
@@ -118,7 +113,7 @@ export async function buildDocuments(opts) {
118
113
  const viewRel = path.posix.join('views', page.identity.locale, `${page.identity.pageKey === 'index' ? 'index' : page.identity.pageKey}.view.json`);
119
114
  const viewAbs = path.join(outDir, viewRel);
120
115
  fs.mkdirSync(path.dirname(viewAbs), { recursive: true });
121
- writePrettyJsonFile(viewAbs, view);
116
+ fs.writeFileSync(viewAbs, JSON.stringify(view, null, 2) + '\n', 'utf8');
122
117
  const html = renderStaticHtml({
123
118
  title: info.title,
124
119
  locale: page.identity.locale,
@@ -130,12 +125,7 @@ export async function buildDocuments(opts) {
130
125
  htmlRel,
131
126
  searchShellHtml: shells.searchHtml,
132
127
  playgroundShellHtml: shells.playgroundHtml,
133
- hostChrome: useHostShell
134
- ? renderHostChromeForLocale(projectRoot, page.identity.locale, routing, enriched.routeBase, hostChromeRaw)
135
- : null,
136
- routing,
137
- routeBase: enriched.routeBase,
138
- pageKey: page.identity.pageKey,
128
+ hostChrome: useHostShell ? hostChrome : null,
139
129
  });
140
130
  fs.writeFileSync(htmlAbs, html, 'utf8');
141
131
  written.push({ route: info.route, htmlPath: htmlRel, viewPath: viewRel });
@@ -157,17 +147,17 @@ export async function buildDocuments(opts) {
157
147
  islands: 'document.islands.json',
158
148
  },
159
149
  };
160
- writePrettyJsonFile(path.join(outDir, 'document.manifest.json'), manifestOut);
161
- writePrettyJsonFile(path.join(outDir, 'document.evidence.json'), evidence.evidence);
162
- writePrettyJsonFile(path.join(outDir, 'document.search.json'), search);
163
- writePrettyJsonFile(path.join(outDir, 'document.islands.json'), islands);
150
+ fs.writeFileSync(path.join(outDir, 'document.manifest.json'), JSON.stringify(manifestOut, null, 2) + '\n', 'utf8');
151
+ fs.writeFileSync(path.join(outDir, 'document.evidence.json'), JSON.stringify(evidence.evidence, null, 2) + '\n', 'utf8');
152
+ fs.writeFileSync(path.join(outDir, 'document.search.json'), JSON.stringify(search, null, 2) + '\n', 'utf8');
153
+ fs.writeFileSync(path.join(outDir, 'document.islands.json'), JSON.stringify(islands, null, 2) + '\n', 'utf8');
164
154
  return { ok: true, manifest: manifestOut, outDir, pages: written, search, islands };
165
155
  }
166
156
  /**
167
157
  * No-JS readable static HTML: nav + main landmarks, Island shells without scripts.
168
158
  * Integrated mounts reuse host SiteHeader/SiteFooter templates when present.
169
159
  */
170
- function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, designsHref, htmlRel, searchShellHtml = '', playgroundShellHtml = '', hostChrome = null, routing = { strategy: 'prefix' }, routeBase = '/docs', pageKey = 'index', }) {
160
+ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, designsHref, htmlRel, searchShellHtml = '', playgroundShellHtml = '', hostChrome = null, }) {
171
161
  const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
172
162
  const depth = htmlRel.split('/').length - 1;
173
163
  const prefix = depth > 0 ? '../'.repeat(depth) : './';
@@ -180,9 +170,10 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
180
170
  }
181
171
  if (designsHref)
182
172
  cssHrefs.push(hostChrome ? `/${designsHref}` : prefix + designsHref);
173
+ const cssLink = cssHrefs.map((href) => ` <link rel="stylesheet" href="${esc(href)}" />`).join('\n') + (cssHrefs.length ? '\n' : '');
183
174
  const navItems = nav
184
175
  .map((n) => {
185
- const href = routing.strategy === 'none' || routing.strategy === 'domain' ? n.href : relativeHref(htmlRel, n.href, route);
176
+ const href = relativeHref(htmlRel, n.href, route);
186
177
  const current = n.href === route ? ' aria-current="page"' : '';
187
178
  return ` <li><a href="${esc(href)}"${current}>${esc(n.title)}</a></li>`;
188
179
  })
@@ -197,11 +188,17 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
197
188
  ${navItems}
198
189
  </ul>
199
190
  </nav>`;
200
- /** @type {string} */
201
- let bodyInner;
202
191
  if (hostChrome) {
203
192
  const header = hostChrome.header.replace(/(<a\s+href="\/d\/?")([^>]*>文档<\/a>)/, '$1 aria-current="page"$2');
204
- bodyInner = ` <div class="site site--docs">
193
+ return `<!DOCTYPE html>
194
+ <html lang="${esc(locale)}">
195
+ <head>
196
+ <meta charset="utf-8" />
197
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
198
+ <title>${esc(title)}</title>
199
+ ${cssLink}</head>
200
+ <body data-vmz-hydrate="island-only">
201
+ <div class="site site--docs">
205
202
  <a class="skip-link" href="#main">Skip to content</a>
206
203
  ${header}
207
204
  <div class="doc-body">
@@ -218,37 +215,35 @@ ${playgroundShellHtml}
218
215
  </div>
219
216
  ${hostChrome.footer}
220
217
  </div>
221
- ${routing.strategy === 'none' && hostChrome ? localeNonePickerScript() : ''}
218
+ </body>
219
+ </html>
222
220
  `;
223
221
  }
224
- else {
225
- bodyInner = ` <a class="skip-link" href="#main">Skip to content</a>
222
+ return `<!DOCTYPE html>
223
+ <html lang="${esc(locale)}">
224
+ <head>
225
+ <meta charset="utf-8" />
226
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
227
+ <title>${esc(title)}</title>
228
+ ${cssLink}</head>
229
+ <body data-vmz-hydrate="island-only">
230
+ <a class="skip-link" href="#main">Skip to content</a>
226
231
  ${docsNav}
227
232
  ${searchShellHtml}
228
233
  ${toc}<main id="main">
229
234
  ${bodyHtml}
230
235
  ${playgroundShellHtml}
231
236
  </main>
237
+ </body>
238
+ </html>
232
239
  `;
233
- }
234
- const native = requireNativeAddon();
235
- if (typeof native.generateHtmlShell !== 'function') {
236
- throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
237
- }
238
- return native.generateHtmlShell({
239
- title,
240
- lang: locale,
241
- cssHrefs,
242
- bodyHtml: bodyInner,
243
- bodyAttrs: ['data-vmz-hydrate', 'island-only'],
244
- });
245
240
  }
246
241
  /**
247
242
  * Integrated DocumentMount: reuse host SiteHeader / SiteFooter .vmz templates.
248
243
  * @param {string} projectRoot
249
244
  * @returns {{ header: string, footer: string } | null}
250
245
  */
251
- function resolveHostSiteChromeRaw(projectRoot) {
246
+ function resolveHostSiteChrome(projectRoot) {
252
247
  const headerPath = path.join(projectRoot, 'src', 'components', 'SiteHeader.vmz');
253
248
  const footerPath = path.join(projectRoot, 'src', 'components', 'SiteFooter.vmz');
254
249
  if (!fs.existsSync(headerPath) || !fs.existsSync(footerPath))
@@ -259,28 +254,6 @@ function resolveHostSiteChromeRaw(projectRoot) {
259
254
  return null;
260
255
  return { header, footer };
261
256
  }
262
- /**
263
- * @param {string} projectRoot
264
- * @param {string} localeId
265
- * @param {{ strategy?: string }} routing
266
- * @param {string} routeBase
267
- * @param {{ header: string, footer: string }} raw
268
- */
269
- function renderHostChromeForLocale(projectRoot, localeId, routing, routeBase, raw) {
270
- const messages = loadLocaleCommonMessages(projectRoot, localeId);
271
- const githubUrl = readSiteGithubUrl(projectRoot);
272
- const docsRootHref = routing.strategy === 'none' || routing.strategy === 'domain'
273
- ? `${routeBase.replace(/\/$/, '')}/`
274
- : `${routeBase.replace(/\/$/, '')}/${localeId}/`;
275
- const guideHref = routing.strategy === 'none' || routing.strategy === 'domain'
276
- ? docsRouteNone(routeBase, 'guide/getting-started')
277
- : `${docsRootHref}guide/getting-started`;
278
- const opts = { docsRootHref, guideHref, githubUrl, routing };
279
- return {
280
- header: renderHostChromeTemplate(raw.header, localeId, messages, opts),
281
- footer: renderHostChromeTemplate(raw.footer, localeId, messages, opts),
282
- };
283
- }
284
257
  /** @param {string} filePath */
285
258
  function extractVmzTemplateHtml(filePath) {
286
259
  const src = fs.readFileSync(filePath, 'utf8');
@@ -2,6 +2,7 @@
2
2
  /**
3
3
  * `vmz document` / `vmz docs` CLI .
4
4
  */
5
+ import fs from 'node:fs';
5
6
  import path from 'node:path';
6
7
  import { buildDocuments } from './document-build.js';
7
8
  import { checkDocuments, manifestHasErrors } from './document-check.js';
@@ -11,7 +12,6 @@ import { resolveMarkdownEngine } from './document-markdown.js';
11
12
  import { createWorkspace } from './index.js';
12
13
  import { log } from './log.js';
13
14
  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,7 +95,14 @@ async function cmdDocumentCheck(args) {
95
95
  }
96
96
  const jsonOut = args.json;
97
97
  if (jsonOut) {
98
- emitPrettyJson(jsonOut, manifest, { logWrote: (p) => log.info(`wrote ${p}`) });
98
+ const text = JSON.stringify(manifest, null, 2);
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
+ }
99
106
  }
100
107
  else {
101
108
  for (const d of manifest.diagnostics) {
@@ -2,9 +2,8 @@
2
2
  * @param {string} routeBase e.g. /docs
3
3
  * @param {string} locale
4
4
  * @param {string} pageKey
5
- * @param {{ strategy?: string }} [routing]
6
5
  */
7
- export declare function pageRoute(routeBase: any, locale: any, pageKey: any, routing?: {}): string;
6
+ export declare function pageRoute(routeBase: any, locale: any, pageKey: any): string;
8
7
  /**
9
8
  * Static file path relative to out dir (posix).
10
9
  * @param {string} routeBase