@vmz/vmz 0.0.3 → 0.0.4
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/README.md +6 -4
- package/dist/cdn-policy.d.ts +178 -0
- package/dist/cdn-policy.js +344 -0
- package/dist/cli.js +65 -6
- package/dist/content-addressed-assets.d.ts +69 -0
- package/dist/content-addressed-assets.js +206 -0
- package/dist/dev-session.js +48 -13
- package/dist/document-designs.js +30 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +27 -14
- package/dist/invocation.d.ts +8 -31
- package/dist/invocation.js +12 -33
- package/dist/locale-check.d.ts +16 -0
- package/dist/locale-check.js +125 -0
- package/dist/plugin-host.d.ts +10 -1
- package/dist/plugin-host.js +19 -2
- package/dist/port.d.ts +10 -0
- package/dist/port.js +46 -0
- package/dist/production-observability.d.ts +286 -0
- package/dist/production-observability.js +469 -0
- package/dist/production-test-pack.d.ts +158 -0
- package/dist/production-test-pack.js +452 -0
- package/dist/release-cmd.d.ts +8 -0
- package/dist/release-cmd.js +126 -0
- package/dist/release-pack.d.ts +96 -0
- package/dist/release-pack.js +337 -0
- package/dist/site-delivery.d.ts +134 -0
- package/dist/site-delivery.js +345 -0
- package/dist/static-emit.d.ts +136 -0
- package/dist/static-emit.js +463 -0
- package/package.json +13 -13
package/dist/invocation.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* `vmz` CLI invocation modes (JS gate only; Rust binary stays full).
|
|
4
4
|
*
|
|
5
|
+
* Product install face: workspace `vmz` / publish `@vmz/vmz` (bin still `vmz`).
|
|
6
|
+
*
|
|
5
7
|
* Three modes — do not collapse them:
|
|
6
8
|
* - **developer**: monorepo source checkout (`packages/runtimes/vmz`, not under node_modules)
|
|
7
|
-
* - **project**: app's `node_modules/vmz` or `node_modules/@vmz/vmz`
|
|
9
|
+
* - **project**: app's nearest `node_modules/vmz` or `node_modules/@vmz/vmz`
|
|
8
10
|
* - **global**: npm/pnpm global (or any install under node_modules that is not the nearest project one)
|
|
9
11
|
*
|
|
10
12
|
*/
|
|
@@ -13,8 +15,9 @@ import { existsSync, realpathSync } from 'node:fs';
|
|
|
13
15
|
import path from 'node:path';
|
|
14
16
|
import { fileURLToPath } from 'node:url';
|
|
15
17
|
/** @typedef {'developer' | 'project' | 'global'} InvocationMode */
|
|
18
|
+
const PROJECT_PKG_SEGMENTS = [['@vmz', 'vmz'], ['vmz']];
|
|
16
19
|
/**
|
|
17
|
-
* Package root of the running `vmz` / `@vmz/vmz` install
|
|
20
|
+
* Package root of the running `vmz` / `@vmz/vmz` install.
|
|
18
21
|
* @param {string} [fromUrl]
|
|
19
22
|
*/
|
|
20
23
|
export function resolveThisPackageRoot(fromUrl = import.meta.url) {
|
|
@@ -32,15 +35,15 @@ function tryRealpath(p) {
|
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
/**
|
|
35
|
-
* Walk from `startDir` for nearest project
|
|
38
|
+
* Walk from `startDir` for nearest project CLI package root.
|
|
36
39
|
* @param {string} startDir
|
|
37
|
-
* @returns {string | null}
|
|
40
|
+
* @returns {string | null}
|
|
38
41
|
*/
|
|
39
42
|
export function findNearestProjectVmz(startDir) {
|
|
40
43
|
let dir = path.resolve(startDir);
|
|
41
44
|
for (;;) {
|
|
42
|
-
const
|
|
43
|
-
|
|
45
|
+
for (const segments of PROJECT_PKG_SEGMENTS) {
|
|
46
|
+
const candidate = path.join(dir, 'node_modules', ...segments);
|
|
44
47
|
const pkgJson = path.join(candidate, 'package.json');
|
|
45
48
|
if (existsSync(pkgJson)) {
|
|
46
49
|
return tryRealpath(candidate);
|
|
@@ -53,7 +56,7 @@ export function findNearestProjectVmz(startDir) {
|
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
/**
|
|
56
|
-
* Resolve CLI entry
|
|
59
|
+
* Resolve CLI entry (`bin/vmz.js`).
|
|
57
60
|
* @param {string} packageRoot
|
|
58
61
|
* @returns {string | null}
|
|
59
62
|
*/
|
|
@@ -64,8 +67,6 @@ export function resolveVmzBin(packageRoot) {
|
|
|
64
67
|
return null;
|
|
65
68
|
}
|
|
66
69
|
/**
|
|
67
|
-
* Install lives under a `node_modules` tree (npm -g, pnpm store link, etc.).
|
|
68
|
-
* Workspace source checkout (`packages/runtimes/vmz`) does not → developer mode.
|
|
69
70
|
* @param {string} packageRoot
|
|
70
71
|
*/
|
|
71
72
|
export function isUnderNodeModules(packageRoot) {
|
|
@@ -74,27 +75,10 @@ export function isUnderNodeModules(packageRoot) {
|
|
|
74
75
|
return parts.includes('node_modules');
|
|
75
76
|
}
|
|
76
77
|
/**
|
|
77
|
-
* Classify how this process was launched.
|
|
78
|
-
*
|
|
79
|
-
* | mode | thisPackageRoot | rule |
|
|
80
|
-
* |-------------|-----------------------------------------|-------------------------------------------|
|
|
81
|
-
* | developer | monorepo `packages/runtimes/vmz` | not under `node_modules` |
|
|
82
|
-
* | project | app `node_modules/(@vmz/)vmz` | under node_modules ∧ equals nearest |
|
|
83
|
-
* | global | global / unrelated node_modules install | under node_modules ∧ not nearest project |
|
|
84
|
-
*
|
|
85
78
|
* @param {{
|
|
86
79
|
* cwd?: string,
|
|
87
80
|
* thisPackageRoot?: string,
|
|
88
81
|
* }} [opts]
|
|
89
|
-
* @returns {{
|
|
90
|
-
* mode: InvocationMode,
|
|
91
|
-
* cwd: string,
|
|
92
|
-
* thisPackageRoot: string,
|
|
93
|
-
* nearestProjectVmz: string | null,
|
|
94
|
-
* isDeveloper: boolean,
|
|
95
|
-
* isProjectLocal: boolean,
|
|
96
|
-
* isGlobalLike: boolean,
|
|
97
|
-
* }}
|
|
98
82
|
*/
|
|
99
83
|
export function getInvocationContext(opts = {}) {
|
|
100
84
|
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
@@ -124,8 +108,6 @@ export function getInvocationContext(opts = {}) {
|
|
|
124
108
|
};
|
|
125
109
|
}
|
|
126
110
|
/**
|
|
127
|
-
* Commands allowed in **global** mode without re-exec / refusal.
|
|
128
|
-
* Developer + project modes allow the full CLI.
|
|
129
111
|
* @param {string | undefined} cmd
|
|
130
112
|
*/
|
|
131
113
|
export function isGlobalAllowedCommand(cmd) {
|
|
@@ -142,7 +124,7 @@ export function isGlobalAllowedCommand(cmd) {
|
|
|
142
124
|
}
|
|
143
125
|
/**
|
|
144
126
|
* @param {string} bin
|
|
145
|
-
* @param {string[]} argv
|
|
127
|
+
* @param {string[]} argv
|
|
146
128
|
* @returns {Promise<number>}
|
|
147
129
|
*/
|
|
148
130
|
export function reexecProjectVmz(bin, argv) {
|
|
@@ -161,9 +143,6 @@ export function reexecProjectVmz(bin, argv) {
|
|
|
161
143
|
});
|
|
162
144
|
}
|
|
163
145
|
/**
|
|
164
|
-
* Guard for project-only commands when the current install is **global** mode.
|
|
165
|
-
* Developer / project → proceed. Global + local present → re-exec. Global alone → refuse.
|
|
166
|
-
*
|
|
167
146
|
* @returns {Promise<{ action: 'proceed' } | { action: 'exit', code: number }>}
|
|
168
147
|
*/
|
|
169
148
|
export async function gateGlobalProjectCommand(opts) {
|
|
@@ -175,7 +154,7 @@ export async function gateGlobalProjectCommand(opts) {
|
|
|
175
154
|
if (ctx.nearestProjectVmz && ctx.nearestProjectVmz !== ctx.thisPackageRoot) {
|
|
176
155
|
const bin = resolveVmzBin(ctx.nearestProjectVmz);
|
|
177
156
|
if (!bin) {
|
|
178
|
-
logError('found project
|
|
157
|
+
logError('found project `vmz` / `@vmz/vmz` but bin/vmz.js is missing.');
|
|
179
158
|
return { action: 'exit', code: 1 };
|
|
180
159
|
}
|
|
181
160
|
const code = await reexec(bin, argv);
|
package/dist/locale-check.d.ts
CHANGED
|
@@ -80,6 +80,22 @@ export declare function scanLocaleUsages(projectRoot: any): {
|
|
|
80
80
|
* @param {string} outDir
|
|
81
81
|
*/
|
|
82
82
|
export declare function emitLocaleTypedModules(report: any, outDir: any): any[];
|
|
83
|
+
/**
|
|
84
|
+
* Emit runtime `#locales/<catalog>.js` into application `dist/` and rewrite
|
|
85
|
+
* client imports from `#locales/...` to relative ESM paths.
|
|
86
|
+
*
|
|
87
|
+
* Variant pick reads `html[data-locale]` (else defaultLocale). Thin bridge until
|
|
88
|
+
* host LocaleTransition reloads locale-scoped chunks (I2/I4).
|
|
89
|
+
*
|
|
90
|
+
* @param {string} projectRoot
|
|
91
|
+
* @param {string} distDir
|
|
92
|
+
* @returns {{ ok: boolean, written: string[], diagnostics: any[] }}
|
|
93
|
+
*/
|
|
94
|
+
export declare function emitLocaleRuntimeModules(projectRoot: any, distDir: any): {
|
|
95
|
+
ok: boolean;
|
|
96
|
+
written: any[];
|
|
97
|
+
diagnostics: any;
|
|
98
|
+
};
|
|
83
99
|
/**
|
|
84
100
|
* MessageId rename plan — WorkspaceEdit-shaped, no parallel rename IR.
|
|
85
101
|
* @param {ReturnType<typeof checkLocales>} report
|
package/dist/locale-check.js
CHANGED
|
@@ -694,6 +694,131 @@ export function emitLocaleTypedModules(report, outDir) {
|
|
|
694
694
|
fs.writeFileSync(path.join(outDir, 'index.json'), `${JSON.stringify(index, null, 2)}\n`, 'utf8');
|
|
695
695
|
return written;
|
|
696
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Emit runtime `#locales/<catalog>.js` into application `dist/` and rewrite
|
|
699
|
+
* client imports from `#locales/...` to relative ESM paths.
|
|
700
|
+
*
|
|
701
|
+
* Variant pick reads `html[data-locale]` (else defaultLocale). Thin bridge until
|
|
702
|
+
* host LocaleTransition reloads locale-scoped chunks (I2/I4).
|
|
703
|
+
*
|
|
704
|
+
* @param {string} projectRoot
|
|
705
|
+
* @param {string} distDir
|
|
706
|
+
* @returns {{ ok: boolean, written: string[], diagnostics: any[] }}
|
|
707
|
+
*/
|
|
708
|
+
export function emitLocaleRuntimeModules(projectRoot, distDir) {
|
|
709
|
+
const localesRoot = path.join(projectRoot, 'locales');
|
|
710
|
+
if (!fs.existsSync(localesRoot)) {
|
|
711
|
+
return { ok: true, written: [], diagnostics: [] };
|
|
712
|
+
}
|
|
713
|
+
const report = checkLocales({ projectRoot, checkUnused: false });
|
|
714
|
+
if (localeHasErrors(report)) {
|
|
715
|
+
return { ok: false, written: [], diagnostics: report.diagnostics || [] };
|
|
716
|
+
}
|
|
717
|
+
const defaultLocale = report.manifest?.defaultLocale || 'zh-hans';
|
|
718
|
+
const byId = new Map((report.messageCatalog?.messages || []).map((m) => [m.messageId, m]));
|
|
719
|
+
/** @type {string[]} */
|
|
720
|
+
const written = [];
|
|
721
|
+
const localesOut = path.join(distDir, 'locales');
|
|
722
|
+
fs.mkdirSync(localesOut, { recursive: true });
|
|
723
|
+
for (const mod of report.typedModules || []) {
|
|
724
|
+
const lines = [
|
|
725
|
+
`/** Generated ${mod.module} — runtime LocalizedText (defaultLocale=${defaultLocale}) */`,
|
|
726
|
+
`const __vmzDefaultLocale = ${JSON.stringify(defaultLocale)};`,
|
|
727
|
+
`function __vmzLocaleId() {`,
|
|
728
|
+
` try {`,
|
|
729
|
+
` const stored = localStorage.getItem("vmz.locale");`,
|
|
730
|
+
` if (stored) return stored;`,
|
|
731
|
+
` } catch {}`,
|
|
732
|
+
` if (typeof document !== "undefined") {`,
|
|
733
|
+
` const d = document.documentElement?.getAttribute("data-locale");`,
|
|
734
|
+
` if (d) return d;`,
|
|
735
|
+
` const lang = document.documentElement?.lang;`,
|
|
736
|
+
` if (lang === "en" || lang === "en-US" || lang === "en-us") return "en-us";`,
|
|
737
|
+
` if (lang === "zh" || lang === "zh-CN" || lang === "zh-Hans" || lang === "zh-hans") return "zh-hans";`,
|
|
738
|
+
` }`,
|
|
739
|
+
` return __vmzDefaultLocale;`,
|
|
740
|
+
`}`,
|
|
741
|
+
`function __vmzFormat(template, args) {`,
|
|
742
|
+
` if (!args) return String(template ?? "");`,
|
|
743
|
+
` return String(template ?? "").replace(/\\{(\\w+)(?:,\\s*\\w+)?\\}/g, (m, name) =>`,
|
|
744
|
+
` Object.prototype.hasOwnProperty.call(args, name) ? String(args[name]) : m`,
|
|
745
|
+
` );`,
|
|
746
|
+
`}`,
|
|
747
|
+
`function __vmzPick(variants, args) {`,
|
|
748
|
+
` const id = __vmzLocaleId();`,
|
|
749
|
+
` const template = variants[id] ?? variants[__vmzDefaultLocale] ?? "";`,
|
|
750
|
+
` return __vmzFormat(template, args);`,
|
|
751
|
+
`}`,
|
|
752
|
+
'',
|
|
753
|
+
];
|
|
754
|
+
for (const exp of mod.exports || []) {
|
|
755
|
+
const node = byId.get(exp.messageId);
|
|
756
|
+
const variants = {};
|
|
757
|
+
if (node?.variants) {
|
|
758
|
+
for (const [loc, v] of Object.entries(node.variants)) {
|
|
759
|
+
variants[loc] = v.template;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const lit = JSON.stringify(variants);
|
|
763
|
+
if ((exp.params || []).length) {
|
|
764
|
+
lines.push(`export function ${exp.exportName}(args) { return __vmzPick(${lit}, args); }`);
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
lines.push(`export function ${exp.exportName}() { return __vmzPick(${lit}); }`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
lines.push('');
|
|
771
|
+
const file = path.join(localesOut, `${mod.catalogId}.js`);
|
|
772
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
773
|
+
fs.writeFileSync(file, lines.join('\n'), 'utf8');
|
|
774
|
+
written.push(file);
|
|
775
|
+
}
|
|
776
|
+
rewriteLocaleImportsInDist(distDir);
|
|
777
|
+
return { ok: true, written, diagnostics: report.diagnostics || [] };
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* @param {string} distDir
|
|
781
|
+
*/
|
|
782
|
+
function rewriteLocaleImportsInDist(distDir) {
|
|
783
|
+
/** @type {string[]} */
|
|
784
|
+
const files = [];
|
|
785
|
+
walkDistJs(distDir, (file) => files.push(file));
|
|
786
|
+
for (const file of files) {
|
|
787
|
+
let text = fs.readFileSync(file, 'utf8');
|
|
788
|
+
if (!text.includes('#locales/'))
|
|
789
|
+
continue;
|
|
790
|
+
const fromDir = path.dirname(file);
|
|
791
|
+
// Emit path is dist/locales/*.js — `#` cannot appear in ESM file URLs (fragment).
|
|
792
|
+
const next = text.replace(/from\s*(["'])#locales\/([^"']+)\1/g, (_m, quote, id) => {
|
|
793
|
+
const target = path.join(distDir, 'locales', `${id}.js`);
|
|
794
|
+
let rel = path.relative(fromDir, target).replace(/\\/g, '/');
|
|
795
|
+
if (!rel.startsWith('.'))
|
|
796
|
+
rel = `./${rel}`;
|
|
797
|
+
return `from ${quote}${rel}${quote}`;
|
|
798
|
+
});
|
|
799
|
+
if (next !== text)
|
|
800
|
+
fs.writeFileSync(file, next, 'utf8');
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* @param {string} dir
|
|
805
|
+
* @param {(file: string) => void} fn
|
|
806
|
+
*/
|
|
807
|
+
function walkDistJs(dir, fn) {
|
|
808
|
+
if (!fs.existsSync(dir))
|
|
809
|
+
return;
|
|
810
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
811
|
+
const full = path.join(dir, ent.name);
|
|
812
|
+
if (ent.isDirectory()) {
|
|
813
|
+
if (ent.name === 'node_modules')
|
|
814
|
+
continue;
|
|
815
|
+
walkDistJs(full, fn);
|
|
816
|
+
}
|
|
817
|
+
else if (ent.name.endsWith('.js') || ent.name.endsWith('.mjs')) {
|
|
818
|
+
fn(full);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
697
822
|
/**
|
|
698
823
|
* MessageId rename plan — WorkspaceEdit-shaped, no parallel rename IR.
|
|
699
824
|
* @param {ReturnType<typeof checkLocales>} report
|
package/dist/plugin-host.d.ts
CHANGED
|
@@ -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<{
|
|
15
|
+
* @returns {Promise<{
|
|
16
|
+
* plugins: import('@vmz/plugin').VmzPlugin[],
|
|
17
|
+
* engines: import('@vmz/plugin').VmzEngines,
|
|
18
|
+
* delivery: import('@vmz/plugin').SiteDeliveryAuthoring | 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
|
}>;
|
package/dist/plugin-host.js
CHANGED
|
@@ -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<{
|
|
35
|
+
* @returns {Promise<{
|
|
36
|
+
* plugins: import('@vmz/plugin').VmzPlugin[],
|
|
37
|
+
* engines: import('@vmz/plugin').VmzEngines,
|
|
38
|
+
* delivery: import('@vmz/plugin').SiteDeliveryAuthoring | 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').SiteDeliveryAuthoring | 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
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A5: Production observability — trace facets, redaction, CSP/security,
|
|
3
|
+
* performance budgets, health/readiness, capability closure.
|
|
4
|
+
*/
|
|
5
|
+
export declare const PRODUCTION_OBSERVABILITY_SCHEMA = "vmz.production.observability.v0";
|
|
6
|
+
export declare const PRODUCTION_TRACE_SCHEMA = "vmz.production.trace.v0";
|
|
7
|
+
/** Facets that production traces must be able to carry (08 A5). */
|
|
8
|
+
export declare const REQUIRED_TRACE_FACETS: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* Default Browser Production Profile observability contract.
|
|
11
|
+
* @param {Record<string, unknown>} [overrides]
|
|
12
|
+
*/
|
|
13
|
+
export declare function browserProductionObservability(overrides?: {}): {
|
|
14
|
+
schema: string;
|
|
15
|
+
id: any;
|
|
16
|
+
trace: {
|
|
17
|
+
schema: string;
|
|
18
|
+
requiredFacets: any;
|
|
19
|
+
retainOnFailure: any;
|
|
20
|
+
};
|
|
21
|
+
redaction: {
|
|
22
|
+
mode: string;
|
|
23
|
+
replaceWith: any;
|
|
24
|
+
allowPublicProvenance: boolean;
|
|
25
|
+
sensitiveKeyPattern: string;
|
|
26
|
+
};
|
|
27
|
+
security: {
|
|
28
|
+
csp: any;
|
|
29
|
+
requireIntegrityForRemote: boolean;
|
|
30
|
+
originIsolation: boolean;
|
|
31
|
+
requireNonceForInline: boolean;
|
|
32
|
+
cookieNamespace: any;
|
|
33
|
+
sessionNamespace: any;
|
|
34
|
+
};
|
|
35
|
+
capability: {
|
|
36
|
+
allowlistRequired: boolean;
|
|
37
|
+
inputOutputSchemaRequired: boolean;
|
|
38
|
+
timeoutMsDefault: number;
|
|
39
|
+
cancelSupported: boolean;
|
|
40
|
+
serverSecretClosure: boolean;
|
|
41
|
+
};
|
|
42
|
+
budgets: {
|
|
43
|
+
irrelevantBindingWork: number;
|
|
44
|
+
irrelevantRouteRegionRebuild: number;
|
|
45
|
+
wholeTreeRerender: number;
|
|
46
|
+
maxArtifactBytes: number;
|
|
47
|
+
maxHtmlBytes: number;
|
|
48
|
+
maxClientJsBytes: number;
|
|
49
|
+
maxPatchCountPerTransition: number;
|
|
50
|
+
};
|
|
51
|
+
health: {
|
|
52
|
+
livePath: any;
|
|
53
|
+
readyPath: any;
|
|
54
|
+
gracefulShutdown: {
|
|
55
|
+
stopAccepting: boolean;
|
|
56
|
+
drainInFlight: boolean;
|
|
57
|
+
timeoutMs: number;
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
sampling: {
|
|
61
|
+
diagnosticSampleRate: number;
|
|
62
|
+
rollbackOnErrorRate: number;
|
|
63
|
+
latencyBudgetMs: {
|
|
64
|
+
p95: number;
|
|
65
|
+
p99: number;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* @param {unknown} raw
|
|
71
|
+
*/
|
|
72
|
+
export declare function normalizeObservability(raw: any): {
|
|
73
|
+
schema: string;
|
|
74
|
+
id: any;
|
|
75
|
+
trace: {
|
|
76
|
+
schema: string;
|
|
77
|
+
requiredFacets: any;
|
|
78
|
+
retainOnFailure: any;
|
|
79
|
+
};
|
|
80
|
+
redaction: {
|
|
81
|
+
mode: string;
|
|
82
|
+
replaceWith: any;
|
|
83
|
+
allowPublicProvenance: boolean;
|
|
84
|
+
sensitiveKeyPattern: string;
|
|
85
|
+
};
|
|
86
|
+
security: {
|
|
87
|
+
csp: any;
|
|
88
|
+
requireIntegrityForRemote: boolean;
|
|
89
|
+
originIsolation: boolean;
|
|
90
|
+
requireNonceForInline: boolean;
|
|
91
|
+
cookieNamespace: any;
|
|
92
|
+
sessionNamespace: any;
|
|
93
|
+
};
|
|
94
|
+
capability: {
|
|
95
|
+
allowlistRequired: boolean;
|
|
96
|
+
inputOutputSchemaRequired: boolean;
|
|
97
|
+
timeoutMsDefault: number;
|
|
98
|
+
cancelSupported: boolean;
|
|
99
|
+
serverSecretClosure: boolean;
|
|
100
|
+
};
|
|
101
|
+
budgets: {
|
|
102
|
+
irrelevantBindingWork: number;
|
|
103
|
+
irrelevantRouteRegionRebuild: number;
|
|
104
|
+
wholeTreeRerender: number;
|
|
105
|
+
maxArtifactBytes: number;
|
|
106
|
+
maxHtmlBytes: number;
|
|
107
|
+
maxClientJsBytes: number;
|
|
108
|
+
maxPatchCountPerTransition: number;
|
|
109
|
+
};
|
|
110
|
+
health: {
|
|
111
|
+
livePath: any;
|
|
112
|
+
readyPath: any;
|
|
113
|
+
gracefulShutdown: {
|
|
114
|
+
stopAccepting: boolean;
|
|
115
|
+
drainInFlight: boolean;
|
|
116
|
+
timeoutMs: number;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
sampling: {
|
|
120
|
+
diagnosticSampleRate: number;
|
|
121
|
+
rollbackOnErrorRate: number;
|
|
122
|
+
latencyBudgetMs: {
|
|
123
|
+
p95: number;
|
|
124
|
+
p99: number;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Redact sensitive keys recursively. Never returns secrets for public provenance.
|
|
130
|
+
* @param {unknown} value
|
|
131
|
+
* @param {Record<string, any>} [policy]
|
|
132
|
+
* @param {{ privilege?: 'public' | 'operator' }} [opts]
|
|
133
|
+
*/
|
|
134
|
+
export declare function redactSensitive(value: any, policy?: {}, opts?: {}): any;
|
|
135
|
+
/**
|
|
136
|
+
* Validate a production trace carries required facets (as event.facet or mapped kind).
|
|
137
|
+
* @param {unknown} raw
|
|
138
|
+
* @param {string[]} [requiredFacets]
|
|
139
|
+
*/
|
|
140
|
+
export declare function validateProductionTrace(raw: any, requiredFacets?: readonly string[]): {
|
|
141
|
+
ok: boolean;
|
|
142
|
+
covered: unknown[];
|
|
143
|
+
errors: any[];
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Build a minimal valid production trace covering all required facets (for CI assembly).
|
|
147
|
+
* @param {Record<string, unknown>} [meta]
|
|
148
|
+
*/
|
|
149
|
+
export declare function buildCoveringProductionTrace(meta?: {}): {
|
|
150
|
+
schema: string;
|
|
151
|
+
status: string;
|
|
152
|
+
applicationId: any;
|
|
153
|
+
artifactDigest: any;
|
|
154
|
+
events: {
|
|
155
|
+
facet: string;
|
|
156
|
+
kind: string;
|
|
157
|
+
stableId: {
|
|
158
|
+
kind: string;
|
|
159
|
+
id: string;
|
|
160
|
+
};
|
|
161
|
+
generation: number;
|
|
162
|
+
redacted: boolean;
|
|
163
|
+
payload: any;
|
|
164
|
+
}[];
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* @param {Record<string, any>} measured
|
|
168
|
+
* @param {Record<string, any>} budgets
|
|
169
|
+
*/
|
|
170
|
+
export declare function checkProductionBudgets(measured: any, budgets: any): {
|
|
171
|
+
ok: boolean;
|
|
172
|
+
violations: any[];
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Validate capability production closure (allowlist + schema + timeout + secret).
|
|
176
|
+
* @param {Record<string, any>} cap
|
|
177
|
+
* @param {Record<string, any>} policy
|
|
178
|
+
*/
|
|
179
|
+
export declare function checkCapabilityClosure(cap: any, policy: any): {
|
|
180
|
+
ok: boolean;
|
|
181
|
+
errors: any[];
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Merge CSP / security headers into a CDN policy headers list (HTML matches).
|
|
185
|
+
* @param {Record<string, any>} cdnPolicy
|
|
186
|
+
* @param {Record<string, any>} security
|
|
187
|
+
*/
|
|
188
|
+
export declare function applySecurityHeadersToCdnPolicy(cdnPolicy: any, security: any): any;
|
|
189
|
+
/**
|
|
190
|
+
* Measure dist sizes for budget gates.
|
|
191
|
+
* @param {string} distDir
|
|
192
|
+
*/
|
|
193
|
+
export declare function measureDistBudgets(distDir: any): {
|
|
194
|
+
artifactBytes: number;
|
|
195
|
+
htmlBytes: number;
|
|
196
|
+
clientJsBytes: number;
|
|
197
|
+
irrelevantBindingWork: number;
|
|
198
|
+
irrelevantRouteRegionRebuild: number;
|
|
199
|
+
wholeTreeRerender: number;
|
|
200
|
+
patchCount: number;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Write observability contract (+ covering trace sample) under dist/_vmz.
|
|
204
|
+
* @param {string} distDir
|
|
205
|
+
* @param {Record<string, unknown>} [overrides]
|
|
206
|
+
* @param {{ applicationId?: string, artifactDigest?: string }} [meta]
|
|
207
|
+
*/
|
|
208
|
+
export declare function emitProductionObservability(distDir: any, overrides?: {}, meta?: {}): {
|
|
209
|
+
contract: {
|
|
210
|
+
schema: string;
|
|
211
|
+
id: any;
|
|
212
|
+
trace: {
|
|
213
|
+
schema: string;
|
|
214
|
+
requiredFacets: any;
|
|
215
|
+
retainOnFailure: any;
|
|
216
|
+
};
|
|
217
|
+
redaction: {
|
|
218
|
+
mode: string;
|
|
219
|
+
replaceWith: any;
|
|
220
|
+
allowPublicProvenance: boolean;
|
|
221
|
+
sensitiveKeyPattern: string;
|
|
222
|
+
};
|
|
223
|
+
security: {
|
|
224
|
+
csp: any;
|
|
225
|
+
requireIntegrityForRemote: boolean;
|
|
226
|
+
originIsolation: boolean;
|
|
227
|
+
requireNonceForInline: boolean;
|
|
228
|
+
cookieNamespace: any;
|
|
229
|
+
sessionNamespace: any;
|
|
230
|
+
};
|
|
231
|
+
capability: {
|
|
232
|
+
allowlistRequired: boolean;
|
|
233
|
+
inputOutputSchemaRequired: boolean;
|
|
234
|
+
timeoutMsDefault: number;
|
|
235
|
+
cancelSupported: boolean;
|
|
236
|
+
serverSecretClosure: boolean;
|
|
237
|
+
};
|
|
238
|
+
budgets: {
|
|
239
|
+
irrelevantBindingWork: number;
|
|
240
|
+
irrelevantRouteRegionRebuild: number;
|
|
241
|
+
wholeTreeRerender: number;
|
|
242
|
+
maxArtifactBytes: number;
|
|
243
|
+
maxHtmlBytes: number;
|
|
244
|
+
maxClientJsBytes: number;
|
|
245
|
+
maxPatchCountPerTransition: number;
|
|
246
|
+
};
|
|
247
|
+
health: {
|
|
248
|
+
livePath: any;
|
|
249
|
+
readyPath: any;
|
|
250
|
+
gracefulShutdown: {
|
|
251
|
+
stopAccepting: boolean;
|
|
252
|
+
drainInFlight: boolean;
|
|
253
|
+
timeoutMs: number;
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
sampling: {
|
|
257
|
+
diagnosticSampleRate: number;
|
|
258
|
+
rollbackOnErrorRate: number;
|
|
259
|
+
latencyBudgetMs: {
|
|
260
|
+
p95: number;
|
|
261
|
+
p99: number;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
};
|
|
265
|
+
trace: {
|
|
266
|
+
schema: string;
|
|
267
|
+
status: string;
|
|
268
|
+
applicationId: any;
|
|
269
|
+
artifactDigest: any;
|
|
270
|
+
events: {
|
|
271
|
+
facet: string;
|
|
272
|
+
kind: string;
|
|
273
|
+
stableId: {
|
|
274
|
+
kind: string;
|
|
275
|
+
id: string;
|
|
276
|
+
};
|
|
277
|
+
generation: number;
|
|
278
|
+
redacted: boolean;
|
|
279
|
+
payload: any;
|
|
280
|
+
}[];
|
|
281
|
+
};
|
|
282
|
+
contractPath: string;
|
|
283
|
+
tracePath: string;
|
|
284
|
+
};
|
|
285
|
+
export declare function observabilityDigest(contract: any): any;
|
|
286
|
+
export declare function sha256Text(text: any): string;
|