@vmz/vmz 0.1.21 → 0.1.22

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/index.js CHANGED
@@ -127,6 +127,7 @@ export const SERVE_HOST_RUNTIME_FILES = [
127
127
  ['deployment-registry.js', 'deployment-registry.js'],
128
128
  ['render-host.js', 'render-host.js'],
129
129
  ['route-layout-chain.js', 'route-layout-chain.js'],
130
+ ['localize-body-links.js', 'localize-body-links.js'],
130
131
  ];
131
132
  /** Copy serve-host + registry bootstrap modules from `@vmz/core` into app outDir. */
132
133
  export function materializeServeHostRuntime(outDir, coreDist = resolveCoreRuntimeDist()) {
@@ -130,17 +130,9 @@ export declare function parseLocaleFromPath(pathname: string, supportedLocales:
130
130
  localeId: string;
131
131
  restPath: string;
132
132
  };
133
- export interface LocaleHrefArtifact {
134
- locales?: Array<{
135
- id: string;
136
- } | string>;
137
- defaultLocale?: string;
138
- routing?: LocaleRoutingOpts;
139
- }
140
- /** Rewrite a same-app href to the given LocaleId via route realization. */
141
- export declare function localizeSameAppHref(href: string, localeId: string, artifact: LocaleHrefArtifact): string;
142
- /** Rewrite `<a data-vmz-route href>` in HTML body to retain `localeId`. */
143
- export declare function localizeBodyLinks(html: string, localeId: string, artifact: LocaleHrefArtifact, escapeAttr?: (s: string) => string): string;
133
+ export type { LocaleHrefArtifact } from '@vmz/core/localize-body-links';
134
+ /** Rewrite a same-app href — single implementation in `@vmz/core/localize-body-links`. */
135
+ export { localizeBodyLinks, localizeSameAppHref } from '@vmz/core/localize-body-links';
144
136
  /** Plan redirect / negotiation for an incoming URL (omit-prefix aware). */
145
137
  export declare function planLocalePathNavigation(input: {
146
138
  pathname: string;
@@ -228,58 +228,8 @@ export function parseLocaleFromPath(pathname, supportedLocales) {
228
228
  }
229
229
  return { localeId: null, restPath: normalizePath(pathname) };
230
230
  }
231
- /** Rewrite a same-app href to the given LocaleId via route realization. */
232
- export function localizeSameAppHref(href, localeId, artifact) {
233
- if (!href || !localeId || !artifact)
234
- return href;
235
- if (href.startsWith('#') || /^(mailto|tel|javascript):/i.test(href))
236
- return href;
237
- if (/^[a-z][a-z0-9+.-]*:/i.test(href) && !href.startsWith('/'))
238
- return href;
239
- let pathname = String(href);
240
- let search = '';
241
- let hash = '';
242
- const hashIdx = pathname.indexOf('#');
243
- if (hashIdx >= 0) {
244
- hash = pathname.slice(hashIdx);
245
- pathname = pathname.slice(0, hashIdx);
246
- }
247
- const qIdx = pathname.indexOf('?');
248
- if (qIdx >= 0) {
249
- search = pathname.slice(qIdx);
250
- pathname = pathname.slice(0, qIdx);
251
- }
252
- if (!pathname)
253
- pathname = '/';
254
- const supported = (artifact.locales || []).map((l) => (typeof l === 'string' ? l : l.id)).filter(Boolean);
255
- const defaultLocale = artifact.defaultLocale || artifact.routing?.defaultLocale;
256
- const routing = {
257
- strategy: artifact.routing?.strategy || 'prefix',
258
- defaultPrefix: artifact.routing?.defaultPrefix || 'include',
259
- defaultLocale,
260
- };
261
- const parsed = parseLocaleFromPath(pathname, supported);
262
- const rest = parsed.restPath || '/';
263
- const realized = realizeRoutePath(localeId, rest, routing);
264
- return `${realized.path}${search}${hash}`;
265
- }
266
- /** Rewrite `<a data-vmz-route href>` in HTML body to retain `localeId`. */
267
- export function localizeBodyLinks(html, localeId, artifact, escapeAttr = (s) => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;')) {
268
- if (!html || !localeId || !artifact)
269
- return html;
270
- return String(html).replace(/<a\b([^>]*)>/gi, (full, attrs) => {
271
- if (!/\bdata-vmz-route\s*=/.test(attrs))
272
- return full;
273
- const hm = attrs.match(/\bhref\s*=\s*"([^"]*)"/i);
274
- if (!hm)
275
- return full;
276
- const next = localizeSameAppHref(hm[1], localeId, artifact);
277
- if (next === hm[1])
278
- return full;
279
- const newAttrs = attrs.replace(/\bhref\s*=\s*"[^"]*"/i, `href="${escapeAttr(next)}"`);
280
- return `<a${newAttrs}>`;
281
- });
282
- }
231
+ /** Rewrite a same-app href — single implementation in `@vmz/core/localize-body-links`. */
232
+ export { localizeBodyLinks, localizeSameAppHref } from '@vmz/core/localize-body-links';
283
233
  /** Plan redirect / negotiation for an incoming URL (omit-prefix aware). */
284
234
  export function planLocalePathNavigation(input) {
285
235
  const routing = {
@@ -6,7 +6,7 @@ export declare function isRouteBoundaryStem(stem: string): boolean;
6
6
  export declare function isRouteGroupDir(seg: string): boolean;
7
7
  /**
8
8
  * File-route fallback (`pages/home` → `/home`, `pages/index` → `/`).
9
- * Used only when a deployment unit has no `pathPattern`.
9
+ * Kept for tooling diagnostics only — production hosts must use Plan `pathPattern`.
10
10
  */
11
11
  export declare function filePathPatternFromChunk(chunkId: string): string;
12
12
  export type DeploymentPageUnit = {
@@ -17,7 +17,7 @@ export type DeploymentPageUnit = {
17
17
  pathPattern?: string;
18
18
  routeId?: string;
19
19
  };
20
- /** Canonical Browser HTTP pattern for a page unit. Mini must not read this. */
20
+ /** Canonical Browser HTTP pattern for a page unit. Mini must not read this. Requires Plan `pathPattern`. */
21
21
  export declare function unitBrowserPathPattern(unit: DeploymentPageUnit | null | undefined): string;
22
22
  export type PathSeg = {
23
23
  kind: 'static';
@@ -10,7 +10,7 @@ export function isRouteGroupDir(seg) {
10
10
  }
11
11
  /**
12
12
  * File-route fallback (`pages/home` → `/home`, `pages/index` → `/`).
13
- * Used only when a deployment unit has no `pathPattern`.
13
+ * Kept for tooling diagnostics only — production hosts must use Plan `pathPattern`.
14
14
  */
15
15
  export function filePathPatternFromChunk(chunkId) {
16
16
  const rel = String(chunkId || '').replace(/^pages\//, '');
@@ -28,12 +28,14 @@ export function filePathPatternFromChunk(chunkId) {
28
28
  }
29
29
  return segs.length ? `/${segs.join('/')}` : '/';
30
30
  }
31
- /** Canonical Browser HTTP pattern for a page unit. Mini must not read this. */
31
+ /** Canonical Browser HTTP pattern for a page unit. Mini must not read this. Requires Plan `pathPattern`. */
32
32
  export function unitBrowserPathPattern(unit) {
33
33
  const explicit = String(unit?.pathPattern || '').trim();
34
- if (explicit)
35
- return explicit.startsWith('/') ? explicit : `/${explicit}`;
36
- return filePathPatternFromChunk(String(unit?.chunkId || ''));
34
+ if (!explicit) {
35
+ const chunkId = String(unit?.chunkId || '');
36
+ throw new Error(`unitBrowserPathPattern: page unit ${chunkId || '(unknown)'} missing pathPattern (plan-only host)`);
37
+ }
38
+ return explicit.startsWith('/') ? explicit : `/${explicit}`;
37
39
  }
38
40
  export function parsePathPattern(pattern) {
39
41
  const raw = String(pattern || '').trim();
@@ -1,19 +1,10 @@
1
1
  /**
2
- * P4 ServerArtifact — compiled route decision tree + public ServerRoute contracts
3
- * + internal capability units + selected runtime adapter. Web Standards Fetch entry.
2
+ * P4 ServerArtifact — thin host write of Rust/N-API normalized Plan.
3
+ * Assembly / digests / adapters live in vmz-artifacts via N-API.
4
4
  */
5
5
  export declare const SERVER_ARTIFACT_SCHEMA = "vmz.server.artifact.v0";
6
6
  export declare const HTTP_CONTRACT_SCHEMA = "vmz.http.contract.v0";
7
7
  export declare const SERVER_RUNTIME_ADAPTER_SCHEMA = "vmz.server.runtime_adapter.v0";
8
- /**
9
- * @param {string} outDir
10
- * @param {{
11
- * profileId?: string | null,
12
- * assembly?: string | null,
13
- * serverRuntime?: string | null,
14
- * packDigest?: string | null,
15
- * }} [opts]
16
- */
17
8
  interface ServerArtifactOpts {
18
9
  profileId?: string | null;
19
10
  assembly?: string | null;
@@ -42,6 +33,7 @@ interface ServerArtifactBody {
42
33
  packDigest: string | null;
43
34
  adapters: Record<string, unknown>;
44
35
  artifactDigest?: string;
36
+ [key: string]: unknown;
45
37
  }
46
38
  export declare function emitServerArtifact(outDir: string, opts?: ServerArtifactOpts): {
47
39
  artifact: ServerArtifactBody;
@@ -49,47 +41,7 @@ export declare function emitServerArtifact(outDir: string, opts?: ServerArtifact
49
41
  httpContractDigest: string;
50
42
  };
51
43
  /**
52
- * @param {Record<string, any>} artifact
53
- * @param {string} adapterId
44
+ * Project a runtime adapter document from a normalized artifact (N-API).
54
45
  */
55
- export declare function projectServerRuntimeAdapter(artifact: any, adapterId: any): {
56
- host: string;
57
- invoke: string;
58
- status: string;
59
- schema: string;
60
- adapterId: string;
61
- artifactDigest: any;
62
- httpContractDigest: any;
63
- spaFallback: boolean;
64
- entry: any;
65
- publicRouteCount: any;
66
- internalCapabilityCount: any;
67
- } | {
68
- host: string;
69
- invoke: string;
70
- status: string;
71
- note: string;
72
- schema: string;
73
- adapterId: string;
74
- artifactDigest: any;
75
- httpContractDigest: any;
76
- spaFallback: boolean;
77
- entry: any;
78
- publicRouteCount: any;
79
- internalCapabilityCount: any;
80
- } | {
81
- host: string;
82
- invoke: string;
83
- status: string;
84
- note: string;
85
- consumes: string[];
86
- schema: string;
87
- adapterId: string;
88
- artifactDigest: any;
89
- httpContractDigest: any;
90
- spaFallback: boolean;
91
- entry: any;
92
- publicRouteCount: any;
93
- internalCapabilityCount: any;
94
- };
46
+ export declare function projectServerRuntimeAdapter(artifact: Record<string, unknown>, adapterId: string): any;
95
47
  export {};
@@ -1,109 +1,30 @@
1
1
  /**
2
- * P4 ServerArtifact — compiled route decision tree + public ServerRoute contracts
3
- * + internal capability units + selected runtime adapter. Web Standards Fetch entry.
2
+ * P4 ServerArtifact — thin host write of Rust/N-API normalized Plan.
3
+ * Assembly / digests / adapters live in vmz-artifacts via N-API.
4
4
  */
5
- import crypto from 'node:crypto';
6
5
  import { existsSync, mkdirSync, readFileSync } from 'node:fs';
7
6
  import path from 'node:path';
8
- import { SERVER_RUNTIMES } from './delivery-profile.js';
7
+ import { requireNativeAddon } from './native-addon.js';
9
8
  import { writePrettyJsonFile } from './pretty-json.js';
10
9
  export const SERVER_ARTIFACT_SCHEMA = 'vmz.server.artifact.v0';
11
10
  export const HTTP_CONTRACT_SCHEMA = 'vmz.http.contract.v0';
12
11
  export const SERVER_RUNTIME_ADAPTER_SCHEMA = 'vmz.server.runtime_adapter.v0';
13
- const DEFAULT_RPC_PATH = '/__vmz/rpc';
14
12
  export function emitServerArtifact(outDir, opts = {}) {
15
- const deployment = readJson(path.join(outDir, 'vmz-deployment.json')) || { schema: null, units: [] };
16
- const routes = readJson(path.join(outDir, 'vmz-routes.json'));
17
- const routeRows = Array.isArray(routes) ? routes : [];
18
- const selectedRuntime = normalizeRuntime(opts.serverRuntime);
19
- const units = Array.isArray(deployment.units) ? deployment.units : [];
20
- const publicRoutes = routeRows.map((r) => ({
21
- verb: String(r.verb || 'GET').toUpperCase(),
22
- path: String(r.path || ''),
23
- moduleId: String(r.moduleId || ''),
24
- method: String(r.method || ''),
25
- className: r.className != null ? String(r.className) : null,
26
- visibility: 'public',
27
- kind: 'server-route',
28
- }));
29
- const publicKeys = new Set(publicRoutes.map((r) => `${r.moduleId}::${r.method}`));
30
- /** @type {Array<Record<string, unknown>>} */
31
- const internalCapabilities = [];
32
- for (const u of units) {
33
- const moduleId = u.serverModuleId != null ? String(u.serverModuleId) : '';
34
- if (!moduleId)
35
- continue;
36
- const caps = Array.isArray(u.capabilities) ? u.capabilities.map(String) : [];
37
- for (const method of caps) {
38
- const key = `${moduleId}::${method}`;
39
- if (publicKeys.has(key))
40
- continue;
41
- internalCapabilities.push({
42
- chunkId: String(u.chunkId || ''),
43
- moduleId,
44
- method,
45
- visibility: 'internal',
46
- kind: 'capability',
47
- });
48
- }
49
- }
50
- const routeDecisionTree = [
51
- {
52
- id: 'rpc',
53
- match: { method: 'POST', path: DEFAULT_RPC_PATH },
54
- action: 'invoke-rpc',
55
- visibility: 'internal-transport',
56
- },
57
- ...publicRoutes.map((r, i) => ({
58
- id: `public-route-${i}`,
59
- match: { method: r.verb, path: r.path },
60
- action: 'invoke-server-route',
61
- target: { moduleId: r.moduleId, method: r.method },
62
- visibility: 'public',
63
- })),
64
- ];
65
- const httpContractBody = {
66
- schema: HTTP_CONTRACT_SCHEMA,
67
- rpcPath: DEFAULT_RPC_PATH,
68
- publicRoutes: publicRoutes.map((r) => ({
69
- verb: r.verb,
70
- path: r.path,
71
- moduleId: r.moduleId,
72
- method: r.method,
73
- })),
74
- internalCapabilityCount: internalCapabilities.length,
75
- entry: 'fetch',
76
- };
77
- const httpContractDigest = sha256Hex(canonicalJson(httpContractBody));
78
- const artifact = {
79
- schema: SERVER_ARTIFACT_SCHEMA,
80
- profileId: opts.profileId || null,
81
- assembly: opts.assembly || null,
82
- selectedRuntime,
83
- entry: {
84
- kind: 'fetch',
85
- standards: ['Request', 'Response', 'Streams', 'AbortSignal'],
86
- rpcPath: DEFAULT_RPC_PATH,
87
- },
88
- httpContract: {
89
- schema: HTTP_CONTRACT_SCHEMA,
90
- digest: httpContractDigest,
91
- },
92
- publicRoutes,
93
- internalCapabilities,
94
- middlewareUnits: [],
95
- routeDecisionTree,
96
- deploymentSchema: deployment.schema || null,
97
- packDigest: opts.packDigest || null,
98
- adapters: {
99
- node: { kind: 'node-http', status: 'runtime', entry: 'handleNodeRequest' },
100
- worker: { kind: 'fetch', status: 'runtime', entry: 'handleFetchRequest' },
101
- deno: { kind: 'fetch', status: 'projected', entry: 'handleFetchRequest' },
102
- bun: { kind: 'fetch', status: 'projected', entry: 'handleFetchRequest' },
103
- 'rust-host': { kind: 'contract-projection', status: 'projected', entry: 'fetch' },
104
- },
105
- };
106
- artifact.artifactDigest = sha256Hex(canonicalJson({ ...artifact, artifactDigest: undefined }));
13
+ const deploymentPath = path.join(outDir, 'vmz-deployment.json');
14
+ const routesPath = path.join(outDir, 'vmz-routes.json');
15
+ const deploymentJson = existsSync(deploymentPath) ? readFileSync(deploymentPath, 'utf8') : '{}';
16
+ const routesJson = existsSync(routesPath) ? readFileSync(routesPath, 'utf8') : '[]';
17
+ const native = requireNativeAddon();
18
+ if (typeof native.normalizeServerArtifactJson !== 'function') {
19
+ throw new Error('vmz native addon missing normalizeServerArtifactJson — rebuild with `pnpm napi:build`');
20
+ }
21
+ const artifactJson = native.normalizeServerArtifactJson(deploymentJson, routesJson, {
22
+ profileId: opts.profileId ?? null,
23
+ assembly: opts.assembly ?? null,
24
+ serverRuntime: opts.serverRuntime ?? null,
25
+ packDigest: opts.packDigest ?? null,
26
+ });
27
+ const artifact = JSON.parse(artifactJson);
107
28
  const vmzDir = path.join(outDir, '_vmz');
108
29
  mkdirSync(vmzDir, { recursive: true });
109
30
  const file = path.join(vmzDir, 'server-artifact.json');
@@ -116,79 +37,19 @@ export function emitServerArtifact(outDir, opts = {}) {
116
37
  mkdirSync(dir, { recursive: true });
117
38
  writePrettyJsonFile(path.join(dir, 'adapter.json'), projection);
118
39
  }
119
- return { artifact, path: file, httpContractDigest };
40
+ return {
41
+ artifact,
42
+ path: file,
43
+ httpContractDigest: artifact.httpContract?.digest || null,
44
+ };
120
45
  }
121
46
  /**
122
- * @param {Record<string, any>} artifact
123
- * @param {string} adapterId
47
+ * Project a runtime adapter document from a normalized artifact (N-API).
124
48
  */
125
49
  export function projectServerRuntimeAdapter(artifact, adapterId) {
126
- const id = String(adapterId || '').trim();
127
- if (!SERVER_RUNTIMES.includes(id) && id !== 'worker') {
128
- throw new Error(`projectServerRuntimeAdapter: unknown adapter ${id}`);
129
- }
130
- const base = {
131
- schema: SERVER_RUNTIME_ADAPTER_SCHEMA,
132
- adapterId: id,
133
- artifactDigest: artifact.artifactDigest,
134
- httpContractDigest: artifact.httpContract?.digest || null,
135
- spaFallback: false,
136
- entry: artifact.entry,
137
- publicRouteCount: Array.isArray(artifact.publicRoutes) ? artifact.publicRoutes.length : 0,
138
- internalCapabilityCount: Array.isArray(artifact.internalCapabilities) ? artifact.internalCapabilities.length : 0,
139
- };
140
- if (id === 'node') {
141
- return { ...base, host: 'node:http', invoke: 'handleNodeRequest', status: 'runtime' };
142
- }
143
- if (id === 'worker' || id === 'deno' || id === 'bun') {
144
- return {
145
- ...base,
146
- host: 'fetch',
147
- invoke: 'handleFetchRequest',
148
- status: id === 'worker' ? 'runtime' : 'projected',
149
- note: id === 'worker'
150
- ? 'Fetch entry; live thin gated via worker-shaped subprocess host'
151
- : 'Fetch contract projection; live runtime not gated',
152
- };
50
+ const native = requireNativeAddon();
51
+ if (typeof native.projectServerRuntimeAdapterJson !== 'function') {
52
+ throw new Error('vmz native addon missing projectServerRuntimeAdapterJson — rebuild with `pnpm napi:build`');
153
53
  }
154
- // rust-host
155
- return {
156
- ...base,
157
- host: 'rust-fetch-consumer',
158
- invoke: 'fetch',
159
- status: 'projected',
160
- note: 'contract projection only — live Rust host binary parity not gated',
161
- consumes: ['server-artifact.json', 'vmz-routes.json', 'vmz-deployment.json'],
162
- };
163
- }
164
- function normalizeRuntime(raw) {
165
- const v = String(raw || 'node').trim();
166
- return SERVER_RUNTIMES.includes(v) ? v : 'node';
167
- }
168
- function readJson(file) {
169
- if (!existsSync(file))
170
- return null;
171
- try {
172
- return JSON.parse(readFileSync(file, 'utf8'));
173
- }
174
- catch {
175
- return null;
176
- }
177
- }
178
- function canonicalJson(value) {
179
- return JSON.stringify(sortKeys(value));
180
- }
181
- function sortKeys(value) {
182
- if (Array.isArray(value))
183
- return value.map(sortKeys);
184
- if (value && typeof value === 'object') {
185
- const out = {};
186
- for (const k of Object.keys(value).sort())
187
- out[k] = sortKeys(value[k]);
188
- return out;
189
- }
190
- return value;
191
- }
192
- function sha256Hex(text) {
193
- return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
54
+ return JSON.parse(native.projectServerRuntimeAdapterJson(JSON.stringify(artifact), String(adapterId || '').trim()));
194
55
  }
@@ -15,7 +15,7 @@ import { absoluteUrl, buildLocalePageMeta, localizeBodyLinks } from './locale-ro
15
15
  import { requireNativeAddon } from './native-addon.js';
16
16
  import { writePrettyJsonFile } from './pretty-json.js';
17
17
  import { emitPublicStaticAssets } from './public-static-assets.js';
18
- import { filePathPatternFromChunk, isRouteBoundaryStem, listPublicPageUnits, parsePathPattern, unitBrowserPathPattern } from './route-path.js';
18
+ import { listPublicPageUnits, parsePathPattern, unitBrowserPathPattern } from './route-path.js';
19
19
  import { emitSiteFavicon, readSiteFaviconHeadHtml } from './site-favicon.js';
20
20
  export const STATIC_DELIVERY_MANIFEST_SCHEMA = 'vmz.static.delivery_manifest.v0';
21
21
  export async function emitWebStatic(distDir, opts = {}) {
@@ -265,53 +265,25 @@ function sortKeys(value) {
265
265
  */
266
266
  function listPageClientFiles(distDir) {
267
267
  const fromDep = listPagesFromDeployment(distDir);
268
- if (fromDep.length)
269
- return fromDep;
270
- const root = path.join(distDir, 'pages');
271
- /** @type {Array<{ chunkId: string, segs: ReturnType<typeof parsePathPattern>, pathPattern: string }>} */
272
- const out = [];
273
- function walk(abs, relParts) {
274
- let ents;
275
- try {
276
- ents = fs.readdirSync(abs, { withFileTypes: true });
277
- }
278
- catch {
279
- return;
280
- }
281
- for (const e of ents) {
282
- if (e.isDirectory())
283
- walk(path.join(abs, e.name), [...relParts, e.name]);
284
- else if (e.isFile() && e.name.endsWith('.client.js')) {
285
- const stem = e.name.replace(/\.client\.js$/, '');
286
- if (isRouteBoundaryStem(stem))
287
- continue;
288
- const chunkId = ['pages', ...relParts, stem].join('/');
289
- const pathPattern = filePathPatternFromChunk(chunkId);
290
- out.push({ chunkId, pathPattern, segs: parsePathPattern(pathPattern) });
291
- }
292
- }
268
+ if (!fromDep.length) {
269
+ throw new Error(`emitWebStatic: no page units with pathPattern in ${path.join(distDir, 'vmz-deployment.json')} (plan-only host)`);
293
270
  }
294
- walk(root, []);
295
- return out;
271
+ return fromDep;
296
272
  }
297
273
  /**
298
274
  * @param {string} distDir
299
275
  */
300
276
  function listPagesFromDeployment(distDir) {
301
277
  const deploymentPath = path.join(distDir, 'vmz-deployment.json');
302
- if (!fs.existsSync(deploymentPath))
303
- return [];
304
- try {
305
- const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
306
- return listPublicPageUnits(deployment).map((u) => {
307
- const chunkId = String(u.chunkId || '').replace(/\\/g, '/');
308
- const pathPattern = unitBrowserPathPattern(u);
309
- return { chunkId, pathPattern, segs: parsePathPattern(pathPattern) };
310
- });
311
- }
312
- catch {
313
- return [];
278
+ if (!fs.existsSync(deploymentPath)) {
279
+ throw new Error(`emitWebStatic: missing ${deploymentPath} (plan-only host)`);
314
280
  }
281
+ const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
282
+ return listPublicPageUnits(deployment).map((u) => {
283
+ const chunkId = String(u.chunkId || '').replace(/\\/g, '/');
284
+ const pathPattern = unitBrowserPathPattern(u);
285
+ return { chunkId, pathPattern, segs: parsePathPattern(pathPattern) };
286
+ });
315
287
  }
316
288
  /**
317
289
  * @param {ReturnType<typeof parsePathPattern>} segs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/vmz",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "type": "module",
5
5
  "description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
6
6
  "license": "MIT",
@@ -48,16 +48,16 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@vmz/commander": "0.1.21",
52
- "@vmz/core": "0.1.21",
53
- "@vmz/diagnostic": "0.1.21",
54
- "@vmz/plugin": "0.1.21",
55
- "@vmz/protocol": "0.1.21",
51
+ "@vmz/commander": "0.1.22",
52
+ "@vmz/core": "0.1.22",
53
+ "@vmz/diagnostic": "0.1.22",
54
+ "@vmz/plugin": "0.1.22",
55
+ "@vmz/protocol": "0.1.22",
56
56
  "jiti": "^2.6.1"
57
57
  },
58
58
  "peerDependencies": {
59
- "@vmz/plugin-markdown-it": "0.1.21",
60
- "@vmz/test": "0.1.21",
59
+ "@vmz/plugin-markdown-it": "0.1.22",
60
+ "@vmz/test": "0.1.22",
61
61
  "typescript": "^5.8.3"
62
62
  },
63
63
  "peerDependenciesMeta": {
@@ -92,12 +92,12 @@
92
92
  "cli"
93
93
  ],
94
94
  "optionalDependencies": {
95
- "@vmz/vmz-win32-x64": "0.1.21",
96
- "@vmz/vmz-win32-arm64": "0.1.21",
97
- "@vmz/vmz-darwin-x64": "0.1.21",
98
- "@vmz/vmz-darwin-arm64": "0.1.21",
99
- "@vmz/vmz-linux-x64": "0.1.21",
100
- "@vmz/vmz-linux-arm64": "0.1.21"
95
+ "@vmz/vmz-win32-x64": "0.1.22",
96
+ "@vmz/vmz-win32-arm64": "0.1.22",
97
+ "@vmz/vmz-darwin-x64": "0.1.22",
98
+ "@vmz/vmz-darwin-arm64": "0.1.22",
99
+ "@vmz/vmz-linux-x64": "0.1.22",
100
+ "@vmz/vmz-linux-arm64": "0.1.22"
101
101
  },
102
102
  "publishConfig": {
103
103
  "access": "public"