@vmz/vmz 0.1.12 → 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 (46) 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 -111
  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 +65 -268
  10. package/dist/document-build.d.ts +1 -2
  11. package/dist/document-build.js +82 -104
  12. package/dist/document-cmd.js +9 -2
  13. package/dist/document-enrich.d.ts +1 -2
  14. package/dist/document-enrich.js +4 -15
  15. package/dist/document-integrate.js +21 -29
  16. package/dist/embedded-packaging.js +1 -2
  17. package/dist/index.d.ts +1 -15
  18. package/dist/index.js +3 -59
  19. package/dist/locale-check.js +61 -28
  20. package/dist/locale-cmd.js +42 -10
  21. package/dist/locale-route-emit.d.ts +1 -4
  22. package/dist/locale-route-emit.js +32 -7
  23. package/dist/locale-router.js +8 -26
  24. package/dist/pack.js +2 -3
  25. package/dist/production-observability.js +2 -3
  26. package/dist/production-test-pack.js +3 -4
  27. package/dist/release-pack.js +18 -5
  28. package/dist/server-artifact.js +3 -4
  29. package/dist/site-delivery.js +3 -4
  30. package/dist/static-emit.js +97 -118
  31. package/dist/test-cmd.js +1 -2
  32. package/package.json +12 -12
  33. package/dist/dev-watch-roots.d.ts +0 -80
  34. package/dist/dev-watch-roots.js +0 -245
  35. package/dist/document-layout-render.d.ts +0 -20
  36. package/dist/document-layout-render.js +0 -87
  37. package/dist/document-routing-config.d.ts +0 -13
  38. package/dist/document-routing-config.js +0 -40
  39. package/dist/native-addon.d.ts +0 -9
  40. package/dist/native-addon.js +0 -84
  41. package/dist/pretty-json.d.ts +0 -19
  42. package/dist/pretty-json.js +0 -43
  43. package/dist/route-path.d.ts +0 -35
  44. package/dist/route-path.js +0 -77
  45. package/dist/wechat-packaging.d.ts +0 -22
  46. package/dist/wechat-packaging.js +0 -59
@@ -4,10 +4,9 @@
4
4
  */
5
5
  // @ts-nocheck
6
6
  import crypto from 'node:crypto';
7
- import { existsSync, mkdirSync, readFileSync } from 'node:fs';
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { SERVER_RUNTIMES } from './delivery-profile.js';
10
- import { writePrettyJsonFile } from './pretty-json.js';
11
10
  export const SERVER_ARTIFACT_SCHEMA = 'vmz.server.artifact.v0';
12
11
  export const HTTP_CONTRACT_SCHEMA = 'vmz.http.contract.v0';
13
12
  export const SERVER_RUNTIME_ADAPTER_SCHEMA = 'vmz.server.runtime_adapter.v0';
@@ -117,14 +116,14 @@ export function emitServerArtifact(outDir, opts = {}) {
117
116
  const vmzDir = path.join(outDir, '_vmz');
118
117
  mkdirSync(vmzDir, { recursive: true });
119
118
  const file = path.join(vmzDir, 'server-artifact.json');
120
- writePrettyJsonFile(file, artifact);
119
+ writeFileSync(file, `${JSON.stringify(artifact, null, 2)}\n`, 'utf8');
121
120
  const adapterDir = path.join(vmzDir, 'adapters');
122
121
  mkdirSync(adapterDir, { recursive: true });
123
122
  for (const adapterId of ['worker', 'rust-host']) {
124
123
  const projection = projectServerRuntimeAdapter(artifact, adapterId);
125
124
  const dir = path.join(adapterDir, adapterId);
126
125
  mkdirSync(dir, { recursive: true });
127
- writePrettyJsonFile(path.join(dir, 'adapter.json'), projection);
126
+ writeFileSync(path.join(dir, 'adapter.json'), `${JSON.stringify(projection, null, 2)}\n`, 'utf8');
128
127
  }
129
128
  return { artifact, path: file, httpContractDigest };
130
129
  }
@@ -6,7 +6,6 @@
6
6
  import crypto from 'node:crypto';
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
- import { writePrettyJsonFile } from './pretty-json.js';
10
9
  export const SITE_DELIVERY_CONTRACT_SCHEMA = 'vmz.site.delivery_contract.v0';
11
10
  export const SITE_DELIVERY_RESOLUTION_SCHEMA = 'vmz.site.delivery_resolution.v0';
12
11
  /**
@@ -136,7 +135,7 @@ export function normalizeSiteDelivery(raw, opts = {}) {
136
135
  artifact: String(d.artifact),
137
136
  expectedCompatibility: d.expectedCompatibility || {
138
137
  runtime: 'vmz',
139
- deliveryProfiles: ['static', 'filesystem'],
138
+ deliveryProfiles: ['web-static', 'filesystem'],
140
139
  },
141
140
  sources,
142
141
  resolutionPolicy: {
@@ -319,11 +318,11 @@ export function emitSiteDelivery(outDir, deliveryRaw, opts = {}) {
319
318
  }
320
319
  const vmzDir = path.join(outDir, '_vmz');
321
320
  fs.mkdirSync(vmzDir, { recursive: true });
322
- writePrettyJsonFile(path.join(vmzDir, 'site-delivery-contract.json'), norm.contract);
321
+ fs.writeFileSync(path.join(vmzDir, 'site-delivery-contract.json'), `${JSON.stringify(norm.contract, null, 2)}\n`, 'utf8');
323
322
  let resolution = null;
324
323
  if (opts.probes) {
325
324
  resolution = resolveSiteRelease(norm.contract, opts.probes);
326
- writePrettyJsonFile(path.join(vmzDir, 'site-delivery-resolution.json'), resolution);
325
+ fs.writeFileSync(path.join(vmzDir, 'site-delivery-resolution.json'), `${JSON.stringify(resolution, null, 2)}\n`, 'utf8');
327
326
  }
328
327
  return { contract: norm.contract, resolution };
329
328
  }
@@ -7,15 +7,9 @@ import crypto from 'node:crypto';
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { pathToFileURL } from 'node:url';
10
- import { createRenderHost } from '@vmz/core/render-host';
11
- import { resolveRouteLayoutChain } from '@vmz/core/route-layout-chain';
12
- import { listClientComponentsSync } from '@vmz/core/component-registry';
13
10
  import { emitCdnPolicy } from './cdn-policy.js';
14
11
  import { emitContentAddressedAssets } from './content-addressed-assets.js';
15
12
  import { absoluteUrl, buildLocalePageMeta, localizeBodyLinks } from './locale-router.js';
16
- import { requireNativeAddon } from './native-addon.js';
17
- import { writePrettyJsonFile } from './pretty-json.js';
18
- import { filePathPatternFromChunk, isRouteBoundaryStem, listPublicPageUnits, parsePathPattern, unitBrowserPathPattern } from './route-path.js';
19
13
  export const STATIC_DELIVERY_MANIFEST_SCHEMA = 'vmz.static.delivery_manifest.v0';
20
14
  /**
21
15
  * @param {string} distDir
@@ -32,8 +26,7 @@ export async function emitWebStatic(distDir, opts = {}) {
32
26
  if (!fs.existsSync(domPath)) {
33
27
  throw new Error(`emitWebStatic: missing ${domPath} — run vmz build first`);
34
28
  }
35
- const host = await createRenderHost(distDir, { strictDeployment: true, preload: 'none' });
36
- const { renderToString, renderToStream } = host;
29
+ const { renderToString, renderToStream } = await import(pathToFileURL(domPath).href);
37
30
  const pageCatalog = listPageClientFiles(distDir);
38
31
  /** @type {Array<{
39
32
  * routeId: string,
@@ -50,7 +43,7 @@ export async function emitWebStatic(distDir, opts = {}) {
50
43
  /** @type {Array<{ routeId: string, path: string, chunkId: string, classification: string, reason: string }>} */
51
44
  const skipped = [];
52
45
  for (const page of pageCatalog) {
53
- const pattern = page.pathPattern || patternFromSegs(page.segs);
46
+ const pattern = patternFromSegs(page.segs);
54
47
  const routeId = guessRouteId(distDir, page.chunkId);
55
48
  if (page.segs.some((s) => s.kind === 'param' || s.kind === 'catch')) {
56
49
  skipped.push({
@@ -101,8 +94,7 @@ export async function emitWebStatic(distDir, opts = {}) {
101
94
  }
102
95
  }
103
96
  const meta = await resolvePageMeta(Page, { params, props, pathname: pattern, origin });
104
- const layoutChain = resolveRouteLayoutChain(distDir, page.chunkId);
105
- await host.ensureComponents([page.chunkId, ...layoutChain]);
97
+ const layoutChain = resolveLayoutChain(distDir, page.chunkId);
106
98
  let bodyHtml = '';
107
99
  for await (const chunk of renderToStream(Page, props, {})) {
108
100
  bodyHtml += chunk;
@@ -170,11 +162,7 @@ export async function emitWebStatic(distDir, opts = {}) {
170
162
  fs.writeFileSync(path.join(distDir, '404.html'), notFoundHtml, 'utf8');
171
163
  const sitemap = buildSitemap(origin, generations);
172
164
  fs.writeFileSync(path.join(distDir, 'sitemap.xml'), sitemap, 'utf8');
173
- const nativeRobots = requireNativeAddon();
174
- if (typeof nativeRobots.generateRobotsTxt !== 'function') {
175
- throw new Error('vmz native addon missing generateRobotsTxt — rebuild with `pnpm napi:build`');
176
- }
177
- const robots = nativeRobots.generateRobotsTxt(`${origin}/sitemap.xml`);
165
+ const robots = `User-agent: *\nAllow: /\nDisallow: /404\nSitemap: ${origin}/sitemap.xml\n`;
178
166
  fs.writeFileSync(path.join(distDir, 'robots.txt'), robots, 'utf8');
179
167
  // Hard rule: no SPA fallback shim in artifact.
180
168
  for (const bad of ['_redirects', 'vercel.json', 'netlify.toml']) {
@@ -191,7 +179,7 @@ export async function emitWebStatic(distDir, opts = {}) {
191
179
  const manifest = {
192
180
  schema: STATIC_DELIVERY_MANIFEST_SCHEMA,
193
181
  applicationId,
194
- deliveryProfile: 'static',
182
+ deliveryProfile: 'web-static',
195
183
  origin,
196
184
  generatedAt: new Date().toISOString(),
197
185
  spaFallback: false,
@@ -219,8 +207,7 @@ export async function emitWebStatic(distDir, opts = {}) {
219
207
  };
220
208
  const digest = sha256Hex(canonicalJson(manifest));
221
209
  manifest.manifestDigest = digest;
222
- writePrettyJsonFile(path.join(vmzDir, 'static-delivery-manifest.json'), manifest);
223
- emitStaticClientEntries(distDir, pageCatalog);
210
+ fs.writeFileSync(path.join(vmzDir, 'static-delivery-manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
224
211
  const assets = emitContentAddressedAssets(distDir);
225
212
  manifest.contentAddressedAssets = {
226
213
  schema: assets.manifest.schema,
@@ -231,7 +218,7 @@ export async function emitWebStatic(distDir, opts = {}) {
231
218
  // Re-stamp static manifest after linking asset digest (HTML already rewritten on disk).
232
219
  delete manifest.manifestDigest;
233
220
  manifest.manifestDigest = sha256Hex(canonicalJson(manifest));
234
- writePrettyJsonFile(path.join(vmzDir, 'static-delivery-manifest.json'), manifest);
221
+ fs.writeFileSync(path.join(vmzDir, 'static-delivery-manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
235
222
  const cdn = emitCdnPolicy(distDir, manifest);
236
223
  return {
237
224
  manifest,
@@ -271,11 +258,8 @@ function sortKeys(value) {
271
258
  * @param {string} distDir
272
259
  */
273
260
  function listPageClientFiles(distDir) {
274
- const fromDep = listPagesFromDeployment(distDir);
275
- if (fromDep.length)
276
- return fromDep;
277
261
  const root = path.join(distDir, 'pages');
278
- /** @type {Array<{ chunkId: string, segs: ReturnType<typeof parsePathPattern>, pathPattern: string }>} */
262
+ /** @type {Array<{ chunkId: string, segs: ReturnType<typeof parseChunkSegments> }>} */
279
263
  const out = [];
280
264
  function walk(abs, relParts) {
281
265
  let ents;
@@ -290,11 +274,10 @@ function listPageClientFiles(distDir) {
290
274
  walk(path.join(abs, e.name), [...relParts, e.name]);
291
275
  else if (e.isFile() && e.name.endsWith('.client.js')) {
292
276
  const stem = e.name.replace(/\.client\.js$/, '');
293
- if (isRouteBoundaryStem(stem))
277
+ if (stem === 'Layout' || stem === 'Loading' || stem === 'Error' || stem === 'NotFound')
294
278
  continue;
295
279
  const chunkId = ['pages', ...relParts, stem].join('/');
296
- const pathPattern = filePathPatternFromChunk(chunkId);
297
- out.push({ chunkId, pathPattern, segs: parsePathPattern(pathPattern) });
280
+ out.push({ chunkId, segs: parseChunkSegments(chunkId) });
298
281
  }
299
282
  }
300
283
  }
@@ -302,26 +285,32 @@ function listPageClientFiles(distDir) {
302
285
  return out;
303
286
  }
304
287
  /**
305
- * @param {string} distDir
288
+ * @param {string} chunkId
306
289
  */
307
- function listPagesFromDeployment(distDir) {
308
- const deploymentPath = path.join(distDir, 'vmz-deployment.json');
309
- if (!fs.existsSync(deploymentPath))
310
- return [];
311
- try {
312
- const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
313
- return listPublicPageUnits(deployment).map((u) => {
314
- const chunkId = String(u.chunkId || '').replace(/\\/g, '/');
315
- const pathPattern = unitBrowserPathPattern(u);
316
- return { chunkId, pathPattern, segs: parsePathPattern(pathPattern) };
317
- });
318
- }
319
- catch {
320
- return [];
290
+ function parseChunkSegments(chunkId) {
291
+ const rel = chunkId.replace(/^pages\//, '');
292
+ const parts = rel.split('/').filter(Boolean);
293
+ /** @type {Array<{ kind: 'static' | 'param' | 'catch', value?: string, name?: string }>} */
294
+ const segs = [];
295
+ for (let i = 0; i < parts.length; i++) {
296
+ const p = parts[i];
297
+ if (p.startsWith('(') && p.endsWith(')') && p.length > 2)
298
+ continue;
299
+ if (p === 'index' && i === parts.length - 1)
300
+ continue;
301
+ const catchAll = /^\[\.\.\.([^\]]+)\]$/.exec(p);
302
+ const param = /^\[([^\]]+)\]$/.exec(p);
303
+ if (catchAll)
304
+ segs.push({ kind: 'catch', name: catchAll[1] });
305
+ else if (param)
306
+ segs.push({ kind: 'param', name: param[1] });
307
+ else
308
+ segs.push({ kind: 'static', value: p.toLowerCase() });
321
309
  }
310
+ return segs;
322
311
  }
323
312
  /**
324
- * @param {ReturnType<typeof parsePathPattern>} segs
313
+ * @param {ReturnType<typeof parseChunkSegments>} segs
325
314
  */
326
315
  function patternFromSegs(segs) {
327
316
  if (!segs.length)
@@ -354,6 +343,24 @@ async function loadCtor(distDir, chunkId) {
354
343
  const mod = await import(`${href}?t=${Date.now()}`);
355
344
  return mod.default;
356
345
  }
346
+ /**
347
+ * @param {string} distDir
348
+ * @param {string} pageChunkId
349
+ */
350
+ function resolveLayoutChain(distDir, pageChunkId) {
351
+ const rel = pageChunkId.replace(/^pages\//, '');
352
+ const parts = rel.split('/').filter(Boolean);
353
+ parts.pop();
354
+ /** @type {string[]} */
355
+ const chain = [];
356
+ for (let i = parts.length; i >= 0; i--) {
357
+ const dirParts = parts.slice(0, i);
358
+ const layoutChunk = ['pages', ...dirParts, 'Layout'].join('/');
359
+ if (fs.existsSync(path.join(distDir, `${layoutChunk}.client.js`)))
360
+ chain.unshift(layoutChunk);
361
+ }
362
+ return chain;
363
+ }
357
364
  /**
358
365
  * @param {string} distDir
359
366
  * @param {string} chunkId
@@ -382,8 +389,8 @@ async function resolvePageMeta(Page, ctx) {
382
389
  else if (Page.meta && typeof Page.meta === 'object') {
383
390
  raw = Page.meta;
384
391
  }
385
- const title = String(raw.title || guessTitle(ctx.pathname) || 'App');
386
- const description = String(raw.description || '');
392
+ const title = String(raw.title || `${guessTitle(ctx.pathname)} · VMZ`);
393
+ const description = String(raw.description || `VMZ page ${ctx.pathname}`);
387
394
  const canonical = String(raw.canonical || `${ctx.origin}${ctx.pathname === '/' ? '/' : ctx.pathname}`);
388
395
  const robots = String(raw.robots || 'index,follow');
389
396
  const lang = String(raw.lang || 'en');
@@ -507,87 +514,59 @@ function readCssEntry(distDir) {
507
514
  */
508
515
  function wrapDocument(input) {
509
516
  const propsJson = JSON.stringify(input.props ?? {});
517
+ const layoutAttr = input.layoutChain.length ? ` data-vmz-layout="${escapeAttr(input.layoutChain.join(','))}"` : '';
518
+ const pageAttr = input.chunkId ? ` data-vmz-page="${escapeAttr(input.chunkId)}"` : '';
510
519
  const localeId = input.meta.lang || 'en';
511
520
  const dir = input.meta.dir || 'ltr';
512
- const native = requireNativeAddon();
513
- if (typeof native.generatePageShell !== 'function') {
514
- throw new Error('vmz native addon missing generatePageShell rebuild with `pnpm napi:build`');
515
- }
516
- return native.generatePageShell({
517
- bodyHtml: input.bodyHtml,
518
- chunkId: input.chunkId || '',
519
- layoutChain: input.layoutChain || [],
520
- propsJson,
521
- meta: {
522
- title: input.meta.title,
523
- description: input.meta.description,
524
- canonical: input.meta.canonical,
525
- robots: input.meta.robots,
526
- lang: localeId,
527
- dir,
528
- alternates: input.meta.alternates || [],
529
- },
530
- // napi Option<String>: omit/undefined = None; null is rejected as String
531
- ...(input.cssEntry ? { cssEntry: String(input.cssEntry) } : {}),
532
- isErrorDocument: !!input.isErrorDocument,
533
- });
521
+ const localeAttr = ` data-vmz-locale="${escapeAttr(localeId)}" data-vmz-dir="${escapeAttr(dir)}"`;
522
+ const cssLink = input.cssEntry ? ` <link rel="stylesheet" href="/${String(input.cssEntry).replace(/^\/+/, '')}" />\n` : '';
523
+ const entry = input.isErrorDocument ? '' : ` <script type="module" src="/entry-client.js"></script>\n`;
524
+ const hreflang = (input.meta.alternates || [])
525
+ .map((a) => ` <link rel="alternate" hreflang="${escapeAttr(a.hreflang)}" href="${escapeAttr(a.href)}" />`)
526
+ .join('\n');
527
+ const hreflangBlock = hreflang ? `${hreflang}\n` : '';
528
+ return `<!DOCTYPE html>
529
+ <html lang="${escapeAttr(localeId)}" data-locale="${escapeAttr(localeId)}" dir="${escapeAttr(dir)}">
530
+ <head>
531
+ <meta charset="utf-8" />
532
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
533
+ <title>${escapeHtml(input.meta.title)}</title>
534
+ <meta name="description" content="${escapeAttr(input.meta.description)}" />
535
+ <meta name="robots" content="${escapeAttr(input.meta.robots)}" />
536
+ <link rel="canonical" href="${escapeAttr(input.meta.canonical)}" />
537
+ ${hreflangBlock} <meta property="og:title" content="${escapeAttr(input.meta.title)}" />
538
+ <meta property="og:description" content="${escapeAttr(input.meta.description)}" />
539
+ <meta property="og:url" content="${escapeAttr(input.meta.canonical)}" />
540
+ ${cssLink}</head>
541
+ <body>
542
+ <div id="app"${pageAttr}${layoutAttr}${localeAttr} data-vmz-props="${escapeAttr(propsJson)}">${input.bodyHtml}</div>
543
+ ${entry}</body>
544
+ </html>
545
+ `;
534
546
  }
535
547
  /**
536
548
  * @param {string} origin
537
549
  * @param {Array<{ canonical: string, robots: string }>} generations
538
550
  */
539
- function buildSitemap(_origin, generations) {
540
- const urls = generations.filter((g) => !String(g.robots).includes('noindex')).map((g) => ({ loc: g.canonical }));
541
- const native = requireNativeAddon();
542
- if (typeof native.generateSitemapXml !== 'function') {
543
- throw new Error('vmz native addon missing generateSitemapXml — rebuild with `pnpm napi:build`');
544
- }
545
- return native.generateSitemapXml(urls);
551
+ function buildSitemap(origin, generations) {
552
+ const urls = generations
553
+ .filter((g) => !String(g.robots).includes('noindex'))
554
+ .map((g) => ` <url>
555
+ <loc>${escapeXml(g.canonical)}</loc>
556
+ </url>`)
557
+ .join('\n');
558
+ return `<?xml version="1.0" encoding="UTF-8"?>
559
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
560
+ ${urls}
561
+ </urlset>
562
+ `;
546
563
  }
547
- /**
548
- * Static CDN must ship entry-client/event like serve-host (content-addressed + HTML rewrite).
549
- * @param {string} distDir
550
- * @param {Array<{ chunkId: string }>} pageCatalog
551
- */
552
- function emitStaticClientEntries(distDir, pageCatalog) {
553
- const componentEntries = listClientComponentsSync(distDir, { strict: true });
554
- const indexChunk = pageCatalog.find((p) => p.chunkId === 'pages/index')?.chunkId || pageCatalog[0]?.chunkId || 'pages/index';
555
- const resumeEntries = loadPageResumeEntriesSync(distDir, indexChunk);
556
- const lazySet = new Set(resumeEntries
557
- .filter((e) => isEventResumeStrategy(e.strategy))
558
- .map((e) => e.component)
559
- .filter(Boolean));
560
- const eager = componentEntries.filter((e) => !lazySet.has(e.name));
561
- const lazy = componentEntries.filter((e) => lazySet.has(e.name));
562
- const native = requireNativeAddon();
563
- if (typeof native.generateServeEntryClient !== 'function') {
564
- throw new Error('vmz native addon missing generateServeEntryClient — rebuild with `pnpm napi:build`');
565
- }
566
- fs.writeFileSync(path.join(distDir, 'entry-client.js'), native.generateServeEntryClient(eager, lazy, ''), 'utf8');
567
- if (typeof native.generateServeEntryEvent === 'function') {
568
- fs.writeFileSync(path.join(distDir, 'entry-event.js'), native.generateServeEntryEvent(''), 'utf8');
569
- }
564
+ function escapeHtml(s) {
565
+ return String(s).replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
570
566
  }
571
- /**
572
- * @param {string} distDir
573
- * @param {string} chunkId
574
- */
575
- function loadPageResumeEntriesSync(distDir, chunkId) {
576
- try {
577
- const dep = JSON.parse(fs.readFileSync(path.join(distDir, 'vmz-deployment.json'), 'utf8'));
578
- const units = Array.isArray(dep.units) ? dep.units : [];
579
- const page = units.find((u) => u.chunkId === chunkId) || units.find((u) => u.chunkId === 'pages/index') || units.find((u) => u.kind === 'page');
580
- const entries = Array.isArray(page?.resumeEntries) ? page.resumeEntries : [];
581
- return entries.map((e) => ({
582
- component: String(e.component || ''),
583
- strategy: String(e.strategy || ''),
584
- }));
585
- }
586
- catch {
587
- return [];
588
- }
567
+ function escapeAttr(s) {
568
+ return escapeHtml(s).replaceAll('"', '&quot;');
589
569
  }
590
- /** @param {string} strategy */
591
- function isEventResumeStrategy(strategy) {
592
- return strategy === 'event' || strategy === 'click' || String(strategy).startsWith('event:');
570
+ function escapeXml(s) {
571
+ return escapeAttr(s).replaceAll("'", '&apos;');
593
572
  }
package/dist/test-cmd.js CHANGED
@@ -6,7 +6,6 @@
6
6
  import path from 'node:path';
7
7
  import { createWorkspace } from './index.js';
8
8
  import { log } from './log.js';
9
- import { generatePrettyJson } from './pretty-json.js';
10
9
  /**
11
10
  * @returns {Promise<typeof import('@vmz/test')>}
12
11
  */
@@ -318,7 +317,7 @@ export async function cmdTest(args) {
318
317
  return errors.length ? 2 : 0;
319
318
  }
320
319
  if (wantJson) {
321
- const text = `${generatePrettyJson(report)}\n`;
320
+ const text = `${JSON.stringify(report, null, 2)}\n`;
322
321
  if (typeof args.json === 'string' && args.json !== 'true') {
323
322
  const { writeFileSync } = await import('node:fs');
324
323
  writeFileSync(path.resolve(String(args.json)), text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/vmz",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
6
6
  "license": "MIT",
@@ -48,15 +48,15 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@vmz/core": "0.1.12",
52
- "@vmz/plugin": "0.1.12",
53
- "@vmz/protocol": "0.1.12",
51
+ "@vmz/core": "0.1.13",
52
+ "@vmz/plugin": "0.1.13",
53
+ "@vmz/protocol": "0.1.13",
54
54
  "jiti": "^2.6.1",
55
55
  "json5": "^2.2.3"
56
56
  },
57
57
  "peerDependencies": {
58
- "@vmz/plugin-markdown-it": "0.1.12",
59
- "@vmz/test": "0.1.12",
58
+ "@vmz/plugin-markdown-it": "0.1.13",
59
+ "@vmz/test": "0.1.13",
60
60
  "typescript": "^5.8.3"
61
61
  },
62
62
  "peerDependenciesMeta": {
@@ -90,12 +90,12 @@
90
90
  "cli"
91
91
  ],
92
92
  "optionalDependencies": {
93
- "@vmz/vmz-win32-x64": "0.1.12",
94
- "@vmz/vmz-win32-arm64": "0.1.12",
95
- "@vmz/vmz-darwin-x64": "0.1.12",
96
- "@vmz/vmz-darwin-arm64": "0.1.12",
97
- "@vmz/vmz-linux-x64": "0.1.12",
98
- "@vmz/vmz-linux-arm64": "0.1.12"
93
+ "@vmz/vmz-win32-x64": "0.1.13",
94
+ "@vmz/vmz-win32-arm64": "0.1.13",
95
+ "@vmz/vmz-darwin-x64": "0.1.13",
96
+ "@vmz/vmz-darwin-arm64": "0.1.13",
97
+ "@vmz/vmz-linux-x64": "0.1.13",
98
+ "@vmz/vmz-linux-arm64": "0.1.13"
99
99
  },
100
100
  "publishConfig": {
101
101
  "access": "public"
@@ -1,80 +0,0 @@
1
- /**
2
- * Dev watch helpers: coalesce multi-file bursts without dropping dirty set,
3
- * and derive extra watch roots from the compile graph (deployment unit sources).
4
- */
5
- /**
6
- * @typedef {{ changed: string[], deleted: string[] }} DirtySet
7
- */
8
- /**
9
- * Merge dirty sets: later change cancels delete and vice versa.
10
- * @param {DirtySet} a
11
- * @param {DirtySet} b
12
- * @returns {DirtySet}
13
- */
14
- export declare function mergeDirtySets(a: any, b: any): {
15
- changed: unknown[];
16
- deleted: unknown[];
17
- };
18
- /**
19
- * Wait until `root` stops changing; return the **accumulated** dirty set since `initial`.
20
- * Updates `fingerprints` for `root` as it polls. Does **not** discard `initial`.
21
- *
22
- * @param {string} root
23
- * @param {Map<string, Map<string, string>>} fingerprints
24
- * @param {DirtySet} initial
25
- * @param {{ sleep?: (ms: number) => Promise<void>, maxRounds?: number, settleMs?: number }} [opts]
26
- * @returns {Promise<DirtySet>}
27
- */
28
- export declare function coalesceRootBurst(root: any, fingerprints: any, initial: any, opts?: {}): Promise<{
29
- changed: any[];
30
- deleted: any[];
31
- }>;
32
- /**
33
- * Walk up from a file to find a package.json directory.
34
- * @param {string} file
35
- * @returns {string | null}
36
- */
37
- export declare function findPackageRoot(file: any): string;
38
- /**
39
- * Prefer package/src when present; otherwise the directory containing the source file.
40
- * @param {string} sourceFile
41
- * @returns {string | null}
42
- */
43
- export declare function watchRootForSourceFile(sourceFile: any): string;
44
- /**
45
- * Absolute roots for workspace / file / link deps that have a src tree.
46
- * @param {string} project
47
- * @returns {string[]}
48
- */
49
- export declare function localLinkDependencyRoots(project: any): any[];
50
- /**
51
- * Collect watch roots: project src/locales/documents + compile-graph external sources
52
- * + local link/workspace package roots. Never adds a bare registry node_modules tree.
53
- *
54
- * @param {{ project: string, outDir: string }} opts
55
- * @returns {{ roots: string[], dependencyRoots: string[], applicationRoots: string[] }}
56
- */
57
- export declare function collectDevWatchRoots(opts: any): {
58
- roots: any[];
59
- dependencyRoots: unknown[];
60
- applicationRoots: any[];
61
- };
62
- /**
63
- * Classify which watch bucket a root belongs to.
64
- * @param {string} root
65
- * @param {{
66
- * src: string,
67
- * docsRoot: string,
68
- * localesRoot: string,
69
- * designsRoot: string,
70
- * dependencyRoots: string[],
71
- * }} ctx
72
- * @returns {'src' | 'locales' | 'docs' | 'designs' | 'dep' | 'other'}
73
- */
74
- export declare function classifyWatchRoot(root: any, ctx: any): "src" | "locales" | "docs" | "designs" | "dep" | "other";
75
- /**
76
- * Classify whether a changed file lives under a dependency watch root (not app src).
77
- * @param {string} file
78
- * @param {string[]} dependencyRoots
79
- */
80
- export declare function isDependencyPath(file: any, dependencyRoots: any): any;