@vmz/vmz 0.0.4 → 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.
- package/dist/build-assemble.d.ts +52 -0
- package/dist/build-assemble.js +191 -0
- package/dist/cdn-policy.d.ts +22 -4
- package/dist/cdn-policy.js +109 -10
- package/dist/cli.js +106 -24
- package/dist/delivery-profile.d.ts +74 -0
- package/dist/delivery-profile.js +279 -0
- package/dist/dev-session.js +2 -1
- package/dist/document-build.js +9 -4
- package/dist/document-enrich.js +8 -0
- package/dist/embedded-packaging.d.ts +22 -0
- package/dist/embedded-packaging.js +113 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +8 -1
- package/dist/locale-check.js +11 -8
- package/dist/locale-cmd.js +2 -2
- package/dist/locale-route-emit.d.ts +34 -0
- package/dist/locale-route-emit.js +134 -0
- package/dist/locale-router.d.ts +20 -0
- package/dist/locale-router.js +68 -0
- package/dist/log.d.ts +2 -2
- package/dist/log.js +11 -3
- package/dist/pack.d.ts +40 -0
- package/dist/pack.js +108 -0
- package/dist/plugin-host.d.ts +1 -1
- package/dist/plugin-host.js +2 -2
- package/dist/production-test-pack.d.ts +0 -14
- package/dist/production-test-pack.js +23 -28
- package/dist/release-pack.js +12 -3
- package/dist/server-artifact.d.ts +140 -0
- package/dist/server-artifact.js +205 -0
- package/dist/server-language-backend.d.ts +89 -0
- package/dist/server-language-backend.js +121 -0
- package/dist/static-emit.d.ts +10 -1
- package/dist/static-emit.js +140 -26
- package/package.json +12 -12
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit RouteId × LocaleId realization (+ PageMeta / hreflang seed) into dist/_vmz.
|
|
3
|
+
* Consumed by serve-host + static-emit; LocaleId stays out of stable RouteId.
|
|
4
|
+
*/
|
|
5
|
+
export declare const LOCALE_ROUTE_REALIZATION_ARTIFACT_SCHEMA = "vmz.locale.route_realization.v0";
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} projectRoot
|
|
8
|
+
* @param {string} distDir
|
|
9
|
+
* @param {{ origin?: string }} [opts]
|
|
10
|
+
*/
|
|
11
|
+
export declare function emitLocaleRouteRealization(projectRoot: any, distDir: any, opts?: {}): {
|
|
12
|
+
ok: boolean;
|
|
13
|
+
written: any[];
|
|
14
|
+
diagnostics: any;
|
|
15
|
+
artifact: any;
|
|
16
|
+
} | {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
written: string[];
|
|
19
|
+
artifact: {
|
|
20
|
+
schema: string;
|
|
21
|
+
defaultLocale: any;
|
|
22
|
+
locales: any;
|
|
23
|
+
routing: {
|
|
24
|
+
strategy: any;
|
|
25
|
+
defaultPrefix: any;
|
|
26
|
+
defaultLocale: any;
|
|
27
|
+
};
|
|
28
|
+
origin: string;
|
|
29
|
+
routes: any;
|
|
30
|
+
realizations: any[];
|
|
31
|
+
pageMetas: any[];
|
|
32
|
+
};
|
|
33
|
+
diagnostics: any[];
|
|
34
|
+
};
|
|
@@ -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
|
+
}
|
package/dist/locale-router.d.ts
CHANGED
|
@@ -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 {{
|
package/dist/locale-router.js
CHANGED
|
@@ -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, '&').replace(/"/g, '"')) {
|
|
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
|
|
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
|
|
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
|
|
22
|
+
/** @param {{ severity: string, path?: string, message: string, code?: string }} d */
|
|
23
23
|
diagnostic(d) {
|
|
24
|
-
|
|
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
|
|
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
|
+
}
|
package/dist/plugin-host.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export declare function importMaybeTs(full: any): Promise<any>;
|
|
|
15
15
|
* @returns {Promise<{
|
|
16
16
|
* plugins: import('@vmz/plugin').VmzPlugin[],
|
|
17
17
|
* engines: import('@vmz/plugin').VmzEngines,
|
|
18
|
-
* delivery: import('@vmz/plugin').
|
|
18
|
+
* delivery: import('@vmz/plugin').DeliveryAuthoring | null,
|
|
19
19
|
* application: { id?: string } | null,
|
|
20
20
|
* path: string | null,
|
|
21
21
|
* pluginPath: string | null,
|
package/dist/plugin-host.js
CHANGED
|
@@ -35,7 +35,7 @@ export async function importMaybeTs(full) {
|
|
|
35
35
|
* @returns {Promise<{
|
|
36
36
|
* plugins: import('@vmz/plugin').VmzPlugin[],
|
|
37
37
|
* engines: import('@vmz/plugin').VmzEngines,
|
|
38
|
-
* delivery: import('@vmz/plugin').
|
|
38
|
+
* delivery: import('@vmz/plugin').DeliveryAuthoring | null,
|
|
39
39
|
* application: { id?: string } | null,
|
|
40
40
|
* path: string | null,
|
|
41
41
|
* pluginPath: string | null,
|
|
@@ -46,7 +46,7 @@ export async function loadVmzConfig(project) {
|
|
|
46
46
|
const plugins = [];
|
|
47
47
|
/** @type {import('@vmz/plugin').VmzEngines} */
|
|
48
48
|
let engines = {};
|
|
49
|
-
/** @type {import('@vmz/plugin').
|
|
49
|
+
/** @type {import('@vmz/plugin').DeliveryAuthoring | null} */
|
|
50
50
|
let delivery = null;
|
|
51
51
|
/** @type {{ id?: string } | null} */
|
|
52
52
|
let application = null;
|
|
@@ -19,8 +19,6 @@ export declare function browserProductionScenarioPack(): {
|
|
|
19
19
|
runner: string;
|
|
20
20
|
required: boolean;
|
|
21
21
|
detail?: undefined;
|
|
22
|
-
quarantine?: undefined;
|
|
23
|
-
reason?: undefined;
|
|
24
22
|
} | {
|
|
25
23
|
scenarioId: string;
|
|
26
24
|
category: string;
|
|
@@ -29,18 +27,6 @@ export declare function browserProductionScenarioPack(): {
|
|
|
29
27
|
runner: string;
|
|
30
28
|
required: boolean;
|
|
31
29
|
detail: string;
|
|
32
|
-
quarantine?: undefined;
|
|
33
|
-
reason?: undefined;
|
|
34
|
-
} | {
|
|
35
|
-
scenarioId: string;
|
|
36
|
-
category: string;
|
|
37
|
-
fixture: any;
|
|
38
|
-
modes: string[];
|
|
39
|
-
runner: string;
|
|
40
|
-
required: boolean;
|
|
41
|
-
quarantine: boolean;
|
|
42
|
-
reason: string;
|
|
43
|
-
detail?: undefined;
|
|
44
30
|
})[];
|
|
45
31
|
};
|
|
46
32
|
/** Deterministic CI profile for production-test (no flaky disguise). */
|
|
@@ -126,56 +126,51 @@ export function browserProductionScenarioPack() {
|
|
|
126
126
|
required: true,
|
|
127
127
|
detail: 'pack digest + CURRENT/PREVIOUS rollback',
|
|
128
128
|
},
|
|
129
|
-
//
|
|
129
|
+
// Previously quarantined — now required with real serve-host/browser runners.
|
|
130
130
|
{
|
|
131
131
|
scenarioId: 'production.ui.field.submit',
|
|
132
132
|
category: 'field',
|
|
133
|
-
fixture:
|
|
133
|
+
fixture: 'packages/examples/production-inspector',
|
|
134
134
|
modes: ['browser'],
|
|
135
|
-
runner: '
|
|
136
|
-
required:
|
|
137
|
-
|
|
138
|
-
reason: 'A4: Field input/submit/dup-submit/cancel not assembled (@vmz/ui UI1)',
|
|
135
|
+
runner: 'serve-host-browser',
|
|
136
|
+
required: true,
|
|
137
|
+
detail: 'Field input + validation error on inspector',
|
|
139
138
|
},
|
|
140
139
|
{
|
|
141
140
|
scenarioId: 'production.ui.dialog.focus',
|
|
142
141
|
category: 'dialog',
|
|
143
|
-
fixture:
|
|
142
|
+
fixture: 'packages/examples/production-inspector',
|
|
144
143
|
modes: ['browser'],
|
|
145
|
-
runner: '
|
|
146
|
-
required:
|
|
147
|
-
|
|
148
|
-
reason: 'A4: Dialog focus enter/loop/restore + outside dismiss not assembled',
|
|
144
|
+
runner: 'serve-host-browser',
|
|
145
|
+
required: true,
|
|
146
|
+
detail: 'Dialog open/focus/Escape dismiss on inspector',
|
|
149
147
|
},
|
|
150
148
|
{
|
|
151
149
|
scenarioId: 'production.locale.switch-rtl',
|
|
152
150
|
category: 'locale',
|
|
153
|
-
fixture:
|
|
151
|
+
fixture: 'packages/examples/production-router',
|
|
154
152
|
modes: ['browser'],
|
|
155
|
-
runner: '
|
|
156
|
-
required:
|
|
157
|
-
|
|
158
|
-
reason: 'A4: locale switch / fallback forbidden / RTL not in this pack',
|
|
153
|
+
runner: 'serve-host-browser',
|
|
154
|
+
required: true,
|
|
155
|
+
detail: 'LocaleTransition commit + inspector RTL dir toggle',
|
|
159
156
|
},
|
|
160
157
|
{
|
|
161
158
|
scenarioId: 'production.theme.missing-token',
|
|
162
159
|
category: 'theme',
|
|
163
|
-
fixture:
|
|
164
|
-
modes: ['
|
|
165
|
-
runner: '
|
|
166
|
-
required:
|
|
167
|
-
|
|
168
|
-
reason: 'A4: theme switch + missing semantic token not in this pack',
|
|
160
|
+
fixture: 'temp:missing-token',
|
|
161
|
+
modes: ['compile'],
|
|
162
|
+
runner: 'vmz-build',
|
|
163
|
+
required: true,
|
|
164
|
+
detail: 'missing semantic token → build fails unknown_design_token',
|
|
169
165
|
},
|
|
170
166
|
{
|
|
171
167
|
scenarioId: 'production.mount.child-failure',
|
|
172
168
|
category: 'mount',
|
|
173
|
-
fixture:
|
|
174
|
-
modes: ['
|
|
175
|
-
runner: '
|
|
176
|
-
required:
|
|
177
|
-
|
|
178
|
-
reason: 'A4: ApplicationMount child failure isolation not in this pack',
|
|
169
|
+
fixture: 'temp:application-isolation',
|
|
170
|
+
modes: ['deployment'],
|
|
171
|
+
runner: 'application-isolation',
|
|
172
|
+
required: true,
|
|
173
|
+
detail: 'ApplicationMount child failure → 503 application_unavailable; siblings survive',
|
|
179
174
|
},
|
|
180
175
|
],
|
|
181
176
|
};
|
package/dist/release-pack.js
CHANGED
|
@@ -241,16 +241,25 @@ export function publishRelease(releasesRoot, distDir, envelope) {
|
|
|
241
241
|
if (!digest)
|
|
242
242
|
throw new Error('publishRelease: envelope missing artifactDigest');
|
|
243
243
|
const root = path.resolve(releasesRoot);
|
|
244
|
+
const srcDist = path.resolve(distDir);
|
|
245
|
+
// Node fs.cpSync refuses copying a directory into any subdirectory of itself.
|
|
246
|
+
// Releases root must sit beside dist (e.g. .vmz-releases), never under dist/.
|
|
244
247
|
const dest = path.join(root, digest);
|
|
248
|
+
const destDist = path.join(dest, 'dist');
|
|
249
|
+
if (root === srcDist || root.startsWith(srcDist + path.sep) || destDist.startsWith(srcDist + path.sep)) {
|
|
250
|
+
throw new Error(`publishRelease: releasesRoot must not be under distDir (got releasesRoot=${root}, distDir=${srcDist})`);
|
|
251
|
+
}
|
|
245
252
|
fs.mkdirSync(dest, { recursive: true });
|
|
246
253
|
// Immutable snapshot of packed dist (exclude prior releases nesting).
|
|
247
|
-
const destDist = path.join(dest, 'dist');
|
|
248
254
|
fs.rmSync(destDist, { recursive: true, force: true });
|
|
249
|
-
fs.cpSync(
|
|
255
|
+
fs.cpSync(srcDist, destDist, {
|
|
250
256
|
recursive: true,
|
|
251
257
|
filter: (src) => {
|
|
252
258
|
const n = src.replace(/\\/g, '/');
|
|
253
|
-
return !n.includes('/.vmz-releases/') &&
|
|
259
|
+
return (!n.includes('/.vmz-releases/') &&
|
|
260
|
+
!n.includes('/.vmz-cdn-releases/') &&
|
|
261
|
+
!n.includes('/releases-cdn/') &&
|
|
262
|
+
!/\/dist\/releases(\/|$)/.test(n));
|
|
254
263
|
},
|
|
255
264
|
});
|
|
256
265
|
writeJson(path.join(dest, 'envelope.json'), envelope);
|