@vmz/vmz 0.0.3 → 0.1.0

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/README.md +6 -4
  2. package/dist/build-assemble.d.ts +52 -0
  3. package/dist/build-assemble.js +191 -0
  4. package/dist/cdn-policy.d.ts +196 -0
  5. package/dist/cdn-policy.js +443 -0
  6. package/dist/cli.js +152 -11
  7. package/dist/content-addressed-assets.d.ts +69 -0
  8. package/dist/content-addressed-assets.js +206 -0
  9. package/dist/delivery-profile.d.ts +74 -0
  10. package/dist/delivery-profile.js +279 -0
  11. package/dist/dev-session.js +49 -13
  12. package/dist/document-build.js +9 -4
  13. package/dist/document-designs.js +30 -2
  14. package/dist/document-enrich.js +8 -0
  15. package/dist/embedded-packaging.d.ts +22 -0
  16. package/dist/embedded-packaging.js +113 -0
  17. package/dist/index.d.ts +19 -3
  18. package/dist/index.js +35 -15
  19. package/dist/invocation.d.ts +8 -31
  20. package/dist/invocation.js +12 -33
  21. package/dist/locale-check.d.ts +16 -0
  22. package/dist/locale-check.js +131 -3
  23. package/dist/locale-cmd.js +2 -2
  24. package/dist/locale-route-emit.d.ts +34 -0
  25. package/dist/locale-route-emit.js +134 -0
  26. package/dist/locale-router.d.ts +20 -0
  27. package/dist/locale-router.js +68 -0
  28. package/dist/log.d.ts +2 -2
  29. package/dist/log.js +11 -3
  30. package/dist/pack.d.ts +40 -0
  31. package/dist/pack.js +108 -0
  32. package/dist/plugin-host.d.ts +10 -1
  33. package/dist/plugin-host.js +19 -2
  34. package/dist/port.d.ts +10 -0
  35. package/dist/port.js +46 -0
  36. package/dist/production-observability.d.ts +286 -0
  37. package/dist/production-observability.js +469 -0
  38. package/dist/production-test-pack.d.ts +144 -0
  39. package/dist/production-test-pack.js +447 -0
  40. package/dist/release-cmd.d.ts +8 -0
  41. package/dist/release-cmd.js +126 -0
  42. package/dist/release-pack.d.ts +96 -0
  43. package/dist/release-pack.js +346 -0
  44. package/dist/server-artifact.d.ts +140 -0
  45. package/dist/server-artifact.js +205 -0
  46. package/dist/server-language-backend.d.ts +89 -0
  47. package/dist/server-language-backend.js +121 -0
  48. package/dist/site-delivery.d.ts +134 -0
  49. package/dist/site-delivery.js +345 -0
  50. package/dist/static-emit.d.ts +145 -0
  51. package/dist/static-emit.js +577 -0
  52. package/package.json +13 -13
@@ -0,0 +1,134 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Emit RouteId × LocaleId realization (+ PageMeta / hreflang seed) into dist/_vmz.
4
+ * Consumed by serve-host + static-emit; LocaleId stays out of stable RouteId.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { checkLocales, localeHasErrors } from './locale-check.js';
9
+ import { buildLocalePageMeta, buildLocaleRouteRealizationTable } from './locale-router.js';
10
+ export const LOCALE_ROUTE_REALIZATION_ARTIFACT_SCHEMA = 'vmz.locale.route_realization.v0';
11
+ /**
12
+ * @param {string} chunkId
13
+ */
14
+ function pathPatternFromChunk(chunkId) {
15
+ const rel = String(chunkId || '').replace(/^pages\//, '');
16
+ const parts = rel.split('/').filter(Boolean);
17
+ const segs = [];
18
+ for (let i = 0; i < parts.length; i++) {
19
+ const p = parts[i];
20
+ if (p === 'index' && i === parts.length - 1)
21
+ continue;
22
+ segs.push(p);
23
+ }
24
+ return segs.length ? `/${segs.join('/')}` : '/';
25
+ }
26
+ /**
27
+ * Layouts are not public RouteNodes for locale realization / SEO.
28
+ * @param {string} chunkId
29
+ */
30
+ function isPublicPageChunk(chunkId) {
31
+ const id = String(chunkId || '');
32
+ if (!id.startsWith('pages/'))
33
+ return false;
34
+ if (/(^|\/)Layout$/.test(id))
35
+ return false;
36
+ return true;
37
+ }
38
+ /**
39
+ * @param {string} projectRoot
40
+ * @param {string} distDir
41
+ * @param {{ origin?: string }} [opts]
42
+ */
43
+ export function emitLocaleRouteRealization(projectRoot, distDir, opts = {}) {
44
+ const report = checkLocales({ projectRoot, checkUnused: false });
45
+ if (localeHasErrors(report)) {
46
+ return { ok: false, written: [], diagnostics: report.diagnostics || [], artifact: null };
47
+ }
48
+ // Missing /locales is warning (not error) — still never silent; no realization artifact yet.
49
+ if (!report.manifest) {
50
+ return { ok: true, written: [], artifact: null, diagnostics: report.diagnostics || [] };
51
+ }
52
+ const deploymentPath = path.join(distDir, 'vmz-deployment.json');
53
+ if (!fs.existsSync(deploymentPath)) {
54
+ return {
55
+ ok: false,
56
+ written: [],
57
+ diagnostics: [{ severity: 'error', code: 'locale.route.missing_deployment', message: 'missing vmz-deployment.json' }],
58
+ artifact: null,
59
+ };
60
+ }
61
+ const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
62
+ const pages = (deployment.units || []).filter((u) => u.kind === 'page' && isPublicPageChunk(String(u.chunkId)));
63
+ const routes = pages.map((u) => ({
64
+ routeId: String(u.chunkId),
65
+ path: pathPatternFromChunk(String(u.chunkId)),
66
+ }));
67
+ const localeEntries = report.manifest?.locales || [];
68
+ const locales = localeEntries.map((l) => l.id);
69
+ const directions = Object.fromEntries(localeEntries.map((l) => [l.id, l.direction || 'ltr']));
70
+ const defaultLocale = report.manifest?.defaultLocale;
71
+ const routing = report.manifest?.routing || { strategy: 'prefix', defaultPrefix: 'include' };
72
+ const table = buildLocaleRouteRealizationTable({
73
+ routes,
74
+ locales,
75
+ defaultLocale,
76
+ routing,
77
+ });
78
+ if (table.status === 'failed') {
79
+ return { ok: false, written: [], diagnostics: table.diagnostics || [], artifact: null };
80
+ }
81
+ const origin = String(opts.origin || process.env.VMZ_SITE_ORIGIN || 'https://example.test').replace(/\/$/, '');
82
+ /** @type {any[]} */
83
+ const pageMetas = [];
84
+ for (const route of routes) {
85
+ for (const loc of locales) {
86
+ const meta = buildLocalePageMeta({
87
+ routeId: route.routeId,
88
+ localeId: loc,
89
+ direction: directions[loc],
90
+ title: route.routeId,
91
+ origin,
92
+ realizations: table.realizations,
93
+ locales,
94
+ defaultLocale,
95
+ });
96
+ pageMetas.push(meta);
97
+ }
98
+ }
99
+ const artifact = {
100
+ schema: LOCALE_ROUTE_REALIZATION_ARTIFACT_SCHEMA,
101
+ defaultLocale,
102
+ locales: localeEntries.map((l) => ({
103
+ id: l.id,
104
+ label: l.label || l.id,
105
+ direction: l.direction || 'ltr',
106
+ })),
107
+ routing: {
108
+ strategy: routing.strategy || 'prefix',
109
+ defaultPrefix: routing.defaultPrefix || 'include',
110
+ defaultLocale,
111
+ },
112
+ origin,
113
+ routes,
114
+ realizations: table.realizations,
115
+ pageMetas,
116
+ };
117
+ const vmzDir = path.join(distDir, '_vmz');
118
+ fs.mkdirSync(vmzDir, { recursive: true });
119
+ const outPath = path.join(vmzDir, 'locale-route-realization.json');
120
+ fs.writeFileSync(outPath, `${JSON.stringify(artifact, null, 2)}\n`, 'utf8');
121
+ const manifestOut = path.join(vmzDir, 'locale-manifest.json');
122
+ fs.writeFileSync(manifestOut, `${JSON.stringify({
123
+ schema: 'vmz.locale.manifest.v0',
124
+ defaultLocale,
125
+ locales: artifact.locales,
126
+ routing: artifact.routing,
127
+ }, null, 2)}\n`, 'utf8');
128
+ return {
129
+ ok: true,
130
+ written: ['_vmz/locale-route-realization.json', '_vmz/locale-manifest.json'],
131
+ artifact,
132
+ diagnostics: [],
133
+ };
134
+ }
@@ -116,6 +116,26 @@ export declare function parseLocaleFromPath(pathname: any, supportedLocales: any
116
116
  localeId: string;
117
117
  restPath: string;
118
118
  };
119
+ /**
120
+ * Rewrite a same-app href to the given LocaleId via route realization.
121
+ * Stable path (no locale) is recovered first, then re-realized — Link never hardcodes locale.
122
+ * @param {string} href
123
+ * @param {string} localeId
124
+ * @param {{
125
+ * locales?: Array<{ id: string }|string>,
126
+ * defaultLocale?: string,
127
+ * routing?: { strategy?: string, defaultPrefix?: string, defaultLocale?: string },
128
+ * }} artifact
129
+ */
130
+ export declare function localizeSameAppHref(href: any, localeId: any, artifact: any): any;
131
+ /**
132
+ * Rewrite `<a data-vmz-route href>` in HTML body to retain `localeId`.
133
+ * @param {string} html
134
+ * @param {string} localeId
135
+ * @param {Parameters<typeof localizeSameAppHref>[2]} artifact
136
+ * @param {(s: string) => string} [escapeAttr]
137
+ */
138
+ export declare function localizeBodyLinks(html: any, localeId: any, artifact: any, escapeAttr?: (s: any) => string): any;
119
139
  /**
120
140
  * Plan redirect / negotiation for an incoming URL (omit-prefix aware).
121
141
  * @param {{
@@ -260,6 +260,74 @@ export function parseLocaleFromPath(pathname, supportedLocales) {
260
260
  }
261
261
  return { localeId: null, restPath: normalizePath(pathname) };
262
262
  }
263
+ /**
264
+ * Rewrite a same-app href to the given LocaleId via route realization.
265
+ * Stable path (no locale) is recovered first, then re-realized — Link never hardcodes locale.
266
+ * @param {string} href
267
+ * @param {string} localeId
268
+ * @param {{
269
+ * locales?: Array<{ id: string }|string>,
270
+ * defaultLocale?: string,
271
+ * routing?: { strategy?: string, defaultPrefix?: string, defaultLocale?: string },
272
+ * }} artifact
273
+ */
274
+ export function localizeSameAppHref(href, localeId, artifact) {
275
+ if (!href || !localeId || !artifact)
276
+ return href;
277
+ if (href.startsWith('#') || /^(mailto|tel|javascript):/i.test(href))
278
+ return href;
279
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href) && !href.startsWith('/'))
280
+ return href;
281
+ let pathname = String(href);
282
+ let search = '';
283
+ let hash = '';
284
+ const hashIdx = pathname.indexOf('#');
285
+ if (hashIdx >= 0) {
286
+ hash = pathname.slice(hashIdx);
287
+ pathname = pathname.slice(0, hashIdx);
288
+ }
289
+ const qIdx = pathname.indexOf('?');
290
+ if (qIdx >= 0) {
291
+ search = pathname.slice(qIdx);
292
+ pathname = pathname.slice(0, qIdx);
293
+ }
294
+ if (!pathname)
295
+ pathname = '/';
296
+ const supported = (artifact.locales || []).map((l) => (typeof l === 'string' ? l : l.id)).filter(Boolean);
297
+ const defaultLocale = artifact.defaultLocale || artifact.routing?.defaultLocale;
298
+ const routing = {
299
+ strategy: artifact.routing?.strategy || 'prefix',
300
+ defaultPrefix: artifact.routing?.defaultPrefix || 'include',
301
+ defaultLocale,
302
+ };
303
+ const parsed = parseLocaleFromPath(pathname, supported);
304
+ const rest = parsed.restPath || '/';
305
+ const realized = realizeRoutePath(localeId, rest, routing);
306
+ return `${realized.path}${search}${hash}`;
307
+ }
308
+ /**
309
+ * Rewrite `<a data-vmz-route href>` in HTML body to retain `localeId`.
310
+ * @param {string} html
311
+ * @param {string} localeId
312
+ * @param {Parameters<typeof localizeSameAppHref>[2]} artifact
313
+ * @param {(s: string) => string} [escapeAttr]
314
+ */
315
+ export function localizeBodyLinks(html, localeId, artifact, escapeAttr = (s) => String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;')) {
316
+ if (!html || !localeId || !artifact)
317
+ return html;
318
+ return String(html).replace(/<a\b([^>]*)>/gi, (full, attrs) => {
319
+ if (!/\bdata-vmz-route\s*=/.test(attrs))
320
+ return full;
321
+ const hm = attrs.match(/\bhref\s*=\s*"([^"]*)"/i);
322
+ if (!hm)
323
+ return full;
324
+ const next = localizeSameAppHref(hm[1], localeId, artifact);
325
+ if (next === hm[1])
326
+ return full;
327
+ const newAttrs = attrs.replace(/\bhref\s*=\s*"[^"]*"/i, `href="${escapeAttr(next)}"`);
328
+ return `<a${newAttrs}>`;
329
+ });
330
+ }
263
331
  /**
264
332
  * Plan redirect / negotiation for an incoming URL (omit-prefix aware).
265
333
  * @param {{
package/dist/log.d.ts CHANGED
@@ -8,10 +8,10 @@ export declare const log: {
8
8
  warn(...args: any[]): void;
9
9
  /** @param {...unknown} args */
10
10
  error(...args: any[]): void;
11
- /** @param {{ severity: string, path: string, message: string }} d */
11
+ /** @param {{ severity: string, path?: string, message: string, code?: string }} d */
12
12
  diagnostic(d: any): void;
13
13
  /**
14
- * @param {Array<{ severity: string, path: string, message: string }>} diagnostics
14
+ * @param {Array<{ severity: string, path?: string, message: string, code?: string }>} diagnostics
15
15
  * @param {{ denyWarnings?: boolean }} [opts]
16
16
  * @returns {number} failing count (errors, and warnings if denyWarnings)
17
17
  */
package/dist/log.js CHANGED
@@ -19,12 +19,20 @@ export const log = {
19
19
  error(...args) {
20
20
  console.error(stamp('error'), ...args);
21
21
  },
22
- /** @param {{ severity: string, path: string, message: string }} d */
22
+ /** @param {{ severity: string, path?: string, message: string, code?: string }} d */
23
23
  diagnostic(d) {
24
- console.error(`${d.severity}: ${d.path}: ${d.message}`);
24
+ const sev = d.severity || 'error';
25
+ const code = d.code ? `${d.code}: ` : '';
26
+ const loc = d.path ? ` (${d.path})` : '';
27
+ // Prefer `vmz warn|error CODE: message (path)` so locale warnings are visible, not silent.
28
+ if (sev === 'warning') {
29
+ console.error(`${stamp('warn')} ${code}${d.message}${loc}`);
30
+ return;
31
+ }
32
+ console.error(`${stamp(sev === 'error' ? 'error' : sev)} ${code}${d.path ? `${d.path}: ` : ''}${d.message}`);
25
33
  },
26
34
  /**
27
- * @param {Array<{ severity: string, path: string, message: string }>} diagnostics
35
+ * @param {Array<{ severity: string, path?: string, message: string, code?: string }>} diagnostics
28
36
  * @param {{ denyWarnings?: boolean }} [opts]
29
37
  * @returns {number} failing count (errors, and warnings if denyWarnings)
30
38
  */
package/dist/pack.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * B4 — Pack stage: consume Deployment IR (VPG-owned units), emit pack manifest.
3
+ * Full oxc minify/chunk-split lands progressively; this stage always runs and
4
+ * records integrity digests so Assemble/Prove never skip the pack contract.
5
+ */
6
+ export declare const PACK_MANIFEST_SCHEMA = "vmz.pack.manifest.v0";
7
+ /**
8
+ * Ensure dom split companions sit next to vmz-dom.js (barrel imports ./dom-core.js).
9
+ * Always refresh from `@vmz/core` when present so SSR/runtime fixes are not sticky in outDir.
10
+ * @param {string} outDir
11
+ * @param {string | null | undefined} coreDist `@vmz/core` dist root
12
+ * @returns {string[]} copied relative names
13
+ */
14
+ export declare function ensureRuntimeCompanions(outDir: any, coreDist: any): any[];
15
+ /**
16
+ * @param {string} outDir
17
+ * @param {{
18
+ * release?: boolean,
19
+ * profileId?: string,
20
+ * assembly?: string,
21
+ * preferredClientFace?: string,
22
+ * coreDist?: string | null,
23
+ * }} [opts]
24
+ */
25
+ export declare function packFromDeploymentIr(outDir: any, opts?: {}): {
26
+ manifest: {
27
+ schema: string;
28
+ profileId: any;
29
+ assembly: any;
30
+ release: boolean;
31
+ preferredClientFace: any;
32
+ deploymentSchema: any;
33
+ unitCount: number;
34
+ units: any[];
35
+ minify: string;
36
+ treeShakeBasis: string;
37
+ bundler: string;
38
+ };
39
+ path: string;
40
+ };
package/dist/pack.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * B4 — Pack stage: consume Deployment IR (VPG-owned units), emit pack manifest.
3
+ * Full oxc minify/chunk-split lands progressively; this stage always runs and
4
+ * records integrity digests so Assemble/Prove never skip the pack contract.
5
+ */
6
+ // @ts-nocheck
7
+ import crypto from 'node:crypto';
8
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
+ import path from 'node:path';
10
+ import { loadDeploymentIr, planBundleInputs } from './bundler-adapter.js';
11
+ export const PACK_MANIFEST_SCHEMA = 'vmz.pack.manifest.v0';
12
+ /**
13
+ * Ensure dom split companions sit next to vmz-dom.js (barrel imports ./dom-core.js).
14
+ * Always refresh from `@vmz/core` when present so SSR/runtime fixes are not sticky in outDir.
15
+ * @param {string} outDir
16
+ * @param {string | null | undefined} coreDist `@vmz/core` dist root
17
+ * @returns {string[]} copied relative names
18
+ */
19
+ export function ensureRuntimeCompanions(outDir, coreDist) {
20
+ if (!coreDist)
21
+ return [];
22
+ const names = ['dom-core.js', 'dom-ssr.js', 'dom.client.js'];
23
+ const copied = [];
24
+ for (const name of names) {
25
+ const src = path.join(coreDist, name);
26
+ if (!existsSync(src))
27
+ continue;
28
+ const dest = path.join(outDir, name);
29
+ copyFileSync(src, dest);
30
+ copied.push(name);
31
+ }
32
+ return copied;
33
+ }
34
+ /**
35
+ * @param {string} outDir
36
+ * @param {{
37
+ * release?: boolean,
38
+ * profileId?: string,
39
+ * assembly?: string,
40
+ * preferredClientFace?: string,
41
+ * coreDist?: string | null,
42
+ * }} [opts]
43
+ */
44
+ export function packFromDeploymentIr(outDir, opts = {}) {
45
+ ensureRuntimeCompanions(outDir, opts.coreDist);
46
+ const ir = loadDeploymentIr(outDir);
47
+ const inputs = planBundleInputs(outDir, ir);
48
+ const units = [];
49
+ for (const entry of inputs) {
50
+ const abs = entry.entry;
51
+ let digest = null;
52
+ let bytes = 0;
53
+ let present = false;
54
+ if (existsSync(abs)) {
55
+ present = true;
56
+ const buf = readFileSync(abs);
57
+ bytes = buf.length;
58
+ digest = crypto.createHash('sha256').update(buf).digest('hex');
59
+ }
60
+ units.push({
61
+ chunkId: entry.chunkId,
62
+ kind: entry.kind,
63
+ entry: path.relative(outDir, abs).replace(/\\/g, '/'),
64
+ programIr: path.relative(outDir, entry.programIr).replace(/\\/g, '/'),
65
+ source: entry.source,
66
+ present,
67
+ bytes,
68
+ digest,
69
+ rebuilt: Boolean(entry.rebuilt),
70
+ });
71
+ }
72
+ const body = {
73
+ schema: PACK_MANIFEST_SCHEMA,
74
+ profileId: opts.profileId || null,
75
+ assembly: opts.assembly || null,
76
+ release: Boolean(opts.release),
77
+ preferredClientFace: opts.preferredClientFace || '@vmz/core/dom/client',
78
+ deploymentSchema: ir.schema,
79
+ unitCount: units.length,
80
+ units,
81
+ minify: opts.release ? 'oxc-pending' : 'dev-identity',
82
+ treeShakeBasis: 'vpg-deployment-ir',
83
+ bundler: 'vmz-pack',
84
+ };
85
+ body.packDigest = sha256Hex(stableStringify({ ...body }));
86
+ const vmzDir = path.join(outDir, '_vmz');
87
+ mkdirSync(vmzDir, { recursive: true });
88
+ const file = path.join(vmzDir, 'pack-manifest.json');
89
+ writeFileSync(file, `${JSON.stringify(body, null, 2)}\n`, 'utf8');
90
+ return { manifest: body, path: file };
91
+ }
92
+ function stableStringify(value) {
93
+ return JSON.stringify(sortKeys(value));
94
+ }
95
+ function sortKeys(value) {
96
+ if (Array.isArray(value))
97
+ return value.map(sortKeys);
98
+ if (value && typeof value === 'object') {
99
+ const out = {};
100
+ for (const k of Object.keys(value).sort())
101
+ out[k] = sortKeys(value[k]);
102
+ return out;
103
+ }
104
+ return value;
105
+ }
106
+ function sha256Hex(text) {
107
+ return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
108
+ }
@@ -12,11 +12,20 @@ export declare function importMaybeTs(full: any): Promise<any>;
12
12
  /**
13
13
  * Load `vmz.config.*` from project root (+ optional root `vmz.plugin.*`).
14
14
  * @param {string} project
15
- * @returns {Promise<{ plugins: import('@vmz/plugin').VmzPlugin[], engines: import('@vmz/plugin').VmzEngines, path: string | null, pluginPath: string | null }>}
15
+ * @returns {Promise<{
16
+ * plugins: import('@vmz/plugin').VmzPlugin[],
17
+ * engines: import('@vmz/plugin').VmzEngines,
18
+ * delivery: import('@vmz/plugin').DeliveryAuthoring | null,
19
+ * application: { id?: string } | null,
20
+ * path: string | null,
21
+ * pluginPath: string | null,
22
+ * }>}
16
23
  */
17
24
  export declare function loadVmzConfig(project: any): Promise<{
18
25
  plugins: any[];
19
26
  engines: {};
27
+ delivery: any;
28
+ application: any;
20
29
  path: any;
21
30
  pluginPath: any;
22
31
  }>;
@@ -32,13 +32,24 @@ export async function importMaybeTs(full) {
32
32
  /**
33
33
  * Load `vmz.config.*` from project root (+ optional root `vmz.plugin.*`).
34
34
  * @param {string} project
35
- * @returns {Promise<{ plugins: import('@vmz/plugin').VmzPlugin[], engines: import('@vmz/plugin').VmzEngines, path: string | null, pluginPath: string | null }>}
35
+ * @returns {Promise<{
36
+ * plugins: import('@vmz/plugin').VmzPlugin[],
37
+ * engines: import('@vmz/plugin').VmzEngines,
38
+ * delivery: import('@vmz/plugin').DeliveryAuthoring | null,
39
+ * application: { id?: string } | null,
40
+ * path: string | null,
41
+ * pluginPath: string | null,
42
+ * }>}
36
43
  */
37
44
  export async function loadVmzConfig(project) {
38
45
  /** @type {import('@vmz/plugin').VmzPlugin[]} */
39
46
  const plugins = [];
40
47
  /** @type {import('@vmz/plugin').VmzEngines} */
41
48
  let engines = {};
49
+ /** @type {import('@vmz/plugin').DeliveryAuthoring | null} */
50
+ let delivery = null;
51
+ /** @type {{ id?: string } | null} */
52
+ let application = null;
42
53
  /** @type {string | null} */
43
54
  let configPath = null;
44
55
  /** @type {string | null} */
@@ -51,6 +62,12 @@ export async function loadVmzConfig(project) {
51
62
  const cfg = await importMaybeTs(full);
52
63
  const raw = cfg?.plugins ?? [];
53
64
  engines = cfg?.engines && typeof cfg.engines === 'object' ? { ...cfg.engines } : {};
65
+ if (cfg?.delivery && typeof cfg.delivery === 'object') {
66
+ delivery = cfg.delivery;
67
+ }
68
+ if (cfg?.application && typeof cfg.application === 'object') {
69
+ application = cfg.application;
70
+ }
54
71
  for (const entry of raw) {
55
72
  plugins.push(await resolvePluginEntry(project, entry));
56
73
  }
@@ -64,7 +81,7 @@ export async function loadVmzConfig(project) {
64
81
  plugins.push(await resolvePluginEntry(project, full));
65
82
  break;
66
83
  }
67
- return { plugins, engines, path: configPath, pluginPath };
84
+ return { plugins, engines, delivery, application, path: configPath, pluginPath };
68
85
  }
69
86
  /**
70
87
  * @param {string} project
package/dist/port.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Probe for a free TCP port starting at `start` (inclusive).
3
+ * Used by `vmz dev` when `--port` is omitted.
4
+ *
5
+ * @param {string} host
6
+ * @param {number} [start=5173]
7
+ * @param {number} [maxTries=50]
8
+ * @returns {Promise<number>}
9
+ */
10
+ export declare function findAvailablePort(host: any, start?: number, maxTries?: number): Promise<unknown>;
package/dist/port.js ADDED
@@ -0,0 +1,46 @@
1
+ // @ts-nocheck
2
+ import net from 'node:net';
3
+ /**
4
+ * Probe for a free TCP port starting at `start` (inclusive).
5
+ * Used by `vmz dev` when `--port` is omitted.
6
+ *
7
+ * @param {string} host
8
+ * @param {number} [start=5173]
9
+ * @param {number} [maxTries=50]
10
+ * @returns {Promise<number>}
11
+ */
12
+ export function findAvailablePort(host, start = 5173, maxTries = 50) {
13
+ const first = Number(start);
14
+ if (!Number.isFinite(first) || first <= 0) {
15
+ return Promise.reject(new Error(`invalid start port: ${start}`));
16
+ }
17
+ return new Promise((resolve, reject) => {
18
+ let port = first;
19
+ const attempt = () => {
20
+ if (port > first + maxTries) {
21
+ reject(new Error(`no free port in ${first}..${first + maxTries} on ${host}`));
22
+ return;
23
+ }
24
+ const server = net.createServer();
25
+ server.unref();
26
+ server.once('error', (err) => {
27
+ if (err && err.code === 'EADDRINUSE') {
28
+ port += 1;
29
+ attempt();
30
+ return;
31
+ }
32
+ reject(err);
33
+ });
34
+ server.once('listening', () => {
35
+ server.close((closeErr) => {
36
+ if (closeErr)
37
+ reject(closeErr);
38
+ else
39
+ resolve(port);
40
+ });
41
+ });
42
+ server.listen(port, host);
43
+ };
44
+ attempt();
45
+ });
46
+ }