@vmz/vmz 0.3.11 → 0.3.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 (52) hide show
  1. package/delivery-runtime-files.json +1 -0
  2. package/dist/cli/cli.js +46 -23
  3. package/dist/cli/format-cmd.d.ts +9 -17
  4. package/dist/cli/format-cmd.js +37 -49
  5. package/dist/cli/format-config.d.ts +6 -9
  6. package/dist/cli/format-config.js +5 -29
  7. package/dist/delivery/delivery-profile.js +1 -1
  8. package/dist/dev/dev-session.js +16 -5
  9. package/dist/document/document-base-css.d.ts +15 -0
  10. package/dist/document/document-base-css.js +578 -0
  11. package/dist/document/document-build.d.ts +1 -0
  12. package/dist/document/document-build.js +94 -78
  13. package/dist/document/document-client.d.ts +13 -0
  14. package/dist/document/document-client.js +217 -0
  15. package/dist/document/document-cmd.js +3 -1
  16. package/dist/document/document-designs.d.ts +9 -3
  17. package/dist/document/document-designs.js +20 -34
  18. package/dist/document/document-enrich.d.ts +9 -0
  19. package/dist/document/document-enrich.js +55 -0
  20. package/dist/document/document-highlight.d.ts +26 -0
  21. package/dist/document/document-highlight.js +54 -0
  22. package/dist/document/document-integrate.d.ts +5 -0
  23. package/dist/document/document-integrate.js +1 -0
  24. package/dist/document/document-interactive.js +10 -2
  25. package/dist/document/document-layout-render.js +6 -2
  26. package/dist/document/document-schema.d.ts +5 -0
  27. package/dist/workspace/build-assemble.js +18 -6
  28. package/dist/workspace/cdn-policy.js +12 -0
  29. package/dist/workspace/content-addressed-assets.d.ts +6 -17
  30. package/dist/workspace/content-addressed-assets.js +10 -95
  31. package/dist/workspace/delivery-runtime-imports.js +3 -0
  32. package/dist/workspace/native-addon.d.ts +4 -0
  33. package/dist/workspace/pack-native.d.ts +139 -0
  34. package/dist/workspace/pack-native.js +273 -0
  35. package/dist/workspace/pack.d.ts +113 -27
  36. package/dist/workspace/pack.js +271 -54
  37. package/dist/workspace/plugin-host.d.ts +4 -4
  38. package/dist/workspace/plugin-host.js +9 -12
  39. package/dist/workspace/production-observability.d.ts +2 -0
  40. package/dist/workspace/production-observability.js +6 -2
  41. package/dist/workspace/project-build.js +26 -4
  42. package/dist/workspace/public-api.d.ts +7 -3
  43. package/dist/workspace/public-api.js +5 -3
  44. package/dist/workspace/static-emit.d.ts +30 -0
  45. package/dist/workspace/static-emit.js +211 -18
  46. package/dist/workspace/user-config.d.ts +112 -0
  47. package/dist/workspace/user-config.js +12 -0
  48. package/host-runtime-files.json +1 -0
  49. package/locales/en-US/cli.json +5 -4
  50. package/package.json +20 -20
  51. package/dist/workspace/pack-client-packages.d.ts +0 -32
  52. package/dist/workspace/pack-client-packages.js +0 -437
@@ -9,6 +9,12 @@ export type EmitWebStaticOpts = {
9
9
  applicationId?: string;
10
10
  staticParams?: Record<string, Array<Record<string, string>>>;
11
11
  projectRoot?: string;
12
+ /** Assembly recorded in the manifest (`local-static` overrides the static default). */
13
+ deliveryProfile?: string;
14
+ /** Build cancellation signal, forwarded into the SSR `onMount` budget race. */
15
+ signal?: AbortSignal;
16
+ /** Per-instance SSR `onMount` budget in ms (runtime default applies when unset). */
17
+ onMountBudgetMs?: number;
12
18
  };
13
19
  type StaticDeliveryManifest = {
14
20
  schema: string;
@@ -134,4 +140,28 @@ export declare function emitWebStatic(distDir: string, opts?: EmitWebStaticOpts)
134
140
  };
135
141
  };
136
142
  }>;
143
+ /**
144
+ * Write the browser bootstrap modules ahead of the pack stage: `pack` needs the
145
+ * literal dynamic-import tables in `entry-client.js` / `entry-event.js` as graph
146
+ * entries and must run before HTML emit (HTML resolves `_vmz/pack/` URLs). The
147
+ * static emit stage re-runs the same function, so both passes produce identical
148
+ * bytes.
149
+ * @param {string} distDir
150
+ * @returns {string[]} written bootstrap file names
151
+ */
152
+ export declare function emitStaticBootstrapEntries(distDir: any): string[];
153
+ /**
154
+ * Prune the web-static delivery to its final browser set after HTML emit:
155
+ * remove the Node-only serve-host material (`_vmz/host/`, `vmz-serve-host.mjs`)
156
+ * and every unpacked module JS/map that `_vmz/pack/` chunks supersede
157
+ * (bootstrap entries, page/component/layout units, server faces, `#server/`,
158
+ * `ssr/`). The `/dom.browser.js` face closure stays — the packed navigation
159
+ * keeps that root-URL load by contract. JSON metadata and assets are untouched.
160
+ * Only `vmz serve` re-materializes the host (`materializeServeHostRuntime`);
161
+ * static hosting never needs it.
162
+ */
163
+ export declare function pruneWebStaticDelivery(outDir: string): {
164
+ removed: number;
165
+ keptFaces: string[];
166
+ };
137
167
  export {};
@@ -6,7 +6,7 @@ import crypto from 'node:crypto';
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
8
  import { pathToFileURL } from 'node:url';
9
- import { listClientComponentsSync } from '@vmz/core/component-registry';
9
+ import { collectDependsOnClosure, listClientComponentsSync, readDeploymentDocument } from '@vmz/core/component-registry';
10
10
  import { createRenderHost } from '@vmz/core/render-host';
11
11
  import { resolveRouteLayoutChain } from '@vmz/core/route-layout-chain';
12
12
  import { emitCdnPolicy } from './cdn-policy.js';
@@ -14,6 +14,7 @@ import { emitContentAddressedAssets, hashedAssetHref } from './content-addressed
14
14
  import { LOCALE_LINK_PLAN_REL } from '../locale/locale-route-emit.js';
15
15
  import { absoluteUrl, applyLocaleLinkPlan, buildLocaleLinkPlan, buildLocalePageMeta, linkRouteAliasesFromUnits, } from '../locale/locale-router.js';
16
16
  import { requireNativeAddon } from './native-addon.js';
17
+ import { PACK_NODE_MANIFEST_REL, PACK_NODE_OUTPUT_DIR_REL, packEntryHref, packNodeEntryFile } from './pack.js';
17
18
  import { writePrettyJsonFile } from './pretty-json.js';
18
19
  import { emitPublicStaticAssets } from './public-static-assets.js';
19
20
  import { emitRouteCatalog, loadRouteCatalog, ROUTE_CATALOG_SCHEMA } from './route-catalog-emit.js';
@@ -23,6 +24,7 @@ export const STATIC_EMIT_PLAN_SCHEMA = 'vmz.static.emit_plan.v0';
23
24
  export async function emitWebStatic(distDir, opts = {}) {
24
25
  const origin = String(opts.origin || process.env.VMZ_SITE_ORIGIN || 'https://example.test').replace(/\/$/, '');
25
26
  const applicationId = opts.applicationId || path.basename(path.dirname(distDir));
27
+ const deliveryProfile = String(opts.deliveryProfile || 'static');
26
28
  const domPath = path.join(distDir, 'vmz-dom.js');
27
29
  if (!fs.existsSync(domPath)) {
28
30
  throw new Error(`emitWebStatic: missing ${domPath} — run vmz build first`);
@@ -32,6 +34,9 @@ export async function emitWebStatic(distDir, opts = {}) {
32
34
  const publicAssets = emitPublicStaticAssets(distDir, { projectRoot: opts.projectRoot });
33
35
  const host = await createRenderHost(distDir, { strictDeployment: true, preload: 'none' });
34
36
  const { renderToString, renderToStream } = host;
37
+ // One budget/signal pair for every SSR render of this build: a hung or
38
+ // aborted `onMount` fails the page build instead of stalling it.
39
+ const renderOpts = { signal: opts.signal ?? undefined, onMountBudgetMs: opts.onMountBudgetMs };
35
40
  const pageCatalog = listPageClientFiles(distDir);
36
41
  const generations = [];
37
42
  const skipped = [];
@@ -41,7 +46,9 @@ export async function emitWebStatic(distDir, opts = {}) {
41
46
  const assets = emitContentAddressedAssets(distDir);
42
47
  const cssLogical = readCssEntry(distDir);
43
48
  const cssEntry = hashedAssetHref(assets.rewrites, cssLogical) || hashedAssetHref(assets.rewrites, 'vmz.css');
44
- const moduleScriptSrc = hashedAssetHref(assets.rewrites, 'entry-client.js') || '/entry-client.js';
49
+ // Packed browser JS is final-named under `_vmz/pack/` — resolve via the pack
50
+ // manifest entryFiles mapping; unpacked root bytes are never a fallback.
51
+ const moduleScriptSrc = packEntryHref(distDir, 'entry-client.js');
45
52
  const localeArt = loadLocaleArtifact(distDir);
46
53
  const localeLinkPlan = loadLocaleLinkPlan(distDir) || buildLocaleLinkPlan(withLinkRouteAliases(localeArt, distDir, pageCatalog));
47
54
  for (const page of pageCatalog) {
@@ -99,14 +106,23 @@ export async function emitWebStatic(distDir, opts = {}) {
99
106
  const layoutChain = resolveRouteLayoutChain(distDir, page.chunkId);
100
107
  await host.ensureComponents([page.chunkId, ...layoutChain]);
101
108
  let bodyHtml = '';
102
- for await (const chunk of renderToStream(Page, props, {})) {
103
- bodyHtml += chunk;
109
+ try {
110
+ for await (const chunk of renderToStream(Page, props, renderOpts)) {
111
+ bodyHtml += chunk;
112
+ }
113
+ for (let i = layoutChain.length - 1; i >= 0; i--) {
114
+ const Layout = await loadCtor(distDir, layoutChain[i]);
115
+ if (!Layout)
116
+ continue;
117
+ bodyHtml = await renderToString(Layout, {}, { ...renderOpts, slotHtml: bodyHtml });
118
+ }
104
119
  }
105
- for (let i = layoutChain.length - 1; i >= 0; i--) {
106
- const Layout = await loadCtor(distDir, layoutChain[i]);
107
- if (!Layout)
108
- continue;
109
- bodyHtml = await renderToString(Layout, {}, { slotHtml: bodyHtml });
120
+ catch (err) {
121
+ const detail = err instanceof Error ? err.message : String(err);
122
+ throw new Error(`emitWebStatic: SSR render failed for route ${pattern} (${page.chunkId}): ${detail}`, { cause: err });
123
+ }
124
+ if (opts.signal?.aborted) {
125
+ throw new Error(`emitWebStatic: build aborted during SSR render for route ${pattern} (${page.chunkId})`);
110
126
  }
111
127
  const localeWrites = expandLocaleStaticGenerations({
112
128
  localeArt,
@@ -188,7 +204,7 @@ export async function emitWebStatic(distDir, opts = {}) {
188
204
  const manifest = {
189
205
  schema: STATIC_DELIVERY_MANIFEST_SCHEMA,
190
206
  applicationId,
191
- deliveryProfile: 'static',
207
+ deliveryProfile,
192
208
  origin,
193
209
  generatedAt: new Date().toISOString(),
194
210
  spaFallback: false,
@@ -232,7 +248,7 @@ export async function emitWebStatic(distDir, opts = {}) {
232
248
  writePrettyJsonFile(path.join(vmzDir, 'static-emit-plan.json'), {
233
249
  schema: STATIC_EMIT_PLAN_SCHEMA,
234
250
  applicationId,
235
- deliveryProfile: 'static',
251
+ deliveryProfile,
236
252
  origin,
237
253
  localeLinks: localeLinkPlan,
238
254
  assetPlanPath: '_vmz/asset-plan.json',
@@ -331,8 +347,11 @@ function htmlPathForRoute(pathname) {
331
347
  * @param {string} chunkId
332
348
  */
333
349
  async function loadCtor(distDir, chunkId) {
334
- const href = pathToFileURL(path.join(distDir, `${chunkId}.client.js`)).href;
335
- const mod = await import(`${href}?t=${Date.now()}`);
350
+ // Node chunk identity comes from `_vmz/pack-node-manifest.json`; the
351
+ // unpacked root `${chunkId}.client.js` is not a fallback. No `?t=` cache
352
+ // bust: packed chunks are shared instance URLs.
353
+ const href = pathToFileURL(packNodeEntryFile(distDir, chunkId)).href;
354
+ const mod = await import(href);
336
355
  return mod.default;
337
356
  }
338
357
  /**
@@ -559,9 +578,14 @@ function buildSitemap(_origin, generations) {
559
578
  return native.generateSitemapXml(urls);
560
579
  }
561
580
  /**
562
- * Static CDN must ship entry-client/event like serve-host (content-addressed + HTML rewrite).
581
+ * Static CDN must ship entry-client/event like serve-host. The bootstrap tables
582
+ * are literal dynamic-import maps: page/layout units are keyed by chunkId
583
+ * (`data-vmz-page` / `data-vmz-layout` lookups) and islands are enumerated
584
+ * explicitly — unknown names throw at runtime by design, never via fallback
585
+ * specifier concatenation.
563
586
  * @param {string} distDir
564
587
  * @param {Array<{ chunkId: string }>} pageCatalog
588
+ * @returns {string[]} written bootstrap file names
565
589
  */
566
590
  function emitStaticClientEntries(distDir, pageCatalog) {
567
591
  const componentEntries = listClientComponentsSync(distDir, { strict: true });
@@ -571,16 +595,74 @@ function emitStaticClientEntries(distDir, pageCatalog) {
571
595
  .filter((e) => isEventResumeStrategy(e.strategy))
572
596
  .map((e) => e.component)
573
597
  .filter(Boolean));
574
- const eager = componentEntries.filter((e) => !lazySet.has(e.name));
575
- const lazy = componentEntries.filter((e) => lazySet.has(e.name));
598
+ // Pages/layouts resolve `<chunkId>.client.js` at the dist root (SSR loadCtor
599
+ // uses the same path); they must be in the table or hydration throws.
600
+ const pageEntries = pageCatalog.map((p) => ({ name: p.chunkId, entry: `${p.chunkId}.client.js` }));
601
+ const layoutNames = new Set();
602
+ for (const page of pageCatalog) {
603
+ for (const id of resolveRouteLayoutChain(distDir, page.chunkId))
604
+ layoutNames.add(String(id));
605
+ }
606
+ const layoutEntries = [...layoutNames].sort().map((id) => ({ name: id, entry: `${id}.client.js` }));
607
+ // 引导表只登记从页面/布局可达的组件:部署 IR 的 `dependsOn` 闭包之外的
608
+ // 组件没有任何模板引用,进入表只会变成 pack 死字节;不登记后 pack 图
609
+ // 不可达,prune 同时清掉未打包的散落模块。
610
+ const deployment = readDeploymentDocument(distDir, { strict: true });
611
+ const closure = collectDependsOnClosure(deployment, [...pageCatalog.map((p) => p.chunkId), ...layoutNames]);
612
+ const reachableComponents = componentEntries.filter((e) => closure.has(e.chunkId));
613
+ const eagerComponents = reachableComponents.filter((e) => !lazySet.has(e.name));
614
+ const lazyComponents = reachableComponents.filter((e) => lazySet.has(e.name));
615
+ const eager = [...eagerComponents, ...pageEntries, ...layoutEntries];
576
616
  const native = requireNativeAddon();
577
617
  if (typeof native.generateServeEntryClient !== 'function') {
578
618
  throw new Error('vmz native addon missing generateServeEntryClient — rebuild with `pnpm napi:build`');
579
619
  }
580
- fs.writeFileSync(path.join(distDir, 'entry-client.js'), native.generateServeEntryClient(eager, lazy, ''), 'utf8');
620
+ const written = ['entry-client.js'];
621
+ fs.writeFileSync(path.join(distDir, 'entry-client.js'), native.generateServeEntryClient(eager, lazyComponents, ''), 'utf8');
581
622
  if (typeof native.generateServeEntryEvent === 'function') {
582
- fs.writeFileSync(path.join(distDir, 'entry-event.js'), native.generateServeEntryEvent(''), 'utf8');
623
+ // Event shell islands: event-strategy components + document islands whose
624
+ // compiled module exists in the component set (explicit enumeration).
625
+ const islandNames = readDocumentIslandNames(distDir);
626
+ const islands = [...lazyComponents];
627
+ for (const entry of eagerComponents) {
628
+ if (islandNames.has(entry.name))
629
+ islands.push(entry);
630
+ }
631
+ fs.writeFileSync(path.join(distDir, 'entry-event.js'), native.generateServeEntryEvent([], islands, ''), 'utf8');
632
+ written.push('entry-event.js');
583
633
  }
634
+ return written;
635
+ }
636
+ /**
637
+ * Write the browser bootstrap modules ahead of the pack stage: `pack` needs the
638
+ * literal dynamic-import tables in `entry-client.js` / `entry-event.js` as graph
639
+ * entries and must run before HTML emit (HTML resolves `_vmz/pack/` URLs). The
640
+ * static emit stage re-runs the same function, so both passes produce identical
641
+ * bytes.
642
+ * @param {string} distDir
643
+ * @returns {string[]} written bootstrap file names
644
+ */
645
+ export function emitStaticBootstrapEntries(distDir) {
646
+ return emitStaticClientEntries(distDir, listPageClientFiles(distDir));
647
+ }
648
+ /**
649
+ * Island names from an integrated document surface (`document.islands.json`,
650
+ * written by the document build into the same dist). Absent file → no islands.
651
+ * @param {string} distDir
652
+ */
653
+ function readDocumentIslandNames(distDir) {
654
+ const names = new Set();
655
+ try {
656
+ const plan = JSON.parse(fs.readFileSync(path.join(distDir, 'document.islands.json'), 'utf8'));
657
+ for (const island of plan?.islands || []) {
658
+ if (typeof island?.name === 'string' && island.name)
659
+ names.add(island.name);
660
+ }
661
+ }
662
+ catch {
663
+ /* no document surface */
664
+ }
665
+ return names;
584
666
  }
585
667
  /**
586
668
  * @param {string} distDir
@@ -605,3 +687,114 @@ function loadPageResumeEntriesSync(distDir, chunkId) {
605
687
  function isEventResumeStrategy(strategy) {
606
688
  return strategy === 'event' || strategy === 'click' || String(strategy).startsWith('event:');
607
689
  }
690
+ /** Runtime face barrel loaded by URL (`import('/dom.browser.js')`) in the packed client navigation. */
691
+ const FACE_ENTRY_REL = 'dom.browser.js';
692
+ /**
693
+ * Transitive closure of the URL-loaded browser face: start at `dom.browser.js`
694
+ * and follow relative import specifiers that resolve to files in the dist tree.
695
+ * Everything else that is executable JS is either packed into `_vmz/pack/` or
696
+ * Node-side, so it must not ship in a web-static delivery.
697
+ */
698
+ function collectFaceClosure(abs) {
699
+ const closure = new Set();
700
+ if (!fs.existsSync(path.join(abs, FACE_ENTRY_REL)))
701
+ return closure;
702
+ const queue = [FACE_ENTRY_REL];
703
+ while (queue.length) {
704
+ const rel = queue.shift();
705
+ if (closure.has(rel))
706
+ continue;
707
+ closure.add(rel);
708
+ let text;
709
+ try {
710
+ text = fs.readFileSync(path.join(abs, ...rel.split('/')), 'utf8');
711
+ }
712
+ catch {
713
+ continue;
714
+ }
715
+ const specs = [
716
+ ...text.matchAll(/from\s+['"](\.[^'"]+)['"]/g),
717
+ ...text.matchAll(/import\(\s*['"](\.[^'"]+)['"]/g),
718
+ ];
719
+ for (const m of specs) {
720
+ const next = path.posix.normalize(`${path.posix.dirname(rel)}/${m[1]}`);
721
+ if (next.startsWith('..'))
722
+ continue;
723
+ if (!fs.existsSync(path.join(abs, ...next.split('/'))))
724
+ continue;
725
+ if (!closure.has(next))
726
+ queue.push(next);
727
+ }
728
+ }
729
+ return closure;
730
+ }
731
+ function listFilesRecursive(abs, rel = '') {
732
+ const out = [];
733
+ for (const entry of fs.readdirSync(path.join(abs, ...rel.split('/')), { withFileTypes: true })) {
734
+ const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
735
+ if (entry.isDirectory())
736
+ out.push(...listFilesRecursive(abs, nextRel));
737
+ else if (entry.isFile())
738
+ out.push(nextRel);
739
+ }
740
+ return out;
741
+ }
742
+ function removeEmptyDirsBottomUp(abs, rel = '') {
743
+ for (const entry of fs.readdirSync(path.join(abs, ...rel.split('/')), { withFileTypes: true })) {
744
+ if (!entry.isDirectory())
745
+ continue;
746
+ const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
747
+ if (nextRel === '_vmz/pack' || nextRel.startsWith('_vmz/pack/'))
748
+ continue;
749
+ removeEmptyDirsBottomUp(abs, nextRel);
750
+ if (nextRel === '_vmz')
751
+ continue;
752
+ if (fs.readdirSync(path.join(abs, ...nextRel.split('/'))).length === 0) {
753
+ fs.rmdirSync(path.join(abs, ...nextRel.split('/')));
754
+ }
755
+ }
756
+ }
757
+ /**
758
+ * Prune the web-static delivery to its final browser set after HTML emit:
759
+ * remove the Node-only serve-host material (`_vmz/host/`, `vmz-serve-host.mjs`)
760
+ * and every unpacked module JS/map that `_vmz/pack/` chunks supersede
761
+ * (bootstrap entries, page/component/layout units, server faces, `#server/`,
762
+ * `ssr/`). The `/dom.browser.js` face closure stays — the packed navigation
763
+ * keeps that root-URL load by contract. JSON metadata and assets are untouched.
764
+ * Only `vmz serve` re-materializes the host (`materializeServeHostRuntime`);
765
+ * static hosting never needs it.
766
+ */
767
+ export function pruneWebStaticDelivery(outDir) {
768
+ const abs = path.resolve(outDir);
769
+ if (!fs.existsSync(abs))
770
+ throw new Error(`pruneWebStaticDelivery: missing dist ${abs}`);
771
+ const faceClosure = collectFaceClosure(abs);
772
+ let removed = 0;
773
+ for (const rel of listFilesRecursive(abs)) {
774
+ if (rel.startsWith('_vmz/pack/') || rel.startsWith('assets/'))
775
+ continue;
776
+ const isHost = rel.startsWith('_vmz/host/');
777
+ const isJs = rel.endsWith('.js') || rel.endsWith('.mjs') || rel.endsWith('.map');
778
+ if (!isHost && (!isJs || faceClosure.has(rel)))
779
+ continue;
780
+ fs.rmSync(path.join(abs, ...rel.split('/')), { force: true });
781
+ removed++;
782
+ }
783
+ // Node pack 是构建期产物:静态托管不会执行它,浏览器集已由 `_vmz/pack/`
784
+ // 定型。这里显式清掉 Node chunk 目录与 manifest,避免把服务端字节一起交付。
785
+ const nodePackDir = path.join(abs, ...PACK_NODE_OUTPUT_DIR_REL.split('/'));
786
+ if (fs.existsSync(nodePackDir)) {
787
+ for (const entry of fs.readdirSync(nodePackDir, { recursive: true, withFileTypes: true })) {
788
+ if (entry.isFile())
789
+ removed++;
790
+ }
791
+ fs.rmSync(nodePackDir, { recursive: true, force: true });
792
+ }
793
+ const nodeManifestPath = path.join(abs, ...PACK_NODE_MANIFEST_REL.split('/'));
794
+ if (fs.existsSync(nodeManifestPath)) {
795
+ fs.rmSync(nodeManifestPath, { force: true });
796
+ removed++;
797
+ }
798
+ removeEmptyDirsBottomUp(abs);
799
+ return { removed, keptFaces: [...faceClosure].sort() };
800
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Application authoring config surface — `defineConfig` + `VmzUserConfig`.
3
+ *
4
+ * Ownership: the application config entry belongs to the `vmz` package (CLI /
5
+ * workspace host). `@vmz/plugin` keeps the plugin author surface (`definePlugin`,
6
+ * protocol types). Plugin-facing types (`VmzPlugin`, `VmzEngines`) stay in
7
+ * `@vmz/plugin` and are referenced here by type import only.
8
+ */
9
+ import type { VmzEngines, VmzPlugin } from '@vmz/plugin';
10
+ /**
11
+ * Format options (`vmz format` reads `vmz.config` `format`).
12
+ *
13
+ * Scope is always the explicit CLI path; config can only narrow it. There is
14
+ * no `includes` / language toggle here — `vmz format <path>` formats the
15
+ * given file or directory and nothing else.
16
+ */
17
+ export interface VmzFormatConfig {
18
+ /** Skip paths under these globs inside an explicit directory target. */
19
+ excludes?: string[];
20
+ /** Biome-style formatter config (default: `biome.json` at project root). */
21
+ style?: string;
22
+ }
23
+ export interface VmzUserConfig {
24
+ plugins?: Array<string | VmzPlugin | Promise<VmzPlugin>>;
25
+ engines?: VmzEngines;
26
+ /** Format excludes / style (`vmz format` scope is the explicit CLI path). */
27
+ format?: VmzFormatConfig;
28
+ /** Application identity (optional authoring). */
29
+ application?: {
30
+ id?: string;
31
+ [key: string]: unknown;
32
+ };
33
+ /**
34
+ * Delivery: named profiles (`default` + `profiles`) or legacy site sugar.
35
+ * Pure data only; never a parallel `vmz.site.ts` entry.
36
+ */
37
+ delivery?: DeliveryAuthoring;
38
+ }
39
+ /** Site resource sources (embedded | filesystem | remote) — orthogonal to assembly. */
40
+ export interface SiteDeliveryAuthoring {
41
+ artifact: string;
42
+ siteId?: string;
43
+ sources: Array<{
44
+ id: string;
45
+ kind: 'embedded' | 'filesystem' | 'remote';
46
+ directory?: string;
47
+ baseUrl?: string;
48
+ artifact?: string;
49
+ trust?: string;
50
+ timeoutMs?: number;
51
+ priority?: number;
52
+ integrity?: unknown;
53
+ signaturePolicy?: string;
54
+ }>;
55
+ resolution?: {
56
+ mode?: 'release';
57
+ fallback?: string[];
58
+ };
59
+ activation?: 'atomic';
60
+ expectedCompatibility?: unknown;
61
+ failure?: unknown;
62
+ failurePolicy?: unknown;
63
+ update?: unknown;
64
+ updatePolicy?: unknown;
65
+ rollback?: unknown;
66
+ rollbackPolicy?: unknown;
67
+ security?: unknown;
68
+ securityPolicy?: unknown;
69
+ }
70
+ export type DeliveryAssembly = 'local-static' | 'web-static' | 'server-host' | 'cdn+server' | 'rust-embedded';
71
+ export type DeliveryServerRuntime = 'node' | 'worker' | 'deno' | 'bun' | 'rust-host';
72
+ export interface DeliveryProfileAuthoring {
73
+ host?: 'browser';
74
+ assembly: DeliveryAssembly;
75
+ /**
76
+ * Artifact directory under CLI `--out-dir` (default = profile id).
77
+ * Example: profile `static` + `name: 'cdn'` → `dist/cdn`.
78
+ */
79
+ name?: string;
80
+ serverRuntime?: DeliveryServerRuntime;
81
+ /** Inline site sources or `defineSite({...})`. */
82
+ sources?: SiteDeliveryAuthoring | SiteDeliveryAuthoring['sources'];
83
+ artifact?: string;
84
+ resolution?: SiteDeliveryAuthoring['resolution'];
85
+ activation?: SiteDeliveryAuthoring['activation'];
86
+ }
87
+ /** WeChat DevTools project identity. Pure data; not WeChat JSON / wx.* APIs. */
88
+ export interface WechatPackagingAuthoring {
89
+ /** Registered AppID. Omit for WeChat tourist mode (`touristappid`). */
90
+ appId?: string;
91
+ projectName?: string;
92
+ /** `app.json` `window.navigationBarTitleText`. */
93
+ title?: string;
94
+ }
95
+ /** Vendor packaging identity under `defineConfig({ delivery })`. */
96
+ export interface DeliveryPackagingAuthoring {
97
+ wechat?: WechatPackagingAuthoring;
98
+ }
99
+ /** Named profiles (preferred) or legacy site-only sugar. */
100
+ export type DeliveryAuthoring = {
101
+ default?: string;
102
+ profiles: Record<string, DeliveryProfileAuthoring>;
103
+ packaging?: DeliveryPackagingAuthoring;
104
+ } | (SiteDeliveryAuthoring & {
105
+ default?: string;
106
+ assembly?: DeliveryAssembly;
107
+ host?: 'browser';
108
+ serverRuntime?: DeliveryServerRuntime;
109
+ packaging?: DeliveryPackagingAuthoring;
110
+ });
111
+ /** Identity helper for `vmz.config.ts` inference. */
112
+ export declare function defineConfig(config: VmzUserConfig): VmzUserConfig;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Application authoring config surface — `defineConfig` + `VmzUserConfig`.
3
+ *
4
+ * Ownership: the application config entry belongs to the `vmz` package (CLI /
5
+ * workspace host). `@vmz/plugin` keeps the plugin author surface (`definePlugin`,
6
+ * protocol types). Plugin-facing types (`VmzPlugin`, `VmzEngines`) stay in
7
+ * `@vmz/plugin` and are referenced here by type import only.
8
+ */
9
+ /** Identity helper for `vmz.config.ts` inference. */
10
+ export function defineConfig(config) {
11
+ return config ?? {};
12
+ }
@@ -14,6 +14,7 @@
14
14
  { "src": "host/serve/hmr-dev.js", "out": "_vmz/host/serve/hmr-dev.js", "rewriteVmzRuntimeImport": true },
15
15
  { "src": "host/serve/page-render.js", "out": "_vmz/host/serve/page-render.js", "rewriteVmzRuntimeImport": true },
16
16
  { "src": "host/serve/page-catalog.js", "out": "_vmz/host/serve/page-catalog.js" },
17
+ { "src": "host/serve/pack-entry.js", "out": "_vmz/host/serve/pack-entry.js" },
17
18
  { "src": "host/serve/locale-theme.js", "out": "_vmz/host/serve/locale-theme.js" },
18
19
  { "src": "host/serve/constants.js", "out": "_vmz/host/serve/constants.js" },
19
20
  { "src": "host/serve/route-catalog.js", "out": "_vmz/host/serve/route-catalog.js" },
@@ -5,10 +5,11 @@
5
5
  "cli.cmd.build": "Build project via Workspace",
6
6
  "cli.cmd.serve": "Serve dist (optional --build)",
7
7
  "cli.cmd.dev": "Rebuild session",
8
- "cli.cmd.format": "Format workspace surfaces and .vmz via vmz.config format",
9
- "cli.opt.format-no-javascript": "Skip JS/TS workspace format",
10
- "cli.opt.format-no-rust": "Skip cargo fmt",
11
- "cli.opt.format-no-vmz": "Skip .vmz author format",
8
+ "cli.cmd.format": "Format an explicit file/directory path (JS/TS, .vmz, .rs with --rust)",
9
+ "cli.opt.format-rust": "Also format .rs files under a directory target via rustfmt",
10
+ "cli.opt.format-no-javascript": "Reject JS/TS targets",
11
+ "cli.opt.format-no-rust": "Reject .rs targets",
12
+ "cli.opt.format-no-vmz": "Skip .vmz targets",
12
13
  "cli.cmd.lint": "Lint (= check) via N-API",
13
14
  "cli.cmd.test": "Native test discover / report",
14
15
  "cli.cmd.document": "Project /documents domain",
package/package.json CHANGED
@@ -3,23 +3,23 @@
3
3
  "vmz": "./bin/vmz.js"
4
4
  },
5
5
  "dependencies": {
6
- "@vmz/commander": "0.3.11",
7
- "@vmz/core": "0.3.11",
8
- "@vmz/diagnostic": "0.3.11",
9
- "@vmz/plugin": "0.3.11",
10
- "@vmz/protocol": "0.3.11"
6
+ "@vmz/commander": "0.3.13",
7
+ "@vmz/core": "0.3.13",
8
+ "@vmz/diagnostic": "0.3.13",
9
+ "@vmz/plugin": "0.3.13",
10
+ "@vmz/protocol": "0.3.13"
11
11
  },
12
12
  "description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
13
13
  "devDependencies": {
14
14
  "@types/node": "^22.15.0",
15
- "@vmz/plugin": "0.3.11",
16
- "@vmz/plugin-katex": "0.3.11",
17
- "@vmz/plugin-markdown-it": "0.3.11",
18
- "@vmz/plugin-shiki": "0.3.11",
19
- "@vmz/test": "0.3.11",
15
+ "@vmz/plugin": "0.3.13",
16
+ "@vmz/plugin-katex": "0.3.13",
17
+ "@vmz/plugin-markdown-it": "0.3.13",
18
+ "@vmz/plugin-shiki": "0.3.13",
19
+ "@vmz/test": "0.3.13",
20
20
  "linkedom": "^0.18.13",
21
21
  "typescript": "^5.8.3",
22
- "vmz-plugin-conformance": "0.3.11"
22
+ "vmz-plugin-conformance": "0.3.13"
23
23
  },
24
24
  "engines": {
25
25
  "node": ">=24.12.0"
@@ -80,16 +80,16 @@
80
80
  "main": "./dist/index.js",
81
81
  "name": "@vmz/vmz",
82
82
  "optionalDependencies": {
83
- "@vmz/vmz-darwin-arm64": "0.3.11",
84
- "@vmz/vmz-darwin-x64": "0.3.11",
85
- "@vmz/vmz-linux-arm64": "0.3.11",
86
- "@vmz/vmz-linux-x64": "0.3.11",
87
- "@vmz/vmz-win32-arm64": "0.3.11",
88
- "@vmz/vmz-win32-x64": "0.3.11"
83
+ "@vmz/vmz-darwin-arm64": "0.3.13",
84
+ "@vmz/vmz-darwin-x64": "0.3.13",
85
+ "@vmz/vmz-linux-arm64": "0.3.13",
86
+ "@vmz/vmz-linux-x64": "0.3.13",
87
+ "@vmz/vmz-win32-arm64": "0.3.13",
88
+ "@vmz/vmz-win32-x64": "0.3.13"
89
89
  },
90
90
  "peerDependencies": {
91
- "@vmz/plugin-markdown-it": "0.3.11",
92
- "@vmz/test": "0.3.11",
91
+ "@vmz/plugin-markdown-it": "0.3.13",
92
+ "@vmz/test": "0.3.13",
93
93
  "typescript": "^5.8.3"
94
94
  },
95
95
  "peerDependenciesMeta": {
@@ -122,5 +122,5 @@
122
122
  },
123
123
  "type": "module",
124
124
  "types": "./dist/index.d.ts",
125
- "version": "0.3.11"
125
+ "version": "0.3.13"
126
126
  }
@@ -1,32 +0,0 @@
1
- /**
2
- * Browser-safe client package lowering (Pack stage thin slice).
3
- *
4
- * Bare npm/workspace imports are legal on the author surface (01/04).
5
- * Browser ESM cannot resolve them. Pack materializes reachable package
6
- * modules under `dist/vendor/<pkg>/…` and rewrites importers to relative paths
7
- * via a bounded rewrite of generated module syntax. Pack must not re-enter the
8
- * parser pipeline for every emitted JavaScript file.
9
- *
10
- * Not full oxc chunk-split/minify (`oxc-pending` remains for release minify).
11
- */
12
- /**
13
- * @param {string} outDir
14
- * @param {{ projectRoot?: string | null }} [opts]
15
- * @returns {{ rewrittenFiles: number, vendoredModules: string[], bareSpecs: string[] }}
16
- */
17
- interface PackClientBareImportsOpts {
18
- projectRoot?: string | null;
19
- }
20
- export declare function packClientBareImports(outDir: string, opts?: PackClientBareImportsOpts): {
21
- rewrittenFiles: number;
22
- vendoredModules: string[];
23
- bareSpecs: any[];
24
- unresolvedBareSpecs: any[];
25
- skippedVmzExports: any[];
26
- remainingBareSpecs: any[];
27
- };
28
- /** @param {string} js */
29
- export declare function collectBareSpecs(js: any): unknown[];
30
- /** oxc AST module specifier rewrite (`.ts`→`.js` + optional exact map). */
31
- export declare function rewriteRelativeTsSpecs(js: any, exactMap?: any): any;
32
- export {};