@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
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
+
* Logical paths stay available for serve/dev; web-static HTML rewrites to hashed URLs.
|
|
4
|
+
* Identical bytes → identical asset path (cross-release / cross-source reuse by digest).
|
|
5
|
+
*/
|
|
6
|
+
export declare const CONTENT_ADDRESSED_ASSETS_SCHEMA = "vmz.content_addressed_assets.v0";
|
|
7
|
+
/**
|
|
8
|
+
* Emit `assets/<sha256>.<ext>` copies and rewrite HTML href/src to hashed URLs.
|
|
9
|
+
* @param {string} distDir
|
|
10
|
+
* @param {{ candidates?: string[], rewriteHtml?: boolean }} [opts]
|
|
11
|
+
*/
|
|
12
|
+
export declare function emitContentAddressedAssets(distDir: any, opts?: {}): {
|
|
13
|
+
manifest: {
|
|
14
|
+
schema: string;
|
|
15
|
+
layout: string;
|
|
16
|
+
immutable: boolean;
|
|
17
|
+
objectCount: number;
|
|
18
|
+
objects: any[];
|
|
19
|
+
rewrittenHtml: number;
|
|
20
|
+
};
|
|
21
|
+
assetsDir: string;
|
|
22
|
+
rewrites: {};
|
|
23
|
+
manifestPath: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Resolve an immutable object by digest under dist/assets (cross-source reuse).
|
|
27
|
+
* @param {string} distDir
|
|
28
|
+
* @param {string} digest
|
|
29
|
+
* @param {string} [ext]
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveAssetByDigest(distDir: any, digest: any, ext?: string): {
|
|
32
|
+
assetPath: string;
|
|
33
|
+
digest: string;
|
|
34
|
+
bytes: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Prove two buffers share one asset path (content-address stability).
|
|
38
|
+
* @param {string} distDir
|
|
39
|
+
* @param {Buffer|string} a
|
|
40
|
+
* @param {Buffer|string} b
|
|
41
|
+
* @param {string} [ext]
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertSharedAssetPath(distDir: any, a: any, b: any, ext?: string): {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
reason: string;
|
|
46
|
+
digestA: string;
|
|
47
|
+
digestB: string;
|
|
48
|
+
rel?: undefined;
|
|
49
|
+
assetPath?: undefined;
|
|
50
|
+
digest?: undefined;
|
|
51
|
+
} | {
|
|
52
|
+
ok: boolean;
|
|
53
|
+
reason: string;
|
|
54
|
+
rel: string;
|
|
55
|
+
digestA?: undefined;
|
|
56
|
+
digestB?: undefined;
|
|
57
|
+
assetPath?: undefined;
|
|
58
|
+
digest?: undefined;
|
|
59
|
+
} | {
|
|
60
|
+
ok: boolean;
|
|
61
|
+
assetPath: string;
|
|
62
|
+
digest: string;
|
|
63
|
+
reason?: undefined;
|
|
64
|
+
digestA?: undefined;
|
|
65
|
+
digestB?: undefined;
|
|
66
|
+
rel?: undefined;
|
|
67
|
+
};
|
|
68
|
+
export declare function contentAddressedAssetsDigest(manifest: any): any;
|
|
69
|
+
export declare function sha256Buffer(buf: any): string;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
+
* Logical paths stay available for serve/dev; web-static HTML rewrites to hashed URLs.
|
|
4
|
+
* Identical bytes → identical asset path (cross-release / cross-source reuse by digest).
|
|
5
|
+
*/
|
|
6
|
+
// @ts-nocheck
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { canonicalJson, sha256Hex } from './release-pack.js';
|
|
11
|
+
export const CONTENT_ADDRESSED_ASSETS_SCHEMA = 'vmz.content_addressed_assets.v0';
|
|
12
|
+
/** Immutable delivery candidates (client-facing bytes). */
|
|
13
|
+
const DEFAULT_CANDIDATES = [
|
|
14
|
+
'entry-client.js',
|
|
15
|
+
'entry-event.js',
|
|
16
|
+
'vmz.css',
|
|
17
|
+
'vmz-designs.css',
|
|
18
|
+
'vmz-dom.js',
|
|
19
|
+
'vmz-runtime.js',
|
|
20
|
+
'vmz-http.js',
|
|
21
|
+
'vmz-client-nav.js',
|
|
22
|
+
];
|
|
23
|
+
/**
|
|
24
|
+
* Emit `assets/<sha256>.<ext>` copies and rewrite HTML href/src to hashed URLs.
|
|
25
|
+
* @param {string} distDir
|
|
26
|
+
* @param {{ candidates?: string[], rewriteHtml?: boolean }} [opts]
|
|
27
|
+
*/
|
|
28
|
+
export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
29
|
+
const abs = path.resolve(distDir);
|
|
30
|
+
if (!fs.existsSync(abs)) {
|
|
31
|
+
throw new Error(`emitContentAddressedAssets: missing dist ${abs}`);
|
|
32
|
+
}
|
|
33
|
+
const assetsDir = path.join(abs, 'assets');
|
|
34
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
35
|
+
const candidates = Array.isArray(opts.candidates) ? opts.candidates : collectCandidates(abs);
|
|
36
|
+
/** @type {Array<Record<string, any>>} */
|
|
37
|
+
const objects = [];
|
|
38
|
+
/** @type {Record<string, string>} */
|
|
39
|
+
const rewrites = {};
|
|
40
|
+
for (const rel of candidates) {
|
|
41
|
+
const logical = String(rel).replace(/\\/g, '/').replace(/^\//, '');
|
|
42
|
+
const src = path.join(abs, ...logical.split('/'));
|
|
43
|
+
if (!fs.existsSync(src) || !fs.statSync(src).isFile())
|
|
44
|
+
continue;
|
|
45
|
+
const buf = fs.readFileSync(src);
|
|
46
|
+
const digest = sha256Hex(buf);
|
|
47
|
+
const ext = path.extname(logical) || '';
|
|
48
|
+
const assetRel = `assets/${digest}${ext}`;
|
|
49
|
+
const dest = path.join(abs, ...assetRel.split('/'));
|
|
50
|
+
if (!fs.existsSync(dest)) {
|
|
51
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
52
|
+
fs.writeFileSync(dest, buf);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
// Cross-release reuse: identical digest must not be rewritten.
|
|
56
|
+
const existing = sha256Hex(fs.readFileSync(dest));
|
|
57
|
+
if (existing !== digest) {
|
|
58
|
+
throw new Error(`content-address collision at ${assetRel}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
objects.push({
|
|
62
|
+
logicalPath: logical,
|
|
63
|
+
assetPath: assetRel,
|
|
64
|
+
digest,
|
|
65
|
+
bytes: buf.length,
|
|
66
|
+
immutable: true,
|
|
67
|
+
});
|
|
68
|
+
rewrites[`/${logical}`] = `/${assetRel}`;
|
|
69
|
+
rewrites[logical] = assetRel;
|
|
70
|
+
}
|
|
71
|
+
objects.sort((a, b) => (a.logicalPath < b.logicalPath ? -1 : a.logicalPath > b.logicalPath ? 1 : 0));
|
|
72
|
+
let rewrittenHtml = 0;
|
|
73
|
+
if (opts.rewriteHtml !== false) {
|
|
74
|
+
rewrittenHtml = rewriteHtmlReferences(abs, rewrites);
|
|
75
|
+
}
|
|
76
|
+
const manifest = {
|
|
77
|
+
schema: CONTENT_ADDRESSED_ASSETS_SCHEMA,
|
|
78
|
+
layout: 'assets/<sha256>.<ext>',
|
|
79
|
+
immutable: true,
|
|
80
|
+
objectCount: objects.length,
|
|
81
|
+
objects,
|
|
82
|
+
rewrittenHtml,
|
|
83
|
+
};
|
|
84
|
+
manifest.manifestDigest = sha256Hex(canonicalJson({ ...manifest, manifestDigest: undefined }));
|
|
85
|
+
const vmzDir = path.join(abs, '_vmz');
|
|
86
|
+
fs.mkdirSync(vmzDir, { recursive: true });
|
|
87
|
+
const outPath = path.join(vmzDir, 'content-addressed-assets.json');
|
|
88
|
+
fs.writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
89
|
+
return { manifest, assetsDir, rewrites, manifestPath: outPath };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Resolve an immutable object by digest under dist/assets (cross-source reuse).
|
|
93
|
+
* @param {string} distDir
|
|
94
|
+
* @param {string} digest
|
|
95
|
+
* @param {string} [ext]
|
|
96
|
+
*/
|
|
97
|
+
export function resolveAssetByDigest(distDir, digest, ext = '') {
|
|
98
|
+
const d = String(digest || '').trim();
|
|
99
|
+
if (!/^[a-f0-9]{64}$/i.test(d))
|
|
100
|
+
return null;
|
|
101
|
+
const suffix = ext && !ext.startsWith('.') ? `.${ext}` : ext;
|
|
102
|
+
const rel = `assets/${d}${suffix}`;
|
|
103
|
+
const abs = path.join(distDir, ...rel.split('/'));
|
|
104
|
+
if (!fs.existsSync(abs))
|
|
105
|
+
return null;
|
|
106
|
+
return { assetPath: rel, digest: d.toLowerCase(), bytes: fs.statSync(abs).size };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Prove two buffers share one asset path (content-address stability).
|
|
110
|
+
* @param {string} distDir
|
|
111
|
+
* @param {Buffer|string} a
|
|
112
|
+
* @param {Buffer|string} b
|
|
113
|
+
* @param {string} [ext]
|
|
114
|
+
*/
|
|
115
|
+
export function assertSharedAssetPath(distDir, a, b, ext = '.js') {
|
|
116
|
+
const da = sha256Hex(a);
|
|
117
|
+
const db = sha256Hex(b);
|
|
118
|
+
if (da !== db) {
|
|
119
|
+
return { ok: false, reason: 'digests differ', digestA: da, digestB: db };
|
|
120
|
+
}
|
|
121
|
+
const assetsDir = path.join(distDir, 'assets');
|
|
122
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
123
|
+
const rel = `assets/${da}${ext}`;
|
|
124
|
+
const dest = path.join(distDir, ...rel.split('/'));
|
|
125
|
+
fs.writeFileSync(dest, typeof a === 'string' ? Buffer.from(a) : a);
|
|
126
|
+
// Second write of identical bytes must be reuse, not fork.
|
|
127
|
+
fs.writeFileSync(dest, typeof b === 'string' ? Buffer.from(b) : b);
|
|
128
|
+
const again = resolveAssetByDigest(distDir, da, ext);
|
|
129
|
+
if (!again || again.assetPath !== rel) {
|
|
130
|
+
return { ok: false, reason: 'resolve missed shared path', rel };
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, assetPath: rel, digest: da };
|
|
133
|
+
}
|
|
134
|
+
function collectCandidates(distDir) {
|
|
135
|
+
/** @type {string[]} */
|
|
136
|
+
const out = [];
|
|
137
|
+
for (const name of DEFAULT_CANDIDATES) {
|
|
138
|
+
if (fs.existsSync(path.join(distDir, name)))
|
|
139
|
+
out.push(name);
|
|
140
|
+
}
|
|
141
|
+
// Include top-level *.client.js and pages/**/*.client.js referenced by resume.
|
|
142
|
+
walk(distDir, distDir, (rel) => {
|
|
143
|
+
if (/\.client\.js$/i.test(rel))
|
|
144
|
+
out.push(rel);
|
|
145
|
+
});
|
|
146
|
+
return [...new Set(out)].sort();
|
|
147
|
+
}
|
|
148
|
+
function walk(root, dir, onFile) {
|
|
149
|
+
for (const name of fs.readdirSync(dir)) {
|
|
150
|
+
if (name === 'assets' || name === '_vmz' || name === 'node_modules')
|
|
151
|
+
continue;
|
|
152
|
+
const full = path.join(dir, name);
|
|
153
|
+
const st = fs.statSync(full);
|
|
154
|
+
if (st.isDirectory())
|
|
155
|
+
walk(root, full, onFile);
|
|
156
|
+
else
|
|
157
|
+
onFile(path.relative(root, full).replace(/\\/g, '/'));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} distDir
|
|
162
|
+
* @param {Record<string, string>} rewrites map `/logical` → `/assets/hash.ext`
|
|
163
|
+
*/
|
|
164
|
+
function rewriteHtmlReferences(distDir, rewrites) {
|
|
165
|
+
const pairs = Object.entries(rewrites)
|
|
166
|
+
.filter(([from]) => from.startsWith('/'))
|
|
167
|
+
.sort((a, b) => b[0].length - a[0].length);
|
|
168
|
+
if (!pairs.length)
|
|
169
|
+
return 0;
|
|
170
|
+
let count = 0;
|
|
171
|
+
walkHtml(distDir, (file) => {
|
|
172
|
+
let text = fs.readFileSync(file, 'utf8');
|
|
173
|
+
let next = text;
|
|
174
|
+
for (const [from, to] of pairs) {
|
|
175
|
+
// href="/x" src="/x" and unquoted variants in attributes
|
|
176
|
+
next = next.split(from).join(to);
|
|
177
|
+
}
|
|
178
|
+
if (next !== text) {
|
|
179
|
+
fs.writeFileSync(file, next, 'utf8');
|
|
180
|
+
count += 1;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
return count;
|
|
184
|
+
}
|
|
185
|
+
function walkHtml(dir, onFile) {
|
|
186
|
+
const stack = [dir];
|
|
187
|
+
while (stack.length) {
|
|
188
|
+
const cur = stack.pop();
|
|
189
|
+
for (const name of fs.readdirSync(cur)) {
|
|
190
|
+
if (name === 'assets' || name === '_vmz' || name === 'node_modules')
|
|
191
|
+
continue;
|
|
192
|
+
const full = path.join(cur, name);
|
|
193
|
+
const st = fs.statSync(full);
|
|
194
|
+
if (st.isDirectory())
|
|
195
|
+
stack.push(full);
|
|
196
|
+
else if (name.endsWith('.html'))
|
|
197
|
+
onFile(full);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export function contentAddressedAssetsDigest(manifest) {
|
|
202
|
+
return manifest.manifestDigest || sha256Hex(canonicalJson({ ...manifest, manifestDigest: undefined }));
|
|
203
|
+
}
|
|
204
|
+
export function sha256Buffer(buf) {
|
|
205
|
+
return crypto.createHash('sha256').update(buf).digest('hex');
|
|
206
|
+
}
|
package/dist/dev-session.js
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync } from 'node:fs';
|
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { buildIntegratedDocuments, projectHasDocuments } from './document-integrate.js';
|
|
12
12
|
import { createWorkspace } from './index.js';
|
|
13
|
+
import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
|
|
13
14
|
import { log } from './log.js';
|
|
14
15
|
import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
15
16
|
/**
|
|
@@ -48,12 +49,23 @@ export function createDevSession(options) {
|
|
|
48
49
|
ws.updateFiles(changes);
|
|
49
50
|
return ws.build();
|
|
50
51
|
}
|
|
52
|
+
function emitLocales() {
|
|
53
|
+
const localeEmit = emitLocaleRuntimeModules(project, outDir);
|
|
54
|
+
if (!localeEmit.ok || localeHasErrors({ diagnostics: localeEmit.diagnostics })) {
|
|
55
|
+
log.diagnostics(localeEmit.diagnostics ?? []);
|
|
56
|
+
log.error('locale runtime emit failed');
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
51
61
|
function printReport(report, label) {
|
|
52
62
|
const errors = log.diagnostics(report.diagnostics ?? []);
|
|
53
63
|
if (errors) {
|
|
54
64
|
log.error(`${label} failed (${errors} error(s))`);
|
|
55
65
|
return false;
|
|
56
66
|
}
|
|
67
|
+
if (!emitLocales())
|
|
68
|
+
return false;
|
|
57
69
|
const mode = report.full ? 'full' : 'affected';
|
|
58
70
|
const chunks = (report.affectedChunks || []).join(', ') || '(none)';
|
|
59
71
|
log.info(`${label} ok (${mode}; chunks=[${chunks}]; ${(report.emitted ?? []).length} emitted)`);
|
|
@@ -82,7 +94,8 @@ export function createDevSession(options) {
|
|
|
82
94
|
}
|
|
83
95
|
child = spawnHost({ project, outDir, host, port });
|
|
84
96
|
const docsRoot = path.join(project, 'documents');
|
|
85
|
-
const
|
|
97
|
+
const localesRoot = path.join(project, 'locales');
|
|
98
|
+
const watchRoots = [src].concat(existsSync(docsRoot) ? [docsRoot] : []).concat(existsSync(localesRoot) ? [localesRoot] : []);
|
|
86
99
|
log.info(`dev → http://${host}:${port} (watching ${watchRoots.join(', ')})`);
|
|
87
100
|
/** @type {Map<string, Map<string, string>>} */
|
|
88
101
|
let fingerprints = new Map();
|
|
@@ -100,10 +113,12 @@ export function createDevSession(options) {
|
|
|
100
113
|
if (stopped || signal?.aborted)
|
|
101
114
|
break;
|
|
102
115
|
if (child && child.exitCode != null) {
|
|
103
|
-
|
|
116
|
+
log.warn(`serve-host exited (${child.exitCode}) — respawning…`);
|
|
117
|
+
child = spawnHost({ project, outDir, host, port });
|
|
118
|
+
continue;
|
|
104
119
|
}
|
|
105
|
-
/** @type {{ srcChanged: string[], srcDeleted: string[], docsDirty: boolean }} */
|
|
106
|
-
let batch = { srcChanged: [], srcDeleted: [], docsDirty: false };
|
|
120
|
+
/** @type {{ srcChanged: string[], srcDeleted: string[], docsDirty: boolean, localesDirty: boolean }} */
|
|
121
|
+
let batch = { srcChanged: [], srcDeleted: [], docsDirty: false, localesDirty: false };
|
|
107
122
|
try {
|
|
108
123
|
// Probe only — keep prior fingerprints until debounce resample
|
|
109
124
|
// (same contract as pre-docs watcher: empty second pass would miss soft reload).
|
|
@@ -115,6 +130,10 @@ export function createDevSession(options) {
|
|
|
115
130
|
batch.srcChanged = diff.changed;
|
|
116
131
|
batch.srcDeleted = diff.deleted;
|
|
117
132
|
}
|
|
133
|
+
else if (root === localesRoot) {
|
|
134
|
+
if (diff.changed.length || diff.deleted.length)
|
|
135
|
+
batch.localesDirty = true;
|
|
136
|
+
}
|
|
118
137
|
else if (diff.changed.length || diff.deleted.length) {
|
|
119
138
|
batch.docsDirty = true;
|
|
120
139
|
}
|
|
@@ -124,11 +143,11 @@ export function createDevSession(options) {
|
|
|
124
143
|
log.warn('watch error:', err);
|
|
125
144
|
continue;
|
|
126
145
|
}
|
|
127
|
-
if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty)
|
|
146
|
+
if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty && !batch.localesDirty)
|
|
128
147
|
continue;
|
|
129
148
|
await sleep(200);
|
|
130
149
|
// Resample against the same prior fingerprints, then commit.
|
|
131
|
-
batch = { srcChanged: [], srcDeleted: [], docsDirty: false };
|
|
150
|
+
batch = { srcChanged: [], srcDeleted: [], docsDirty: false, localesDirty: false };
|
|
132
151
|
for (const root of watchRoots) {
|
|
133
152
|
const prev = fingerprints.get(root) || new Map();
|
|
134
153
|
const next = fileFingerprintMap(root);
|
|
@@ -138,11 +157,15 @@ export function createDevSession(options) {
|
|
|
138
157
|
batch.srcChanged = diff.changed;
|
|
139
158
|
batch.srcDeleted = diff.deleted;
|
|
140
159
|
}
|
|
160
|
+
else if (root === localesRoot) {
|
|
161
|
+
if (diff.changed.length || diff.deleted.length)
|
|
162
|
+
batch.localesDirty = true;
|
|
163
|
+
}
|
|
141
164
|
else if (diff.changed.length || diff.deleted.length) {
|
|
142
165
|
batch.docsDirty = true;
|
|
143
166
|
}
|
|
144
167
|
}
|
|
145
|
-
if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty)
|
|
168
|
+
if (!batch.srcChanged.length && !batch.srcDeleted.length && !batch.docsDirty && !batch.localesDirty)
|
|
146
169
|
continue;
|
|
147
170
|
let needFullReload = batch.docsDirty;
|
|
148
171
|
if (batch.srcChanged.length || batch.srcDeleted.length) {
|
|
@@ -180,12 +203,26 @@ export function createDevSession(options) {
|
|
|
180
203
|
: 'soft reload ok (full page)');
|
|
181
204
|
}
|
|
182
205
|
catch (err) {
|
|
183
|
-
log.warn(`soft reload failed (${err}) —
|
|
184
|
-
killChild(child);
|
|
185
|
-
child = spawnHost({ project, outDir, host, port });
|
|
206
|
+
log.warn(`soft reload failed (${err}) — keeping previous serve-host (fix and save)`);
|
|
186
207
|
}
|
|
187
208
|
continue;
|
|
188
209
|
}
|
|
210
|
+
if (batch.localesDirty) {
|
|
211
|
+
log.info('locales change detected — re-emitting #locales runtime…');
|
|
212
|
+
if (!emitLocales()) {
|
|
213
|
+
log.warn('locale runtime emit failed — keeping previous modules');
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
await softReload(host, port, { full: true, islandHmr: false });
|
|
218
|
+
log.info('soft reload ok (full page; locales)');
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
log.warn(`soft reload failed (${err}) — keeping previous serve-host (fix and save)`);
|
|
222
|
+
}
|
|
223
|
+
if (!batch.docsDirty)
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
189
226
|
if (batch.docsDirty) {
|
|
190
227
|
log.info('documents change detected — rebuilding document mount…');
|
|
191
228
|
const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
|
|
@@ -198,9 +235,7 @@ export function createDevSession(options) {
|
|
|
198
235
|
log.info('soft reload ok (full page; docs)');
|
|
199
236
|
}
|
|
200
237
|
catch (err) {
|
|
201
|
-
log.warn(`soft reload failed (${err}) —
|
|
202
|
-
killChild(child);
|
|
203
|
-
child = spawnHost({ project, outDir, host, port });
|
|
238
|
+
log.warn(`soft reload failed (${err}) — keeping previous serve-host (fix and save)`);
|
|
204
239
|
}
|
|
205
240
|
}
|
|
206
241
|
}
|
package/dist/document-designs.js
CHANGED
|
@@ -64,12 +64,17 @@ function emitMinimalDesignsCss(designsDir) {
|
|
|
64
64
|
const vars = {};
|
|
65
65
|
const tokenDir = path.join(designsDir, 'tokens');
|
|
66
66
|
if (fs.existsSync(tokenDir)) {
|
|
67
|
-
walkJson(tokenDir, (obj, prefix) =>
|
|
67
|
+
walkJson(tokenDir, (obj, prefix) => {
|
|
68
|
+
collectStyleThemeEntries(obj, vars);
|
|
69
|
+
flattenTokens(obj, prefix, vars);
|
|
70
|
+
});
|
|
68
71
|
}
|
|
69
72
|
const themeJson = path.join(designsDir, 'theme.json');
|
|
70
73
|
if (fs.existsSync(themeJson)) {
|
|
71
74
|
try {
|
|
72
|
-
|
|
75
|
+
const theme = JSON.parse(fs.readFileSync(themeJson, 'utf8'));
|
|
76
|
+
collectStyleThemeEntries(theme, vars);
|
|
77
|
+
flattenTokens(theme, '', vars);
|
|
73
78
|
}
|
|
74
79
|
catch {
|
|
75
80
|
/* ignore */
|
|
@@ -89,10 +94,33 @@ function cssVar(key) {
|
|
|
89
94
|
.replace(/^-|-$/g, '');
|
|
90
95
|
return `--${name}`;
|
|
91
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Style Theme entries (`key.path` + `value`) → `vmz-*` CSS vars (same naming as application compile).
|
|
99
|
+
* @param {unknown} obj
|
|
100
|
+
* @param {Record<string, string>} out
|
|
101
|
+
*/
|
|
102
|
+
function collectStyleThemeEntries(obj, out) {
|
|
103
|
+
if (obj == null || typeof obj !== 'object' || Array.isArray(obj))
|
|
104
|
+
return;
|
|
105
|
+
const entries = /** @type {{ key?: { path?: unknown }, value?: unknown }[]} */ ( /** @type {{ entries?: unknown }} */(obj).entries);
|
|
106
|
+
if (!Array.isArray(entries))
|
|
107
|
+
return;
|
|
108
|
+
for (const e of entries) {
|
|
109
|
+
const pathParts = e?.key?.path;
|
|
110
|
+
if (!Array.isArray(pathParts) || pathParts.length === 0)
|
|
111
|
+
continue;
|
|
112
|
+
if (typeof e.value !== 'string' && typeof e.value !== 'number')
|
|
113
|
+
continue;
|
|
114
|
+
const dotted = pathParts.map(String).join('-');
|
|
115
|
+
out[`vmz-${dotted}`] = String(e.value);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
92
118
|
function flattenTokens(obj, prefix, out) {
|
|
93
119
|
if (obj == null || typeof obj !== 'object' || Array.isArray(obj))
|
|
94
120
|
return;
|
|
95
121
|
for (const [k, v] of Object.entries(obj)) {
|
|
122
|
+
if (k === 'entries')
|
|
123
|
+
continue;
|
|
96
124
|
const key = prefix ? `${prefix}-${k}` : k;
|
|
97
125
|
if (v != null && typeof v === 'object' && !Array.isArray(v)) {
|
|
98
126
|
if ('value' in v && (typeof v.value === 'string' || typeof v.value === 'number')) {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,8 +14,8 @@ export declare function expectedProtocol(): {
|
|
|
14
14
|
pluginProtocol: string;
|
|
15
15
|
};
|
|
16
16
|
/**
|
|
17
|
-
* Resolve native `.node` via the platform optionalDependency
|
|
18
|
-
* (`@vmz/vmz-<short
|
|
17
|
+
* Resolve native `.node` via the platform optionalDependency
|
|
18
|
+
* (`@vmz/vmz-<short>`; transitional fallback `@vmz/vmz-<short>`).
|
|
19
19
|
* @returns {string}
|
|
20
20
|
*/
|
|
21
21
|
export declare function resolveNativePath(): any;
|
|
@@ -138,12 +138,21 @@ export declare function checkApplicationHostCompositionJson(hostRoot: any, packa
|
|
|
138
138
|
*/
|
|
139
139
|
export declare function checkApplicationDevTestDeployJson(hostRoot: any, packageRoots: any, dirtyPaths?: any[]): any;
|
|
140
140
|
export { createDevSession, listWatchedFiles, srcFingerprint } from './dev-session.js';
|
|
141
|
+
export { findAvailablePort } from './port.js';
|
|
141
142
|
export { runCli, parseArgs, printHelp, printGlobalHelp, printProjectHelp } from './cli.js';
|
|
142
143
|
export { findNearestProjectVmz, getInvocationContext, isGlobalAllowedCommand, isUnderNodeModules, resolveThisPackageRoot, resolveVmzBin, gateGlobalProjectCommand, } from './invocation.js';
|
|
143
144
|
export { resolveWorkspaceDirs, findPackageJson, readPackageMeta } from './resolve.js';
|
|
144
145
|
export { resolvePackageRoot, resolveWorkspacePackages } from './packages.js';
|
|
145
146
|
export { log } from './log.js';
|
|
146
147
|
export { cmdApplication, runCheck as runApplicationCheck } from './application-cmd.js';
|
|
148
|
+
export { cmdArtifact } from './release-cmd.js';
|
|
149
|
+
export { ARTIFACT_DIFF_SCHEMA, DELIVERY_ARTIFACT_MANIFEST_SCHEMA, RELEASE_ENVELOPE_SCHEMA, ROUTE_REALIZATION_TABLE_SCHEMA, atomicWritePointer, canonicalJson, diffArtifacts, loadReleaseEnvelope, packRelease, publishRelease, readPointer, rollbackRelease, sha256File, sha256Hex, } from './release-pack.js';
|
|
150
|
+
export { STATIC_DELIVERY_MANIFEST_SCHEMA, emitWebStatic, } from './static-emit.js';
|
|
151
|
+
export { CONTENT_ADDRESSED_ASSETS_SCHEMA, emitContentAddressedAssets, resolveAssetByDigest, assertSharedAssetPath, contentAddressedAssetsDigest, } from './content-addressed-assets.js';
|
|
152
|
+
export { CDN_POLICY_MANIFEST_SCHEMA, CDN_ADAPTER_PROJECTION_SCHEMA, CACHE_HTML, CACHE_ASSET_IMMUTABLE, CACHE_META, buildCdnPolicyManifest, emitCdnPolicy, projectCdnAdapter, createLocalStaticHandler, listenLocalStaticHost, matchGlob, } from './cdn-policy.js';
|
|
153
|
+
export { SITE_DELIVERY_CONTRACT_SCHEMA, SITE_DELIVERY_RESOLUTION_SCHEMA, defineSite, normalizeSiteDelivery, normalizeSourceProbe, resolveSiteRelease, probeReleaseDirectory, emitSiteDelivery, } from './site-delivery.js';
|
|
154
|
+
export { PRODUCTION_SCENARIO_PACK_SCHEMA, PRODUCTION_CI_PROFILE_SCHEMA, PRODUCTION_TEST_REPORT_SCHEMA, browserProductionScenarioPack, browserProductionCiProfile, normalizeScenarioPack, normalizeCiProfile, scenarioPackDigest, ciProfileDigest, buildProductionTestReport, productionTestReportDigest, emitProductionTestArtifacts, assertNoForbiddenRunners, } from './production-test-pack.js';
|
|
155
|
+
export { PRODUCTION_OBSERVABILITY_SCHEMA, PRODUCTION_TRACE_SCHEMA, REQUIRED_TRACE_FACETS, browserProductionObservability, normalizeObservability, redactSensitive, validateProductionTrace, buildCoveringProductionTrace, checkProductionBudgets, checkCapabilityClosure, applySecurityHeadersToCdnPolicy, measureDistBudgets, emitProductionObservability, observabilityDigest, } from './production-observability.js';
|
|
147
156
|
export { buildApplicationContext, buildFormatterContext, checkLocaleRuntime, checkSsrClientParity, createLocaleSession, formatMessageTemplate, formatterContextDigest, negotiateLocale, resolveMessageVariant, validateFormatterContext, } from './locale-runtime.js';
|
|
148
157
|
export { absoluteUrl, assertLocaleCacheKey, buildLocalePageMeta, buildLocaleRouteRealizationTable, checkLocaleRouter, commitLocaleRouteMetaTransition, localeAwareCacheKey, parseLocaleFromPath, planLocalePathNavigation, realizeRoutePath, resolveLinkHref, } from './locale-router.js';
|
|
149
158
|
export { assertHostMessageInvariant, assertServerErrorEnvelope, assertServerFormatContext, buildLocaleDeliveryResolution, checkLocaleDelivery, fallbackDigest, messageCatalogHash, proveMiniPackageMessages, validateNativeLocalePack, } from './locale-delivery.js';
|
package/dist/index.js
CHANGED
|
@@ -52,32 +52,34 @@ function platformShort(triple = platformTriple()) {
|
|
|
52
52
|
return triple;
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
|
-
* Resolve native `.node` via the platform optionalDependency
|
|
56
|
-
* (`@vmz/vmz-<short
|
|
55
|
+
* Resolve native `.node` via the platform optionalDependency
|
|
56
|
+
* (`@vmz/vmz-<short>`; transitional fallback `@vmz/vmz-<short>`).
|
|
57
57
|
* @returns {string}
|
|
58
58
|
*/
|
|
59
59
|
export function resolveNativePath() {
|
|
60
60
|
const triple = platformTriple();
|
|
61
61
|
const short = platformShort(triple);
|
|
62
|
-
const
|
|
62
|
+
const names = [`@vmz/vmz-${short}`, `@vmz/vmz-${short}`];
|
|
63
63
|
/** @type {string[]} */
|
|
64
64
|
const candidates = [];
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
65
|
+
for (const name of names) {
|
|
66
|
+
try {
|
|
67
|
+
const resolved = require.resolve(`${name}/package.json`);
|
|
68
|
+
const dir = path.dirname(resolved);
|
|
69
|
+
// Prefer platform-named binary; plain `vmz.node` is legacy-only.
|
|
70
|
+
candidates.push(path.join(dir, `vmz.${triple}.node`), path.join(dir, 'vmz.node'));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* optional dep not installed */
|
|
74
|
+
}
|
|
75
|
+
// pnpm may nest under the @vmz/vmz package's node_modules
|
|
76
|
+
candidates.push(path.join(pkgRoot, 'node_modules', name, `vmz.${triple}.node`), path.join(pkgRoot, 'node_modules', name, 'vmz.node'));
|
|
73
77
|
}
|
|
74
|
-
// pnpm may nest under the vmz package's node_modules
|
|
75
|
-
candidates.push(path.join(pkgRoot, 'node_modules', name, `vmz.${triple}.node`), path.join(pkgRoot, 'node_modules', name, 'vmz.node'));
|
|
76
78
|
for (const p of candidates) {
|
|
77
79
|
if (existsSync(p))
|
|
78
80
|
return p;
|
|
79
81
|
}
|
|
80
|
-
throw new Error(`vmz native addon not found for
|
|
82
|
+
throw new Error(`vmz native addon not found for @vmz/vmz-${short}. Run: pnpm napi:build (writes packages/runtimes/vmz-${short}/)\n` +
|
|
81
83
|
`Looked in:\n${candidates.map((c) => ` - ${c}`).join('\n')}`);
|
|
82
84
|
}
|
|
83
85
|
let _native;
|
|
@@ -316,12 +318,23 @@ export function checkApplicationDevTestDeployJson(hostRoot, packageRoots, dirtyP
|
|
|
316
318
|
return native.checkApplicationDevTestDeployJson(hostRoot, packageRoots, dirtyPaths);
|
|
317
319
|
}
|
|
318
320
|
export { createDevSession, listWatchedFiles, srcFingerprint } from './dev-session.js';
|
|
321
|
+
export { findAvailablePort } from './port.js';
|
|
319
322
|
export { runCli, parseArgs, printHelp, printGlobalHelp, printProjectHelp } from './cli.js';
|
|
320
323
|
export { findNearestProjectVmz, getInvocationContext, isGlobalAllowedCommand, isUnderNodeModules, resolveThisPackageRoot, resolveVmzBin, gateGlobalProjectCommand, } from './invocation.js';
|
|
321
324
|
export { resolveWorkspaceDirs, findPackageJson, readPackageMeta } from './resolve.js';
|
|
322
325
|
export { resolvePackageRoot, resolveWorkspacePackages } from './packages.js';
|
|
323
326
|
export { log } from './log.js';
|
|
324
327
|
export { cmdApplication, runCheck as runApplicationCheck } from './application-cmd.js';
|
|
328
|
+
export { cmdArtifact } from './release-cmd.js';
|
|
329
|
+
export { ARTIFACT_DIFF_SCHEMA, DELIVERY_ARTIFACT_MANIFEST_SCHEMA, RELEASE_ENVELOPE_SCHEMA, ROUTE_REALIZATION_TABLE_SCHEMA, atomicWritePointer, canonicalJson, diffArtifacts, loadReleaseEnvelope, packRelease, publishRelease, readPointer, rollbackRelease, sha256File, sha256Hex, } from './release-pack.js';
|
|
330
|
+
// APPLICATION_ARTIFACT_SCHEMA lives in @vmz/protocol (already re-exported above);
|
|
331
|
+
// release-pack keeps a local constant for envelope writes — do not dual-export the name.
|
|
332
|
+
export { STATIC_DELIVERY_MANIFEST_SCHEMA, emitWebStatic, } from './static-emit.js';
|
|
333
|
+
export { CONTENT_ADDRESSED_ASSETS_SCHEMA, emitContentAddressedAssets, resolveAssetByDigest, assertSharedAssetPath, contentAddressedAssetsDigest, } from './content-addressed-assets.js';
|
|
334
|
+
export { CDN_POLICY_MANIFEST_SCHEMA, CDN_ADAPTER_PROJECTION_SCHEMA, CACHE_HTML, CACHE_ASSET_IMMUTABLE, CACHE_META, buildCdnPolicyManifest, emitCdnPolicy, projectCdnAdapter, createLocalStaticHandler, listenLocalStaticHost, matchGlob, } from './cdn-policy.js';
|
|
335
|
+
export { SITE_DELIVERY_CONTRACT_SCHEMA, SITE_DELIVERY_RESOLUTION_SCHEMA, defineSite, normalizeSiteDelivery, normalizeSourceProbe, resolveSiteRelease, probeReleaseDirectory, emitSiteDelivery, } from './site-delivery.js';
|
|
336
|
+
export { PRODUCTION_SCENARIO_PACK_SCHEMA, PRODUCTION_CI_PROFILE_SCHEMA, PRODUCTION_TEST_REPORT_SCHEMA, browserProductionScenarioPack, browserProductionCiProfile, normalizeScenarioPack, normalizeCiProfile, scenarioPackDigest, ciProfileDigest, buildProductionTestReport, productionTestReportDigest, emitProductionTestArtifacts, assertNoForbiddenRunners, } from './production-test-pack.js';
|
|
337
|
+
export { PRODUCTION_OBSERVABILITY_SCHEMA, PRODUCTION_TRACE_SCHEMA, REQUIRED_TRACE_FACETS, browserProductionObservability, normalizeObservability, redactSensitive, validateProductionTrace, buildCoveringProductionTrace, checkProductionBudgets, checkCapabilityClosure, applySecurityHeadersToCdnPolicy, measureDistBudgets, emitProductionObservability, observabilityDigest, } from './production-observability.js';
|
|
325
338
|
export { buildApplicationContext, buildFormatterContext, checkLocaleRuntime, checkSsrClientParity, createLocaleSession, formatMessageTemplate, formatterContextDigest, negotiateLocale, resolveMessageVariant, validateFormatterContext, } from './locale-runtime.js';
|
|
326
339
|
export { absoluteUrl, assertLocaleCacheKey, buildLocalePageMeta, buildLocaleRouteRealizationTable, checkLocaleRouter, commitLocaleRouteMetaTransition, localeAwareCacheKey, parseLocaleFromPath, planLocalePathNavigation, realizeRoutePath, resolveLinkHref, } from './locale-router.js';
|
|
327
340
|
export { assertHostMessageInvariant, assertServerErrorEnvelope, assertServerFormatContext, buildLocaleDeliveryResolution, checkLocaleDelivery, fallbackDigest, messageCatalogHash, proveMiniPackageMessages, validateNativeLocalePack, } from './locale-delivery.js';
|
package/dist/invocation.d.ts
CHANGED
|
@@ -1,58 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `vmz` CLI invocation modes (JS gate only; Rust binary stays full).
|
|
3
3
|
*
|
|
4
|
+
* Product install face: workspace `vmz` / publish `@vmz/vmz` (bin still `vmz`).
|
|
5
|
+
*
|
|
4
6
|
* Three modes — do not collapse them:
|
|
5
7
|
* - **developer**: monorepo source checkout (`packages/runtimes/vmz`, not under node_modules)
|
|
6
|
-
* - **project**: app's `node_modules/vmz` or `node_modules/@vmz/vmz`
|
|
8
|
+
* - **project**: app's nearest `node_modules/vmz` or `node_modules/@vmz/vmz`
|
|
7
9
|
* - **global**: npm/pnpm global (or any install under node_modules that is not the nearest project one)
|
|
8
10
|
*
|
|
9
11
|
*/
|
|
10
|
-
/** @typedef {'developer' | 'project' | 'global'} InvocationMode */
|
|
11
12
|
/**
|
|
12
|
-
* Package root of the running `vmz` / `@vmz/vmz` install
|
|
13
|
+
* Package root of the running `vmz` / `@vmz/vmz` install.
|
|
13
14
|
* @param {string} [fromUrl]
|
|
14
15
|
*/
|
|
15
16
|
export declare function resolveThisPackageRoot(fromUrl?: string): string;
|
|
16
17
|
/**
|
|
17
|
-
* Walk from `startDir` for nearest project
|
|
18
|
+
* Walk from `startDir` for nearest project CLI package root.
|
|
18
19
|
* @param {string} startDir
|
|
19
|
-
* @returns {string | null}
|
|
20
|
+
* @returns {string | null}
|
|
20
21
|
*/
|
|
21
22
|
export declare function findNearestProjectVmz(startDir: any): string;
|
|
22
23
|
/**
|
|
23
|
-
* Resolve CLI entry
|
|
24
|
+
* Resolve CLI entry (`bin/vmz.js`).
|
|
24
25
|
* @param {string} packageRoot
|
|
25
26
|
* @returns {string | null}
|
|
26
27
|
*/
|
|
27
28
|
export declare function resolveVmzBin(packageRoot: any): string;
|
|
28
29
|
/**
|
|
29
|
-
* Install lives under a `node_modules` tree (npm -g, pnpm store link, etc.).
|
|
30
|
-
* Workspace source checkout (`packages/runtimes/vmz`) does not → developer mode.
|
|
31
30
|
* @param {string} packageRoot
|
|
32
31
|
*/
|
|
33
32
|
export declare function isUnderNodeModules(packageRoot: any): boolean;
|
|
34
33
|
/**
|
|
35
|
-
* Classify how this process was launched.
|
|
36
|
-
*
|
|
37
|
-
* | mode | thisPackageRoot | rule |
|
|
38
|
-
* |-------------|-----------------------------------------|-------------------------------------------|
|
|
39
|
-
* | developer | monorepo `packages/runtimes/vmz` | not under `node_modules` |
|
|
40
|
-
* | project | app `node_modules/(@vmz/)vmz` | under node_modules ∧ equals nearest |
|
|
41
|
-
* | global | global / unrelated node_modules install | under node_modules ∧ not nearest project |
|
|
42
|
-
*
|
|
43
34
|
* @param {{
|
|
44
35
|
* cwd?: string,
|
|
45
36
|
* thisPackageRoot?: string,
|
|
46
37
|
* }} [opts]
|
|
47
|
-
* @returns {{
|
|
48
|
-
* mode: InvocationMode,
|
|
49
|
-
* cwd: string,
|
|
50
|
-
* thisPackageRoot: string,
|
|
51
|
-
* nearestProjectVmz: string | null,
|
|
52
|
-
* isDeveloper: boolean,
|
|
53
|
-
* isProjectLocal: boolean,
|
|
54
|
-
* isGlobalLike: boolean,
|
|
55
|
-
* }}
|
|
56
38
|
*/
|
|
57
39
|
export declare function getInvocationContext(opts?: {}): {
|
|
58
40
|
mode: any;
|
|
@@ -65,21 +47,16 @@ export declare function getInvocationContext(opts?: {}): {
|
|
|
65
47
|
isGlobalLike: boolean;
|
|
66
48
|
};
|
|
67
49
|
/**
|
|
68
|
-
* Commands allowed in **global** mode without re-exec / refusal.
|
|
69
|
-
* Developer + project modes allow the full CLI.
|
|
70
50
|
* @param {string | undefined} cmd
|
|
71
51
|
*/
|
|
72
52
|
export declare function isGlobalAllowedCommand(cmd: any): boolean;
|
|
73
53
|
/**
|
|
74
54
|
* @param {string} bin
|
|
75
|
-
* @param {string[]} argv
|
|
55
|
+
* @param {string[]} argv
|
|
76
56
|
* @returns {Promise<number>}
|
|
77
57
|
*/
|
|
78
58
|
export declare function reexecProjectVmz(bin: any, argv: any): Promise<unknown>;
|
|
79
59
|
/**
|
|
80
|
-
* Guard for project-only commands when the current install is **global** mode.
|
|
81
|
-
* Developer / project → proceed. Global + local present → re-exec. Global alone → refuse.
|
|
82
|
-
*
|
|
83
60
|
* @returns {Promise<{ action: 'proceed' } | { action: 'exit', code: number }>}
|
|
84
61
|
*/
|
|
85
62
|
export declare function gateGlobalProjectCommand(opts: any): Promise<{
|