@vmz/vmz 0.1.13 → 0.1.14
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.js +18 -7
- package/dist/cdn-policy.js +5 -4
- package/dist/cli.js +106 -38
- package/dist/content-addressed-assets.d.ts +23 -2
- package/dist/content-addressed-assets.js +178 -32
- package/dist/delivery-profile.d.ts +30 -2
- package/dist/delivery-profile.js +139 -5
- package/dist/dev-session.d.ts +1 -0
- package/dist/dev-session.js +269 -65
- package/dist/dev-watch-roots.d.ts +80 -0
- package/dist/dev-watch-roots.js +245 -0
- package/dist/document-build.d.ts +2 -1
- package/dist/document-build.js +104 -82
- package/dist/document-cmd.js +2 -9
- package/dist/document-enrich.d.ts +2 -1
- package/dist/document-enrich.js +15 -4
- package/dist/document-integrate.js +29 -21
- package/dist/document-layout-render.d.ts +20 -0
- package/dist/document-layout-render.js +87 -0
- package/dist/document-routing-config.d.ts +13 -0
- package/dist/document-routing-config.js +40 -0
- package/dist/embedded-packaging.js +2 -1
- package/dist/index.d.ts +18 -2
- package/dist/index.js +63 -4
- package/dist/locale-check.js +28 -61
- package/dist/locale-cmd.js +10 -42
- package/dist/locale-route-emit.d.ts +4 -1
- package/dist/locale-route-emit.js +7 -32
- package/dist/locale-router.js +26 -8
- package/dist/native-addon.d.ts +9 -0
- package/dist/native-addon.js +84 -0
- package/dist/pack.js +3 -2
- package/dist/pretty-json.d.ts +19 -0
- package/dist/pretty-json.js +43 -0
- package/dist/production-observability.js +3 -2
- package/dist/production-test-pack.js +4 -3
- package/dist/public-static-assets.d.ts +24 -0
- package/dist/public-static-assets.js +140 -0
- package/dist/release-pack.js +5 -18
- package/dist/route-path.d.ts +35 -0
- package/dist/route-path.js +77 -0
- package/dist/server-artifact.js +4 -3
- package/dist/site-delivery.js +4 -3
- package/dist/site-favicon.d.ts +31 -0
- package/dist/site-favicon.js +140 -0
- package/dist/static-emit.d.ts +21 -0
- package/dist/static-emit.js +134 -97
- package/dist/test-cmd.js +2 -1
- package/dist/wechat-packaging.d.ts +22 -0
- package/dist/wechat-packaging.js +59 -0
- package/package.json +12 -12
package/dist/locale-cmd.js
CHANGED
|
@@ -12,6 +12,7 @@ import { checkLocaleRouter } from './locale-router.js';
|
|
|
12
12
|
import { checkLocaleRuntime } from './locale-runtime.js';
|
|
13
13
|
import { checkLocaleConformance, diffLocaleCatalogs, explainLocaleMessage, extractHardcodedText, pseudoLocalizeCatalog, } from './locale-tooling.js';
|
|
14
14
|
import { log } from './log.js';
|
|
15
|
+
import { emitPrettyJson, generatePrettyJson } from './pretty-json.js';
|
|
15
16
|
function printLocaleHelp() {
|
|
16
17
|
console.log(`vmz locale — /locales application i18n (–)
|
|
17
18
|
|
|
@@ -91,14 +92,7 @@ function cmdLocaleCheck(args) {
|
|
|
91
92
|
const report = checkLocales({ projectRoot, strict: Boolean(args.strict) });
|
|
92
93
|
const jsonOut = args.json;
|
|
93
94
|
if (jsonOut) {
|
|
94
|
-
|
|
95
|
-
if (typeof jsonOut === 'string') {
|
|
96
|
-
fs.writeFileSync(jsonOut, `${text}\n`, 'utf8');
|
|
97
|
-
log.info(`wrote ${jsonOut}`);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
console.log(text);
|
|
101
|
-
}
|
|
95
|
+
emitPrettyJson(jsonOut, report, { logWrote: (p) => log.info(`wrote ${p}`) });
|
|
102
96
|
}
|
|
103
97
|
else {
|
|
104
98
|
for (const d of report.diagnostics) {
|
|
@@ -151,7 +145,7 @@ function cmdLocaleRename(args) {
|
|
|
151
145
|
const report = checkLocales({ projectRoot, checkUnused: false });
|
|
152
146
|
const plan = planLocaleRename(report, fromId, toId);
|
|
153
147
|
if (args.json) {
|
|
154
|
-
|
|
148
|
+
emitPrettyJson(args.json, plan);
|
|
155
149
|
}
|
|
156
150
|
else if (plan.status !== 'ready') {
|
|
157
151
|
log.error(plan.error || 'rename failed');
|
|
@@ -181,14 +175,7 @@ function cmdLocaleRuntimeCheck(args) {
|
|
|
181
175
|
});
|
|
182
176
|
const { session: _session, ...jsonReport } = report;
|
|
183
177
|
if (args.json) {
|
|
184
|
-
|
|
185
|
-
if (typeof args.json === 'string') {
|
|
186
|
-
fs.writeFileSync(args.json, `${text}\n`, 'utf8');
|
|
187
|
-
log.info(`wrote ${args.json}`);
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.log(text);
|
|
191
|
-
}
|
|
178
|
+
emitJson(args, jsonReport);
|
|
192
179
|
}
|
|
193
180
|
else {
|
|
194
181
|
for (const d of report.diagnostics) {
|
|
@@ -231,14 +218,7 @@ function cmdLocaleRouterCheck(args) {
|
|
|
231
218
|
origin: routesFile.origin || 'https://example.test',
|
|
232
219
|
});
|
|
233
220
|
if (args.json) {
|
|
234
|
-
|
|
235
|
-
if (typeof args.json === 'string') {
|
|
236
|
-
fs.writeFileSync(args.json, `${text}\n`, 'utf8');
|
|
237
|
-
log.info(`wrote ${args.json}`);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
console.log(text);
|
|
241
|
-
}
|
|
221
|
+
emitJson(args, report);
|
|
242
222
|
}
|
|
243
223
|
else {
|
|
244
224
|
for (const d of report.diagnostics) {
|
|
@@ -268,14 +248,7 @@ function cmdLocaleDeliveryCheck(args) {
|
|
|
268
248
|
planVersion: 'plan.v0',
|
|
269
249
|
});
|
|
270
250
|
if (args.json) {
|
|
271
|
-
|
|
272
|
-
if (typeof args.json === 'string') {
|
|
273
|
-
fs.writeFileSync(args.json, `${text}\n`, 'utf8');
|
|
274
|
-
log.info(`wrote ${args.json}`);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
console.log(text);
|
|
278
|
-
}
|
|
251
|
+
emitJson(args, report);
|
|
279
252
|
}
|
|
280
253
|
else {
|
|
281
254
|
for (const d of report.diagnostics) {
|
|
@@ -290,14 +263,9 @@ function cmdLocaleDeliveryCheck(args) {
|
|
|
290
263
|
return report.status === 'ready' ? 0 : 1;
|
|
291
264
|
}
|
|
292
265
|
function emitJson(args, report) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
log.info(`wrote ${args.json}`);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
console.log(text);
|
|
300
|
-
}
|
|
266
|
+
emitPrettyJson(args.json, report, {
|
|
267
|
+
logWrote: (p) => log.info(`wrote ${p}`),
|
|
268
|
+
});
|
|
301
269
|
}
|
|
302
270
|
function cmdLocaleExplain(args) {
|
|
303
271
|
const messageId = typeof args._[0] === 'string' ? args._[0] : '';
|
|
@@ -401,7 +369,7 @@ function cmdLocalePseudo(args) {
|
|
|
401
369
|
const outDir = (typeof args.out === 'string' && args.out) || path.join(projectRoot, 'dist', 'locales-pseudo');
|
|
402
370
|
fs.mkdirSync(outDir, { recursive: true });
|
|
403
371
|
const outFile = path.join(outDir, `${report.pseudoLocale}.json`);
|
|
404
|
-
fs.writeFileSync(outFile, `${
|
|
372
|
+
fs.writeFileSync(outFile, `${generatePrettyJson(report.catalog)}\n`, 'utf8');
|
|
405
373
|
log.info(`locale pseudo: source=${sourceLocale} keys=${Object.keys(report.catalog).length} out=${outFile}`);
|
|
406
374
|
}
|
|
407
375
|
return report.status === 'ready' ? 0 : 1;
|
|
@@ -26,7 +26,10 @@ export declare function emitLocaleRouteRealization(projectRoot: any, distDir: an
|
|
|
26
26
|
defaultLocale: any;
|
|
27
27
|
};
|
|
28
28
|
origin: string;
|
|
29
|
-
routes:
|
|
29
|
+
routes: {
|
|
30
|
+
routeId: string;
|
|
31
|
+
path: string;
|
|
32
|
+
}[];
|
|
30
33
|
realizations: any[];
|
|
31
34
|
pageMetas: any[];
|
|
32
35
|
};
|
|
@@ -7,34 +7,9 @@ import fs from 'node:fs';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { checkLocales, localeHasErrors } from './locale-check.js';
|
|
9
9
|
import { buildLocalePageMeta, buildLocaleRouteRealizationTable } from './locale-router.js';
|
|
10
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
11
|
+
import { listPublicPageUnits, unitBrowserPathPattern } from './route-path.js';
|
|
10
12
|
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
13
|
/**
|
|
39
14
|
* @param {string} projectRoot
|
|
40
15
|
* @param {string} distDir
|
|
@@ -59,10 +34,10 @@ export function emitLocaleRouteRealization(projectRoot, distDir, opts = {}) {
|
|
|
59
34
|
};
|
|
60
35
|
}
|
|
61
36
|
const deployment = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
|
|
62
|
-
const pages = (deployment
|
|
37
|
+
const pages = listPublicPageUnits(deployment);
|
|
63
38
|
const routes = pages.map((u) => ({
|
|
64
39
|
routeId: String(u.chunkId),
|
|
65
|
-
path:
|
|
40
|
+
path: unitBrowserPathPattern(u),
|
|
66
41
|
}));
|
|
67
42
|
const localeEntries = report.manifest?.locales || [];
|
|
68
43
|
const locales = localeEntries.map((l) => l.id);
|
|
@@ -117,14 +92,14 @@ export function emitLocaleRouteRealization(projectRoot, distDir, opts = {}) {
|
|
|
117
92
|
const vmzDir = path.join(distDir, '_vmz');
|
|
118
93
|
fs.mkdirSync(vmzDir, { recursive: true });
|
|
119
94
|
const outPath = path.join(vmzDir, 'locale-route-realization.json');
|
|
120
|
-
|
|
95
|
+
writePrettyJsonFile(outPath, artifact);
|
|
121
96
|
const manifestOut = path.join(vmzDir, 'locale-manifest.json');
|
|
122
|
-
|
|
97
|
+
writePrettyJsonFile(manifestOut, {
|
|
123
98
|
schema: 'vmz.locale.manifest.v0',
|
|
124
99
|
defaultLocale,
|
|
125
100
|
locales: artifact.locales,
|
|
126
101
|
routing: artifact.routing,
|
|
127
|
-
}
|
|
102
|
+
});
|
|
128
103
|
return {
|
|
129
104
|
ok: true,
|
|
130
105
|
written: ['_vmz/locale-route-realization.json', '_vmz/locale-manifest.json'],
|
package/dist/locale-router.js
CHANGED
|
@@ -90,16 +90,34 @@ export function buildLocaleRouteRealizationTable(input) {
|
|
|
90
90
|
pathPattern: normalizePath(route.path),
|
|
91
91
|
};
|
|
92
92
|
realizations.push(entry);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
// `none` / `domain`: LocaleId is Host preference — many locales share one URL path.
|
|
94
|
+
// Collision only when *different RouteIds* claim the same path.
|
|
95
|
+
if (routing.strategy === 'none' || routing.strategy === 'domain') {
|
|
96
|
+
const prevRoute = pathOwners.get(entry.path);
|
|
97
|
+
if (prevRoute && prevRoute !== route.routeId) {
|
|
98
|
+
diagnostics.push({
|
|
99
|
+
code: DIAG_LOCALE_ROUTE_COLLISION,
|
|
100
|
+
severity: 'error',
|
|
101
|
+
message: `path ${entry.path} claimed by ${prevRoute} and ${route.routeId}`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
pathOwners.set(entry.path, route.routeId);
|
|
106
|
+
}
|
|
100
107
|
}
|
|
101
108
|
else {
|
|
102
|
-
|
|
109
|
+
const owner = `${route.routeId}@${localeId}`;
|
|
110
|
+
const prev = pathOwners.get(entry.path);
|
|
111
|
+
if (prev && prev !== owner) {
|
|
112
|
+
diagnostics.push({
|
|
113
|
+
code: DIAG_LOCALE_ROUTE_COLLISION,
|
|
114
|
+
severity: 'error',
|
|
115
|
+
message: `path ${entry.path} claimed by ${prev} and ${owner}`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
pathOwners.set(entry.path, owner);
|
|
120
|
+
}
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Load the N-API `.node` addon without importing `index.js`
|
|
4
|
+
* (avoids cycles with modules re-exported from the package entry).
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
/** @type {any} */
|
|
12
|
+
let cached;
|
|
13
|
+
/**
|
|
14
|
+
* Load the N-API addon or throw (production printers must not fall back to TS).
|
|
15
|
+
* @returns {any}
|
|
16
|
+
*/
|
|
17
|
+
export function requireNativeAddon() {
|
|
18
|
+
const native = tryLoadNativeAddon();
|
|
19
|
+
if (!native) {
|
|
20
|
+
throw new Error('vmz native addon missing — run `pnpm napi:build` (CodeGenerators live in vmz-generator via N-API)');
|
|
21
|
+
}
|
|
22
|
+
return native;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* @returns {any | null}
|
|
26
|
+
*/
|
|
27
|
+
export function tryLoadNativeAddon() {
|
|
28
|
+
if (cached !== undefined)
|
|
29
|
+
return cached;
|
|
30
|
+
try {
|
|
31
|
+
const envPath = (typeof process.env.VMZ_NATIVE_NODE === 'string' && process.env.VMZ_NATIVE_NODE.trim()) || '';
|
|
32
|
+
if (envPath) {
|
|
33
|
+
cached = require(path.resolve(envPath));
|
|
34
|
+
return cached;
|
|
35
|
+
}
|
|
36
|
+
const { platform, arch } = process;
|
|
37
|
+
let triple = `${platform}-${arch}`;
|
|
38
|
+
if (platform === 'win32' && arch === 'x64')
|
|
39
|
+
triple = 'win32-x64-msvc';
|
|
40
|
+
else if (platform === 'win32' && arch === 'arm64')
|
|
41
|
+
triple = 'win32-arm64-msvc';
|
|
42
|
+
else if (platform === 'darwin' && arch === 'arm64')
|
|
43
|
+
triple = 'darwin-arm64';
|
|
44
|
+
else if (platform === 'darwin' && arch === 'x64')
|
|
45
|
+
triple = 'darwin-x64';
|
|
46
|
+
else if (platform === 'linux' && arch === 'x64')
|
|
47
|
+
triple = 'linux-x64-gnu';
|
|
48
|
+
else if (platform === 'linux' && arch === 'arm64')
|
|
49
|
+
triple = 'linux-arm64-gnu';
|
|
50
|
+
const short = triple === 'win32-x64-msvc'
|
|
51
|
+
? 'win32-x64'
|
|
52
|
+
: triple === 'win32-arm64-msvc'
|
|
53
|
+
? 'win32-arm64'
|
|
54
|
+
: triple === 'linux-x64-gnu'
|
|
55
|
+
? 'linux-x64'
|
|
56
|
+
: triple === 'linux-arm64-gnu'
|
|
57
|
+
? 'linux-arm64'
|
|
58
|
+
: triple;
|
|
59
|
+
const pkgRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
60
|
+
const name = `@vmz/vmz-${short}`;
|
|
61
|
+
/** @type {string[]} */
|
|
62
|
+
const candidates = [];
|
|
63
|
+
try {
|
|
64
|
+
const resolved = require.resolve(`${name}/package.json`);
|
|
65
|
+
const dir = path.dirname(resolved);
|
|
66
|
+
candidates.push(path.join(dir, `vmz.${triple}.node`), path.join(dir, 'vmz.node'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
/* optional */
|
|
70
|
+
}
|
|
71
|
+
candidates.push(path.join(pkgRoot, 'node_modules', name, `vmz.${triple}.node`), path.join(pkgRoot, 'node_modules', name, 'vmz.node'));
|
|
72
|
+
for (const p of candidates) {
|
|
73
|
+
if (fs.existsSync(p)) {
|
|
74
|
+
cached = require(p);
|
|
75
|
+
return cached;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
cached = null;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
cached = null;
|
|
82
|
+
}
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
package/dist/pack.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
// @ts-nocheck
|
|
7
7
|
import crypto from 'node:crypto';
|
|
8
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync
|
|
8
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { loadDeploymentIr, planBundleInputs } from './bundler-adapter.js';
|
|
11
11
|
import { packClientBareImports } from './pack-client-packages.js';
|
|
12
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
12
13
|
export const PACK_MANIFEST_SCHEMA = 'vmz.pack.manifest.v0';
|
|
13
14
|
/**
|
|
14
15
|
* Ensure dom split companions sit next to vmz-dom.js (barrel imports ./dom-core.js).
|
|
@@ -100,7 +101,7 @@ export function packFromDeploymentIr(outDir, opts = {}) {
|
|
|
100
101
|
const vmzDir = path.join(outDir, '_vmz');
|
|
101
102
|
mkdirSync(vmzDir, { recursive: true });
|
|
102
103
|
const file = path.join(vmzDir, 'pack-manifest.json');
|
|
103
|
-
|
|
104
|
+
writePrettyJsonFile(file, body);
|
|
104
105
|
return { manifest: body, path: file, clientPackages };
|
|
105
106
|
}
|
|
106
107
|
function stableStringify(value) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pretty-print a value through `vmz-generator` (N-API).
|
|
3
|
+
* @param {unknown} value
|
|
4
|
+
* @returns {string} Pretty JSON without trailing newline.
|
|
5
|
+
*/
|
|
6
|
+
export declare function generatePrettyJson(value: any): any;
|
|
7
|
+
/**
|
|
8
|
+
* Write a pretty JSON artifact with a trailing newline.
|
|
9
|
+
* @param {string} filePath
|
|
10
|
+
* @param {unknown} value
|
|
11
|
+
*/
|
|
12
|
+
export declare function writePrettyJsonFile(filePath: any, value: any): void;
|
|
13
|
+
/**
|
|
14
|
+
* CLI `--json` helper: write to a path when `target` is a string, else stdout.
|
|
15
|
+
* @param {string | boolean | undefined} target
|
|
16
|
+
* @param {unknown} value
|
|
17
|
+
* @param {{ logWrote?: (path: string) => void }} [opts]
|
|
18
|
+
*/
|
|
19
|
+
export declare function emitPrettyJson(target: any, value: any, opts?: {}): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Production JSON artifact printer — always via N-API JsonCodeGenerator.
|
|
4
|
+
* Do not use `JSON.stringify(x, null, 2)` for on-disk artifacts.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
8
|
+
/**
|
|
9
|
+
* Pretty-print a value through `vmz-generator` (N-API).
|
|
10
|
+
* @param {unknown} value
|
|
11
|
+
* @returns {string} Pretty JSON without trailing newline.
|
|
12
|
+
*/
|
|
13
|
+
export function generatePrettyJson(value) {
|
|
14
|
+
const native = requireNativeAddon();
|
|
15
|
+
if (typeof native.generatePrettyJson !== 'function') {
|
|
16
|
+
throw new Error('vmz native addon missing generatePrettyJson — rebuild with `pnpm napi:build`');
|
|
17
|
+
}
|
|
18
|
+
return native.generatePrettyJson(JSON.stringify(value));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Write a pretty JSON artifact with a trailing newline.
|
|
22
|
+
* @param {string} filePath
|
|
23
|
+
* @param {unknown} value
|
|
24
|
+
*/
|
|
25
|
+
export function writePrettyJsonFile(filePath, value) {
|
|
26
|
+
fs.writeFileSync(filePath, `${generatePrettyJson(value)}\n`, 'utf8');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* CLI `--json` helper: write to a path when `target` is a string, else stdout.
|
|
30
|
+
* @param {string | boolean | undefined} target
|
|
31
|
+
* @param {unknown} value
|
|
32
|
+
* @param {{ logWrote?: (path: string) => void }} [opts]
|
|
33
|
+
*/
|
|
34
|
+
export function emitPrettyJson(target, value, opts = {}) {
|
|
35
|
+
const text = generatePrettyJson(value);
|
|
36
|
+
if (typeof target === 'string') {
|
|
37
|
+
fs.writeFileSync(target, `${text}\n`, 'utf8');
|
|
38
|
+
if (typeof opts.logWrote === 'function')
|
|
39
|
+
opts.logWrote(target);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(text);
|
|
43
|
+
}
|
|
@@ -7,6 +7,7 @@ import crypto from 'node:crypto';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { canonicalJson, sha256Hex } from './release-pack.js';
|
|
10
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
10
11
|
export const PRODUCTION_OBSERVABILITY_SCHEMA = 'vmz.production.observability.v0';
|
|
11
12
|
export const PRODUCTION_TRACE_SCHEMA = 'vmz.production.trace.v0';
|
|
12
13
|
/** Facets that production traces must be able to carry (08 A5). */
|
|
@@ -457,8 +458,8 @@ export function emitProductionObservability(distDir, overrides = {}, meta = {})
|
|
|
457
458
|
fs.mkdirSync(vmzDir, { recursive: true });
|
|
458
459
|
const contractPath = path.join(vmzDir, 'production-observability.json');
|
|
459
460
|
const tracePath = path.join(vmzDir, 'production-trace.sample.json');
|
|
460
|
-
|
|
461
|
-
|
|
461
|
+
writePrettyJsonFile(contractPath, contract);
|
|
462
|
+
writePrettyJsonFile(tracePath, trace);
|
|
462
463
|
return { contract, trace, contractPath, tracePath };
|
|
463
464
|
}
|
|
464
465
|
export function observabilityDigest(contract) {
|
|
@@ -8,6 +8,7 @@ import crypto from 'node:crypto';
|
|
|
8
8
|
import fs from 'node:fs';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { canonicalJson, sha256Hex } from './release-pack.js';
|
|
11
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
11
12
|
export const PRODUCTION_SCENARIO_PACK_SCHEMA = 'vmz.production.scenario_pack.v0';
|
|
12
13
|
export const PRODUCTION_CI_PROFILE_SCHEMA = 'vmz.production.ci_profile.v0';
|
|
13
14
|
export const PRODUCTION_TEST_REPORT_SCHEMA = 'vmz.production.test_report.v0';
|
|
@@ -431,9 +432,9 @@ export function emitProductionTestArtifacts(root, report, pack, profile) {
|
|
|
431
432
|
const reportPath = path.join(dir, 'report.json');
|
|
432
433
|
const packPath = path.join(dir, 'scenario-pack.json');
|
|
433
434
|
const profilePath = path.join(dir, 'ci-profile.json');
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
435
|
+
writePrettyJsonFile(reportPath, stamped);
|
|
436
|
+
writePrettyJsonFile(packPath, pack);
|
|
437
|
+
writePrettyJsonFile(profilePath, profile);
|
|
437
438
|
return { reportPath, packPath, profilePath, report: stamped };
|
|
438
439
|
}
|
|
439
440
|
/** Assert CI profile forbids JS test-runner disguise. */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque site-root static files: project `public/**` → deploy root (static / web-static).
|
|
3
|
+
* Not dependency packaging (no node_modules / @dxo/* scanning).
|
|
4
|
+
*/
|
|
5
|
+
export declare const PUBLIC_STATIC_ASSETS_SCHEMA = "vmz.public_static_assets.v0";
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} distDir
|
|
8
|
+
* @param {{ projectRoot?: string, publicDir?: string }} [opts]
|
|
9
|
+
*/
|
|
10
|
+
export declare function emitPublicStaticAssets(distDir: any, opts?: {}): {
|
|
11
|
+
schema: string;
|
|
12
|
+
status: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
fileCount: number;
|
|
15
|
+
files: any[];
|
|
16
|
+
skippedConflicts: any[];
|
|
17
|
+
} | {
|
|
18
|
+
schema: string;
|
|
19
|
+
status: string;
|
|
20
|
+
source: string;
|
|
21
|
+
fileCount: number;
|
|
22
|
+
files: any[];
|
|
23
|
+
skippedConflicts: any[];
|
|
24
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opaque site-root static files: project `public/**` → deploy root (static / web-static).
|
|
3
|
+
* Not dependency packaging (no node_modules / @dxo/* scanning).
|
|
4
|
+
*/
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
9
|
+
export const PUBLIC_STATIC_ASSETS_SCHEMA = 'vmz.public_static_assets.v0';
|
|
10
|
+
/** Relative paths under dist that VMZ owns; public/ must not clobber. */
|
|
11
|
+
const RESERVED_EXACT = new Set([
|
|
12
|
+
'entry-client.js',
|
|
13
|
+
'entry-event.js',
|
|
14
|
+
'vmz-dom.js',
|
|
15
|
+
'vmz-runtime.js',
|
|
16
|
+
'vmz-http.js',
|
|
17
|
+
'vmz-client-nav.js',
|
|
18
|
+
'vmz.css',
|
|
19
|
+
'vmz-designs.css',
|
|
20
|
+
'vmz-style.css',
|
|
21
|
+
'vmz-deployment.json',
|
|
22
|
+
'vmz-routes.json',
|
|
23
|
+
'vmz-plugin-targets.json',
|
|
24
|
+
'sitemap.xml',
|
|
25
|
+
'robots.txt',
|
|
26
|
+
'404.html',
|
|
27
|
+
]);
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} distDir
|
|
30
|
+
* @param {{ projectRoot?: string, publicDir?: string }} [opts]
|
|
31
|
+
*/
|
|
32
|
+
export function emitPublicStaticAssets(distDir, opts = {}) {
|
|
33
|
+
const absDist = path.resolve(distDir);
|
|
34
|
+
const projectRoot = opts.projectRoot ? path.resolve(opts.projectRoot) : null;
|
|
35
|
+
const publicDir = opts.publicDir ? path.resolve(opts.publicDir) : projectRoot ? path.join(projectRoot, 'public') : null;
|
|
36
|
+
if (!publicDir || !fs.existsSync(publicDir) || !fs.statSync(publicDir).isDirectory()) {
|
|
37
|
+
const skipped = {
|
|
38
|
+
schema: PUBLIC_STATIC_ASSETS_SCHEMA,
|
|
39
|
+
status: 'skipped',
|
|
40
|
+
reason: publicDir ? 'public/ missing' : 'no projectRoot',
|
|
41
|
+
fileCount: 0,
|
|
42
|
+
files: [],
|
|
43
|
+
skippedConflicts: [],
|
|
44
|
+
};
|
|
45
|
+
writeArtifact(absDist, skipped);
|
|
46
|
+
return skipped;
|
|
47
|
+
}
|
|
48
|
+
/** @type {Array<{ path: string, bytes: number }>} */
|
|
49
|
+
const files = [];
|
|
50
|
+
/** @type {Array<{ path: string, reason: string }>} */
|
|
51
|
+
const skippedConflicts = [];
|
|
52
|
+
walkFiles(publicDir, (absFile) => {
|
|
53
|
+
const rel = path.relative(publicDir, absFile).replace(/\\/g, '/');
|
|
54
|
+
if (!rel || rel.startsWith('..'))
|
|
55
|
+
return;
|
|
56
|
+
if (shouldSkipReserved(rel)) {
|
|
57
|
+
skippedConflicts.push({ path: rel, reason: 'reserved-vmz-path' });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const dest = path.join(absDist, ...rel.split('/'));
|
|
61
|
+
if (fs.existsSync(dest) && isReservedExisting(rel, dest, absDist)) {
|
|
62
|
+
skippedConflicts.push({ path: rel, reason: 'would-clobber-generated' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
66
|
+
fs.copyFileSync(absFile, dest);
|
|
67
|
+
files.push({ path: rel, bytes: fs.statSync(dest).size });
|
|
68
|
+
});
|
|
69
|
+
files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
70
|
+
const artifact = {
|
|
71
|
+
schema: PUBLIC_STATIC_ASSETS_SCHEMA,
|
|
72
|
+
status: 'ready',
|
|
73
|
+
source: 'public/',
|
|
74
|
+
fileCount: files.length,
|
|
75
|
+
files,
|
|
76
|
+
skippedConflicts,
|
|
77
|
+
};
|
|
78
|
+
writeArtifact(absDist, artifact);
|
|
79
|
+
return artifact;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} rel
|
|
83
|
+
*/
|
|
84
|
+
function shouldSkipReserved(rel) {
|
|
85
|
+
if (rel === '_vmz' || rel.startsWith('_vmz/'))
|
|
86
|
+
return true;
|
|
87
|
+
if (RESERVED_EXACT.has(rel))
|
|
88
|
+
return true;
|
|
89
|
+
if (rel.startsWith('pages/') || rel.startsWith('components/'))
|
|
90
|
+
return true;
|
|
91
|
+
if (/\.client\.js$/i.test(rel))
|
|
92
|
+
return true;
|
|
93
|
+
if (rel.startsWith('assets/') && /^assets\/[a-f0-9]{64}\./i.test(rel))
|
|
94
|
+
return true;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* After compile, many generated files already exist; do not let public overwrite them.
|
|
99
|
+
* Allow overwrite of non-reserved paths (e.g. re-copy same wasm).
|
|
100
|
+
* @param {string} rel
|
|
101
|
+
* @param {string} dest
|
|
102
|
+
* @param {string} absDist
|
|
103
|
+
*/
|
|
104
|
+
function isReservedExisting(rel, dest, absDist) {
|
|
105
|
+
if (shouldSkipReserved(rel))
|
|
106
|
+
return true;
|
|
107
|
+
// Never overwrite generated HTML shells that static-emit will (or did) write.
|
|
108
|
+
if (/\.html$/i.test(rel) && fs.existsSync(dest)) {
|
|
109
|
+
const text = fs.readFileSync(dest, 'utf8');
|
|
110
|
+
if (text.includes('data-vmz-page') || text.includes('entry-client'))
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
void absDist;
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* @param {string} dir
|
|
118
|
+
* @param {(absFile: string) => void} onFile
|
|
119
|
+
*/
|
|
120
|
+
function walkFiles(dir, onFile) {
|
|
121
|
+
for (const name of fs.readdirSync(dir)) {
|
|
122
|
+
if (name === '.' || name === '..')
|
|
123
|
+
continue;
|
|
124
|
+
const full = path.join(dir, name);
|
|
125
|
+
const st = fs.statSync(full);
|
|
126
|
+
if (st.isDirectory())
|
|
127
|
+
walkFiles(full, onFile);
|
|
128
|
+
else if (st.isFile())
|
|
129
|
+
onFile(full);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* @param {string} absDist
|
|
134
|
+
* @param {Record<string, unknown>} artifact
|
|
135
|
+
*/
|
|
136
|
+
function writeArtifact(absDist, artifact) {
|
|
137
|
+
const vmzDir = path.join(absDist, '_vmz');
|
|
138
|
+
fs.mkdirSync(vmzDir, { recursive: true });
|
|
139
|
+
writePrettyJsonFile(path.join(vmzDir, 'public-static-assets.json'), artifact);
|
|
140
|
+
}
|