@vmz/vmz 0.1.13 → 0.1.15
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/author-input.d.ts +19 -0
- package/dist/author-input.js +52 -0
- 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 +4 -3
- package/dist/document-build.js +104 -82
- package/dist/document-check.d.ts +24 -5
- package/dist/document-check.js +64 -101
- 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 +17 -0
- package/dist/document-routing-config.js +31 -0
- package/dist/document-schema.js +8 -1
- 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 +81 -183
- package/dist/locale-cmd.js +13 -45
- 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 +13 -14
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
-
* Logical paths stay available for serve/dev;
|
|
4
|
-
*
|
|
3
|
+
* Logical paths stay available for serve/dev; static HTML rewrites to hashed URLs.
|
|
4
|
+
* CSS aggregators (vmz.css) rewrite `@import` to hashed sibling paths under assets/.
|
|
5
|
+
* JS under assets/ always rewrites ESM `./` → `../` so barrels (vmz-dom → dom-core)
|
|
6
|
+
* resolve at dist root — never prefer hashed siblings for JS (second-hop 404).
|
|
5
7
|
*/
|
|
6
8
|
// @ts-nocheck
|
|
7
9
|
import crypto from 'node:crypto';
|
|
8
10
|
import fs from 'node:fs';
|
|
9
11
|
import path from 'node:path';
|
|
10
12
|
import { canonicalJson, sha256Hex } from './release-pack.js';
|
|
13
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
11
14
|
export const CONTENT_ADDRESSED_ASSETS_SCHEMA = 'vmz.content_addressed_assets.v0';
|
|
12
15
|
/** Immutable delivery candidates (client-facing bytes). */
|
|
13
16
|
const DEFAULT_CANDIDATES = [
|
|
@@ -15,11 +18,64 @@ const DEFAULT_CANDIDATES = [
|
|
|
15
18
|
'entry-event.js',
|
|
16
19
|
'vmz.css',
|
|
17
20
|
'vmz-designs.css',
|
|
21
|
+
'vmz-style.css',
|
|
18
22
|
'vmz-dom.js',
|
|
19
23
|
'vmz-runtime.js',
|
|
20
24
|
'vmz-http.js',
|
|
21
25
|
'vmz-client-nav.js',
|
|
22
26
|
];
|
|
27
|
+
/** CSS files that may @import other logical CSS; processed after leaf CSS is hashed. */
|
|
28
|
+
const CSS_AGGREGATORS = new Set(['vmz.css']);
|
|
29
|
+
/** JS entry shells hashed under assets/; relative ESM must be rewritten first. */
|
|
30
|
+
const JS_ENTRY_AGGREGATORS = new Set(['entry-client.js', 'entry-event.js']);
|
|
31
|
+
const CSS_IMPORT_RE = /@import\s*(?:url\()?['"]?(\.\/)?([^'")\s;]+)['"]?\)?/gi;
|
|
32
|
+
/**
|
|
33
|
+
* Rewrite relative `@import "./foo.css"` to hashed paths under assets/.
|
|
34
|
+
* @param {string} cssText
|
|
35
|
+
* @param {Record<string, string>} rewrites logical (no leading slash) or `/logical` → `assets/hash.ext`
|
|
36
|
+
*/
|
|
37
|
+
export function rewriteCssImports(cssText, rewrites) {
|
|
38
|
+
return cssText.replace(CSS_IMPORT_RE, (match, _dot, target) => {
|
|
39
|
+
const logical = String(target || '').replace(/^\.\//, '');
|
|
40
|
+
if (!logical)
|
|
41
|
+
return match;
|
|
42
|
+
const hashed = rewrites[logical] || rewrites[`/${logical}`] || rewrites[`assets/${logical}`];
|
|
43
|
+
if (!hashed)
|
|
44
|
+
return match;
|
|
45
|
+
const rel = hashed.startsWith('/') ? hashed.slice(1) : hashed;
|
|
46
|
+
const sibling = rel.startsWith('assets/') ? `./${path.basename(rel)}` : `./${rel}`;
|
|
47
|
+
return `@import"${sibling}"`;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Rewrite relative ESM so a file served from `/assets/<hash>.js` resolves against
|
|
52
|
+
* dist root (static `from "./x"` / `export * from "./x"` + dynamic `import("./"+…)`).
|
|
53
|
+
*
|
|
54
|
+
* Always use `../…` (Fix A). Do **not** prefer hashed siblings under `assets/`:
|
|
55
|
+
* barrels like `vmz-dom.js` (`export * from './dom-core.js'`) would then resolve
|
|
56
|
+
* as `/assets/dom-core.js` and 404. `rewrites` is accepted for API parity with CSS
|
|
57
|
+
* but intentionally ignored for JS path choice.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} jsText
|
|
60
|
+
* @param {Record<string, string>} [_rewrites]
|
|
61
|
+
*/
|
|
62
|
+
export function rewriteJsEntryRelativeImports(jsText, _rewrites = {}) {
|
|
63
|
+
let out = String(jsText || '');
|
|
64
|
+
// Dynamic: import("./" + id) → import("../" + id)
|
|
65
|
+
out = out.replace(/import\(\s*"\.\/"\s*\+/g, 'import("../"+');
|
|
66
|
+
out = out.replace(/import\(\s*'\.\/'\s*\+/g, "import('../'+");
|
|
67
|
+
const rewriteSpec = (spec) => {
|
|
68
|
+
const logical = String(spec || '').replace(/^\.\//, '');
|
|
69
|
+
if (!logical || logical.startsWith('../') || logical.startsWith('/'))
|
|
70
|
+
return spec;
|
|
71
|
+
return `../${logical}`;
|
|
72
|
+
};
|
|
73
|
+
// import/export … from "./x" | import("./x")
|
|
74
|
+
out = out.replace(/\b((?:import|export)\s+[^'"\n]*?\s+from\s+|import\s*\(\s*)(['"])(\.\/[^'"]+)\2/g, (match, prefix, quote, spec) => `${prefix}${quote}${rewriteSpec(spec)}${quote}`);
|
|
75
|
+
// side-effect: import "./x.js"
|
|
76
|
+
out = out.replace(/(^|[;\s])(import\s*)(['"])(\.\/[^'"]+)\3/gm, (match, lead, kw, quote, spec) => `${lead}${kw}${quote}${rewriteSpec(spec)}${quote}`);
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
23
79
|
/**
|
|
24
80
|
* Emit `assets/<sha256>.<ext>` copies and rewrite HTML href/src to hashed URLs.
|
|
25
81
|
* @param {string} distDir
|
|
@@ -37,36 +93,55 @@ export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
|
37
93
|
const objects = [];
|
|
38
94
|
/** @type {Record<string, string>} */
|
|
39
95
|
const rewrites = {};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
96
|
+
const ordered = orderCandidates(candidates);
|
|
97
|
+
for (const rel of ordered) {
|
|
98
|
+
// Aggregators are replaced in later passes; leaf JS must rewrite ./ → ../ so a
|
|
99
|
+
// hashed barrel under assets/ never 404s on second-hop relative re-exports.
|
|
100
|
+
if (CSS_AGGREGATORS.has(rel) || JS_ENTRY_AGGREGATORS.has(rel)) {
|
|
101
|
+
ingestCandidate(abs, rel, rewrites, objects, { transform: null });
|
|
44
102
|
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
103
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
104
|
+
if (/\.m?js$/i.test(rel)) {
|
|
105
|
+
const src = path.join(abs, rel);
|
|
106
|
+
if (!fs.existsSync(src))
|
|
107
|
+
continue;
|
|
108
|
+
const rewritten = rewriteJsEntryRelativeImports(fs.readFileSync(src, 'utf8'), {});
|
|
109
|
+
ingestCandidate(abs, rel, rewrites, objects, {
|
|
110
|
+
transform: () => Buffer.from(rewritten, 'utf8'),
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
60
113
|
}
|
|
61
|
-
objects
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
114
|
+
ingestCandidate(abs, rel, rewrites, objects, { transform: null });
|
|
115
|
+
}
|
|
116
|
+
// Aggregator CSS (vmz.css) must import hashed leaf files — rewrite then hash.
|
|
117
|
+
for (const rel of ordered) {
|
|
118
|
+
if (!CSS_AGGREGATORS.has(rel))
|
|
119
|
+
continue;
|
|
120
|
+
const src = path.join(abs, rel);
|
|
121
|
+
if (!fs.existsSync(src))
|
|
122
|
+
continue;
|
|
123
|
+
const rewritten = rewriteCssImports(fs.readFileSync(src, 'utf8'), rewrites);
|
|
124
|
+
removeLogicalObject(objects, rel);
|
|
125
|
+
delete rewrites[`/${rel}`];
|
|
126
|
+
delete rewrites[rel];
|
|
127
|
+
ingestCandidate(abs, rel, rewrites, objects, {
|
|
128
|
+
transform: () => Buffer.from(rewritten, 'utf8'),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// JS entry shells: rewrite relative ESM (static + dynamic) then re-hash.
|
|
132
|
+
for (const rel of ordered) {
|
|
133
|
+
if (!JS_ENTRY_AGGREGATORS.has(rel))
|
|
134
|
+
continue;
|
|
135
|
+
const src = path.join(abs, rel);
|
|
136
|
+
if (!fs.existsSync(src))
|
|
137
|
+
continue;
|
|
138
|
+
const rewritten = rewriteJsEntryRelativeImports(fs.readFileSync(src, 'utf8'), rewrites);
|
|
139
|
+
removeLogicalObject(objects, rel);
|
|
140
|
+
delete rewrites[`/${rel}`];
|
|
141
|
+
delete rewrites[rel];
|
|
142
|
+
ingestCandidate(abs, rel, rewrites, objects, {
|
|
143
|
+
transform: () => Buffer.from(rewritten, 'utf8'),
|
|
67
144
|
});
|
|
68
|
-
rewrites[`/${logical}`] = `/${assetRel}`;
|
|
69
|
-
rewrites[logical] = assetRel;
|
|
70
145
|
}
|
|
71
146
|
objects.sort((a, b) => (a.logicalPath < b.logicalPath ? -1 : a.logicalPath > b.logicalPath ? 1 : 0));
|
|
72
147
|
let rewrittenHtml = 0;
|
|
@@ -85,9 +160,83 @@ export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
|
85
160
|
const vmzDir = path.join(abs, '_vmz');
|
|
86
161
|
fs.mkdirSync(vmzDir, { recursive: true });
|
|
87
162
|
const outPath = path.join(vmzDir, 'content-addressed-assets.json');
|
|
88
|
-
|
|
163
|
+
writePrettyJsonFile(outPath, manifest);
|
|
89
164
|
return { manifest, assetsDir, rewrites, manifestPath: outPath };
|
|
90
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* @param {string[]} candidates
|
|
168
|
+
*/
|
|
169
|
+
function orderCandidates(candidates) {
|
|
170
|
+
const set = new Set(candidates.map((c) => String(c).replace(/\\/g, '/').replace(/^\//, '')));
|
|
171
|
+
/** @type {string[]} */
|
|
172
|
+
const out = [];
|
|
173
|
+
for (const name of DEFAULT_CANDIDATES) {
|
|
174
|
+
if (set.has(name) && !CSS_AGGREGATORS.has(name) && !JS_ENTRY_AGGREGATORS.has(name))
|
|
175
|
+
out.push(name);
|
|
176
|
+
}
|
|
177
|
+
for (const name of [...set].sort()) {
|
|
178
|
+
if (!CSS_AGGREGATORS.has(name) && !JS_ENTRY_AGGREGATORS.has(name) && !out.includes(name)) {
|
|
179
|
+
out.push(name);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (set.has('vmz.css'))
|
|
183
|
+
out.push('vmz.css');
|
|
184
|
+
for (const name of ['entry-client.js', 'entry-event.js']) {
|
|
185
|
+
if (set.has(name))
|
|
186
|
+
out.push(name);
|
|
187
|
+
}
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* @param {Array<Record<string, any>>} objects
|
|
192
|
+
* @param {string} logical
|
|
193
|
+
*/
|
|
194
|
+
function removeLogicalObject(objects, logical) {
|
|
195
|
+
const idx = objects.findIndex((o) => o.logicalPath === logical);
|
|
196
|
+
if (idx >= 0)
|
|
197
|
+
objects.splice(idx, 1);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* @param {string} absDist
|
|
201
|
+
* @param {string} rel
|
|
202
|
+
* @param {Record<string, string>} rewrites
|
|
203
|
+
* @param {Array<Record<string, any>>} objects
|
|
204
|
+
* @param {{ transform?: ((buf: Buffer) => Buffer) | null }} opts
|
|
205
|
+
*/
|
|
206
|
+
function ingestCandidate(absDist, rel, rewrites, objects, opts) {
|
|
207
|
+
const logical = String(rel).replace(/\\/g, '/').replace(/^\//, '');
|
|
208
|
+
const src = path.join(absDist, ...logical.split('/'));
|
|
209
|
+
if (!fs.existsSync(src) || !fs.statSync(src).isFile())
|
|
210
|
+
return;
|
|
211
|
+
let buf = fs.readFileSync(src);
|
|
212
|
+
if (typeof opts.transform === 'function') {
|
|
213
|
+
buf = opts.transform(buf);
|
|
214
|
+
}
|
|
215
|
+
const digest = sha256Hex(buf);
|
|
216
|
+
const ext = path.extname(logical) || '';
|
|
217
|
+
const assetRel = `assets/${digest}${ext}`;
|
|
218
|
+
const dest = path.join(absDist, ...assetRel.split('/'));
|
|
219
|
+
if (!fs.existsSync(dest)) {
|
|
220
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
221
|
+
fs.writeFileSync(dest, buf);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const existing = sha256Hex(fs.readFileSync(dest));
|
|
225
|
+
if (existing !== digest) {
|
|
226
|
+
// Stale assets/ from a prior partial build can reuse hash filenames with different bytes.
|
|
227
|
+
fs.writeFileSync(dest, buf);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
objects.push({
|
|
231
|
+
logicalPath: logical,
|
|
232
|
+
assetPath: assetRel,
|
|
233
|
+
digest,
|
|
234
|
+
bytes: buf.length,
|
|
235
|
+
immutable: true,
|
|
236
|
+
});
|
|
237
|
+
rewrites[`/${logical}`] = `/${assetRel}`;
|
|
238
|
+
rewrites[logical] = assetRel;
|
|
239
|
+
}
|
|
91
240
|
/**
|
|
92
241
|
* Resolve an immutable object by digest under dist/assets (cross-source reuse).
|
|
93
242
|
* @param {string} distDir
|
|
@@ -123,7 +272,6 @@ export function assertSharedAssetPath(distDir, a, b, ext = '.js') {
|
|
|
123
272
|
const rel = `assets/${da}${ext}`;
|
|
124
273
|
const dest = path.join(distDir, ...rel.split('/'));
|
|
125
274
|
fs.writeFileSync(dest, typeof a === 'string' ? Buffer.from(a) : a);
|
|
126
|
-
// Second write of identical bytes must be reuse, not fork.
|
|
127
275
|
fs.writeFileSync(dest, typeof b === 'string' ? Buffer.from(b) : b);
|
|
128
276
|
const again = resolveAssetByDigest(distDir, da, ext);
|
|
129
277
|
if (!again || again.assetPath !== rel) {
|
|
@@ -138,7 +286,6 @@ function collectCandidates(distDir) {
|
|
|
138
286
|
if (fs.existsSync(path.join(distDir, name)))
|
|
139
287
|
out.push(name);
|
|
140
288
|
}
|
|
141
|
-
// Include top-level *.client.js and pages/**/*.client.js referenced by resume.
|
|
142
289
|
walk(distDir, distDir, (rel) => {
|
|
143
290
|
if (/\.client\.js$/i.test(rel))
|
|
144
291
|
out.push(rel);
|
|
@@ -172,7 +319,6 @@ function rewriteHtmlReferences(distDir, rewrites) {
|
|
|
172
319
|
let text = fs.readFileSync(file, 'utf8');
|
|
173
320
|
let next = text;
|
|
174
321
|
for (const [from, to] of pairs) {
|
|
175
|
-
// href="/x" src="/x" and unquoted variants in attributes
|
|
176
322
|
next = next.split(from).join(to);
|
|
177
323
|
}
|
|
178
324
|
if (next !== text) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const DELIVERY_PROFILE_AUTHORING_SCHEMA = "vmz.delivery.authoring.v0";
|
|
6
6
|
export declare const BUILD_PROFILE_SELECTION_SCHEMA = "vmz.build.profile_selection.v0";
|
|
7
|
-
/** Browser-era assembly kinds (04 B5). */
|
|
7
|
+
/** Browser-era assembly kinds (04 B5). `static-cdn` was renamed to `web-static`. */
|
|
8
8
|
export declare const ASSEMBLIES: readonly string[];
|
|
9
9
|
export declare const SERVER_RUNTIMES: readonly string[];
|
|
10
10
|
/** Official built-in aliases when not overridden in config. */
|
|
@@ -13,7 +13,7 @@ export declare const BUILTIN_PROFILES: Readonly<{
|
|
|
13
13
|
host: string;
|
|
14
14
|
assembly: string;
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
static: {
|
|
17
17
|
host: string;
|
|
18
18
|
assembly: string;
|
|
19
19
|
};
|
|
@@ -28,10 +28,36 @@ export declare const BUILTIN_PROFILES: Readonly<{
|
|
|
28
28
|
serverRuntime: string;
|
|
29
29
|
};
|
|
30
30
|
}>;
|
|
31
|
+
/**
|
|
32
|
+
* Profile artifact directory name under CLI `--out-dir` (default = profile id).
|
|
33
|
+
* @param {string} id
|
|
34
|
+
* @param {unknown} rawName
|
|
35
|
+
* @param {Array<{ code: string, message: string }>} diagnostics
|
|
36
|
+
* @returns {string | null}
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeProfileArtifactName(id: any, rawName: any, diagnostics: any): string;
|
|
39
|
+
/**
|
|
40
|
+
* Workspace `--out-dir` + profile `name` → artifact root.
|
|
41
|
+
* Always nests: `path.join(outDir, name)` where `name` defaults to profile id
|
|
42
|
+
* (`name: 'cdn'` → `dist/cdn`; omit → `dist/static` for profile `static`).
|
|
43
|
+
* @param {string} outDir
|
|
44
|
+
* @param {{ name?: string, id?: string } | null | undefined} profile
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveProfileArtifactDir(outDir: any, profile: any): any;
|
|
31
47
|
export declare function pickSiteAuthoring(raw: any): {
|
|
32
48
|
artifact: string;
|
|
33
49
|
sources: any;
|
|
34
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* `delivery.packaging.wechat` — vendor identity, not WeChat JSON / wx APIs.
|
|
53
|
+
* @param {unknown} raw
|
|
54
|
+
* @param {Array<{ code: string, message: string }>} diagnostics
|
|
55
|
+
*/
|
|
56
|
+
export declare function pickDeliveryPackaging(raw: any, diagnostics: any): {
|
|
57
|
+
wechat?: undefined;
|
|
58
|
+
} | {
|
|
59
|
+
wechat: {};
|
|
60
|
+
};
|
|
35
61
|
export declare function normalizeDeliveryAuthoring(raw: any): {
|
|
36
62
|
ok: boolean;
|
|
37
63
|
table: {
|
|
@@ -59,6 +85,8 @@ export declare function selectBuildProfile(table: any, cliProfile?: string): {
|
|
|
59
85
|
selection: {
|
|
60
86
|
schema: string;
|
|
61
87
|
profileId: any;
|
|
88
|
+
name: any;
|
|
89
|
+
nameExplicit: boolean;
|
|
62
90
|
host: any;
|
|
63
91
|
assembly: any;
|
|
64
92
|
serverRuntime: any;
|
package/dist/delivery-profile.js
CHANGED
|
@@ -4,18 +4,67 @@
|
|
|
4
4
|
*/
|
|
5
5
|
// @ts-nocheck
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
|
+
import path from 'node:path';
|
|
7
8
|
export const DELIVERY_PROFILE_AUTHORING_SCHEMA = 'vmz.delivery.authoring.v0';
|
|
8
9
|
export const BUILD_PROFILE_SELECTION_SCHEMA = 'vmz.build.profile_selection.v0';
|
|
9
|
-
/** Browser-era assembly kinds (04 B5). */
|
|
10
|
-
export const ASSEMBLIES = Object.freeze(['local-static', 'static
|
|
10
|
+
/** Browser-era assembly kinds (04 B5). `static-cdn` was renamed to `web-static`. */
|
|
11
|
+
export const ASSEMBLIES = Object.freeze(['local-static', 'web-static', 'server-host', 'cdn+server', 'rust-embedded']);
|
|
11
12
|
export const SERVER_RUNTIMES = Object.freeze(['node', 'worker', 'deno', 'bun', 'rust-host']);
|
|
12
13
|
/** Official built-in aliases when not overridden in config. */
|
|
13
14
|
export const BUILTIN_PROFILES = Object.freeze({
|
|
14
15
|
'web-client': { host: 'browser', assembly: 'local-static' },
|
|
15
|
-
|
|
16
|
+
static: { host: 'browser', assembly: 'web-static' },
|
|
16
17
|
'web-ssr': { host: 'browser', assembly: 'server-host', serverRuntime: 'node' },
|
|
17
18
|
'web-hybrid': { host: 'browser', assembly: 'cdn+server', serverRuntime: 'node' },
|
|
18
19
|
});
|
|
20
|
+
/**
|
|
21
|
+
* Profile artifact directory name under CLI `--out-dir` (default = profile id).
|
|
22
|
+
* @param {string} id
|
|
23
|
+
* @param {unknown} rawName
|
|
24
|
+
* @param {Array<{ code: string, message: string }>} diagnostics
|
|
25
|
+
* @returns {string | null}
|
|
26
|
+
*/
|
|
27
|
+
export function normalizeProfileArtifactName(id, rawName, diagnostics) {
|
|
28
|
+
const fallback = String(id || '').trim();
|
|
29
|
+
if (rawName == null || rawName === '')
|
|
30
|
+
return fallback || null;
|
|
31
|
+
if (typeof rawName !== 'string') {
|
|
32
|
+
diagnostics.push({
|
|
33
|
+
code: 'delivery.profile.name',
|
|
34
|
+
message: `profiles.${id}.name must be a string (got ${typeof rawName})`,
|
|
35
|
+
});
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const name = rawName.trim();
|
|
39
|
+
if (!name) {
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
code: 'delivery.profile.name',
|
|
42
|
+
message: `profiles.${id}.name must be a non-empty string`,
|
|
43
|
+
});
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (name === '.' || name === '..' || name.includes('/') || name.includes('\\') || name.includes('\0')) {
|
|
47
|
+
diagnostics.push({
|
|
48
|
+
code: 'delivery.profile.name',
|
|
49
|
+
message: `profiles.${id}.name must be a single path segment under --out-dir (got '${name}')`,
|
|
50
|
+
});
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return name;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Workspace `--out-dir` + profile `name` → artifact root.
|
|
57
|
+
* Always nests: `path.join(outDir, name)` where `name` defaults to profile id
|
|
58
|
+
* (`name: 'cdn'` → `dist/cdn`; omit → `dist/static` for profile `static`).
|
|
59
|
+
* @param {string} outDir
|
|
60
|
+
* @param {{ name?: string, id?: string } | null | undefined} profile
|
|
61
|
+
*/
|
|
62
|
+
export function resolveProfileArtifactDir(outDir, profile) {
|
|
63
|
+
const name = String(profile?.name || profile?.id || '').trim();
|
|
64
|
+
if (!name)
|
|
65
|
+
return outDir;
|
|
66
|
+
return path.join(outDir, name);
|
|
67
|
+
}
|
|
19
68
|
function isPlainObject(v) {
|
|
20
69
|
return v != null && typeof v === 'object' && !Array.isArray(v);
|
|
21
70
|
}
|
|
@@ -49,6 +98,67 @@ export function pickSiteAuthoring(raw) {
|
|
|
49
98
|
}
|
|
50
99
|
return site;
|
|
51
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* `delivery.packaging.wechat` — vendor identity, not WeChat JSON / wx APIs.
|
|
103
|
+
* @param {unknown} raw
|
|
104
|
+
* @param {Array<{ code: string, message: string }>} diagnostics
|
|
105
|
+
*/
|
|
106
|
+
export function pickDeliveryPackaging(raw, diagnostics) {
|
|
107
|
+
if (!isPlainObject(raw) || raw.packaging == null)
|
|
108
|
+
return null;
|
|
109
|
+
if (!isPlainObject(raw.packaging)) {
|
|
110
|
+
diagnostics.push({ code: 'delivery.packaging', message: 'delivery.packaging must be an object' });
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
for (const key of Object.keys(raw.packaging)) {
|
|
114
|
+
if (key !== 'wechat') {
|
|
115
|
+
diagnostics.push({
|
|
116
|
+
code: 'delivery.packaging.vendor',
|
|
117
|
+
message: `delivery.packaging.${key} is not a known vendor (wechat)`,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const wechat = raw.packaging.wechat;
|
|
122
|
+
if (wechat == null)
|
|
123
|
+
return {};
|
|
124
|
+
if (!isPlainObject(wechat)) {
|
|
125
|
+
diagnostics.push({
|
|
126
|
+
code: 'delivery.packaging.wechat',
|
|
127
|
+
message: 'delivery.packaging.wechat must be an object',
|
|
128
|
+
});
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
for (const [k, v] of Object.entries(wechat)) {
|
|
132
|
+
if (typeof v === 'function') {
|
|
133
|
+
diagnostics.push({
|
|
134
|
+
code: 'delivery.packaging.executable',
|
|
135
|
+
message: `delivery.packaging.wechat.${k} must be pure data (no functions)`,
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (k !== 'appId' && k !== 'projectName' && k !== 'title') {
|
|
140
|
+
diagnostics.push({
|
|
141
|
+
code: 'delivery.packaging.wechat.field',
|
|
142
|
+
message: `delivery.packaging.wechat.${k} is not a known field (appId|projectName|title)`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (v != null && typeof v !== 'string') {
|
|
146
|
+
diagnostics.push({
|
|
147
|
+
code: 'delivery.packaging.wechat.type',
|
|
148
|
+
message: `delivery.packaging.wechat.${k} must be a string`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const out = {};
|
|
153
|
+
if (typeof wechat.appId === 'string' && wechat.appId.trim())
|
|
154
|
+
out.appId = wechat.appId.trim();
|
|
155
|
+
if (typeof wechat.projectName === 'string' && wechat.projectName.trim()) {
|
|
156
|
+
out.projectName = wechat.projectName.trim();
|
|
157
|
+
}
|
|
158
|
+
if (typeof wechat.title === 'string' && wechat.title.trim())
|
|
159
|
+
out.title = wechat.title.trim();
|
|
160
|
+
return { wechat: out };
|
|
161
|
+
}
|
|
52
162
|
function normalizeProfileEntry(entry, id, diagnostics) {
|
|
53
163
|
if (!isPlainObject(entry)) {
|
|
54
164
|
diagnostics.push({ code: 'delivery.profile.invalid', message: `profiles.${id} must be an object` });
|
|
@@ -61,7 +171,14 @@ function normalizeProfileEntry(entry, id, diagnostics) {
|
|
|
61
171
|
message: `profiles.${id}.host: only 'browser' is supported before Browser Production (got ${host})`,
|
|
62
172
|
});
|
|
63
173
|
}
|
|
64
|
-
|
|
174
|
+
let assembly = String(entry.assembly || '').trim();
|
|
175
|
+
if (assembly === 'static-cdn') {
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
code: 'delivery.profile.assembly.renamed',
|
|
178
|
+
message: `profiles.${id}.assembly 'static-cdn' was renamed to 'web-static'`,
|
|
179
|
+
});
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
65
182
|
if (!ASSEMBLIES.includes(assembly)) {
|
|
66
183
|
diagnostics.push({
|
|
67
184
|
code: 'delivery.profile.assembly',
|
|
@@ -69,6 +186,10 @@ function normalizeProfileEntry(entry, id, diagnostics) {
|
|
|
69
186
|
});
|
|
70
187
|
return null;
|
|
71
188
|
}
|
|
189
|
+
const nameExplicit = entry.name != null && String(entry.name).trim() !== '';
|
|
190
|
+
const name = normalizeProfileArtifactName(id, nameExplicit ? entry.name : id, diagnostics);
|
|
191
|
+
if (!name)
|
|
192
|
+
return null;
|
|
72
193
|
let serverRuntime = null;
|
|
73
194
|
if (assembly === 'server-host' || assembly === 'cdn+server') {
|
|
74
195
|
serverRuntime = String(entry.serverRuntime || 'node');
|
|
@@ -110,6 +231,9 @@ function normalizeProfileEntry(entry, id, diagnostics) {
|
|
|
110
231
|
}
|
|
111
232
|
return {
|
|
112
233
|
id,
|
|
234
|
+
name,
|
|
235
|
+
/** True when author set `profiles.<id>.name`; false → `name` defaulted to profile id. */
|
|
236
|
+
nameExplicit,
|
|
113
237
|
host: 'browser',
|
|
114
238
|
assembly,
|
|
115
239
|
serverRuntime,
|
|
@@ -183,6 +307,10 @@ export function normalizeDeliveryAuthoring(raw) {
|
|
|
183
307
|
},
|
|
184
308
|
};
|
|
185
309
|
}
|
|
310
|
+
else if (isPlainObject(raw.packaging)) {
|
|
311
|
+
defaultId = String(raw.default || 'web-ssr').trim() || 'web-ssr';
|
|
312
|
+
profileInputs = { ...BUILTIN_PROFILES };
|
|
313
|
+
}
|
|
186
314
|
else {
|
|
187
315
|
return {
|
|
188
316
|
ok: false,
|
|
@@ -206,6 +334,9 @@ export function normalizeDeliveryAuthoring(raw) {
|
|
|
206
334
|
message: `delivery.default '${defaultId}' is not a known profile`,
|
|
207
335
|
});
|
|
208
336
|
}
|
|
337
|
+
if (diagnostics.length)
|
|
338
|
+
return { ok: false, diagnostics };
|
|
339
|
+
const packaging = pickDeliveryPackaging(raw, diagnostics);
|
|
209
340
|
if (diagnostics.length)
|
|
210
341
|
return { ok: false, diagnostics };
|
|
211
342
|
const table = {
|
|
@@ -213,6 +344,7 @@ export function normalizeDeliveryAuthoring(raw) {
|
|
|
213
344
|
default: defaultId,
|
|
214
345
|
profiles,
|
|
215
346
|
sugar,
|
|
347
|
+
...(packaging ? { packaging } : {}),
|
|
216
348
|
};
|
|
217
349
|
table.digest = sha256Hex(canonicalJson(table));
|
|
218
350
|
return { ok: true, table };
|
|
@@ -234,6 +366,8 @@ export function selectBuildProfile(table, cliProfile = '') {
|
|
|
234
366
|
const selection = {
|
|
235
367
|
schema: BUILD_PROFILE_SELECTION_SCHEMA,
|
|
236
368
|
profileId: id,
|
|
369
|
+
name: profile.name,
|
|
370
|
+
nameExplicit: Boolean(profile.nameExplicit),
|
|
237
371
|
host: profile.host,
|
|
238
372
|
assembly: profile.assembly,
|
|
239
373
|
serverRuntime: profile.serverRuntime,
|
|
@@ -246,7 +380,7 @@ export function selectBuildProfile(table, cliProfile = '') {
|
|
|
246
380
|
}
|
|
247
381
|
export function semanticIdsForAssembly(assembly) {
|
|
248
382
|
switch (assembly) {
|
|
249
|
-
case 'static
|
|
383
|
+
case 'web-static':
|
|
250
384
|
return ['static-delivery', 'asset-graph'];
|
|
251
385
|
case 'server-host':
|
|
252
386
|
return ['server-host', 'asset-graph'];
|
package/dist/dev-session.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @property {string} [host]
|
|
17
17
|
* @property {number} [port]
|
|
18
18
|
* @property {number} [pollMs]
|
|
19
|
+
* @property {'browser' | 'mini-program-wechat'} [target]
|
|
19
20
|
* @property {AbortSignal} [signal]
|
|
20
21
|
* @property {typeof createWorkspace} [createWorkspaceFn]
|
|
21
22
|
* @property {(opts: { project: string, outDir: string, host: string, port: number }) => import('node:child_process').ChildProcess} [spawnHostFn]
|