@vmz/vmz 0.1.12 → 0.1.13
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 +3 -4
- package/dist/cdn-policy.js +4 -5
- package/dist/cli.js +15 -50
- package/dist/content-addressed-assets.d.ts +2 -8
- package/dist/content-addressed-assets.js +34 -111
- package/dist/delivery-profile.d.ts +1 -11
- package/dist/delivery-profile.js +1 -70
- package/dist/dev-session.d.ts +0 -1
- package/dist/dev-session.js +65 -268
- package/dist/document-build.d.ts +1 -2
- package/dist/document-build.js +82 -104
- package/dist/document-cmd.js +9 -2
- package/dist/document-enrich.d.ts +1 -2
- package/dist/document-enrich.js +4 -15
- package/dist/document-integrate.js +21 -29
- package/dist/embedded-packaging.js +1 -2
- package/dist/index.d.ts +1 -15
- package/dist/index.js +3 -59
- package/dist/locale-check.js +61 -28
- package/dist/locale-cmd.js +42 -10
- package/dist/locale-route-emit.d.ts +1 -4
- package/dist/locale-route-emit.js +32 -7
- package/dist/locale-router.js +8 -26
- package/dist/pack.js +2 -3
- package/dist/production-observability.js +2 -3
- package/dist/production-test-pack.js +3 -4
- package/dist/release-pack.js +18 -5
- package/dist/server-artifact.js +3 -4
- package/dist/site-delivery.js +3 -4
- package/dist/static-emit.js +97 -118
- package/dist/test-cmd.js +1 -2
- package/package.json +12 -12
- package/dist/dev-watch-roots.d.ts +0 -80
- package/dist/dev-watch-roots.js +0 -245
- package/dist/document-layout-render.d.ts +0 -20
- package/dist/document-layout-render.js +0 -87
- package/dist/document-routing-config.d.ts +0 -13
- package/dist/document-routing-config.js +0 -40
- package/dist/native-addon.d.ts +0 -9
- package/dist/native-addon.js +0 -84
- package/dist/pretty-json.d.ts +0 -19
- package/dist/pretty-json.js +0 -43
- package/dist/route-path.d.ts +0 -35
- package/dist/route-path.js +0 -77
- package/dist/wechat-packaging.d.ts +0 -22
- package/dist/wechat-packaging.js +0 -59
package/dist/build-assemble.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
* B5 Assemble dispatch + B6 build-proof (per-build semantic id slots).
|
|
3
3
|
*/
|
|
4
4
|
// @ts-nocheck
|
|
5
|
-
import { mkdirSync } from 'node:fs';
|
|
5
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { semanticIdsForAssembly, sha256Hex, canonicalJson } from './delivery-profile.js';
|
|
8
8
|
import { emitServerArtifact } from './server-artifact.js';
|
|
9
9
|
import { emitEmbeddedPackaging } from './embedded-packaging.js';
|
|
10
10
|
import { emitSiteDelivery } from './site-delivery.js';
|
|
11
11
|
import { emitWebStatic } from './static-emit.js';
|
|
12
|
-
import { writePrettyJsonFile } from './pretty-json.js';
|
|
13
12
|
export const BUILD_PROOF_SCHEMA = 'vmz.build.proof.v0';
|
|
14
13
|
export const ASSEMBLE_MANIFEST_SCHEMA = 'vmz.assemble.manifest.v0';
|
|
15
14
|
/**
|
|
@@ -107,7 +106,7 @@ export async function assembleDelivery(outDir, ctx) {
|
|
|
107
106
|
const vmzDir = path.join(outDir, '_vmz');
|
|
108
107
|
mkdirSync(vmzDir, { recursive: true });
|
|
109
108
|
const file = path.join(vmzDir, 'assemble-manifest.json');
|
|
110
|
-
|
|
109
|
+
writeFileSync(file, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
|
|
111
110
|
return { manifest: result, path: file };
|
|
112
111
|
}
|
|
113
112
|
/**
|
|
@@ -187,6 +186,6 @@ export function emitBuildProof(outDir, ctx) {
|
|
|
187
186
|
const vmzDir = path.join(outDir, '_vmz');
|
|
188
187
|
mkdirSync(vmzDir, { recursive: true });
|
|
189
188
|
const file = path.join(vmzDir, 'build-proof.json');
|
|
190
|
-
|
|
189
|
+
writeFileSync(file, `${JSON.stringify(body, null, 2)}\n`, 'utf8');
|
|
191
190
|
return { proof: body, path: file };
|
|
192
191
|
}
|
package/dist/cdn-policy.js
CHANGED
|
@@ -8,7 +8,6 @@ import fs from 'node:fs';
|
|
|
8
8
|
import http from 'node:http';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { assertLocaleCacheKey, localeAwareCacheKey } from './locale-router.js';
|
|
11
|
-
import { writePrettyJsonFile } from './pretty-json.js';
|
|
12
11
|
export const CDN_POLICY_MANIFEST_SCHEMA = 'vmz.cdn.policy_manifest.v0';
|
|
13
12
|
export const CDN_ADAPTER_PROJECTION_SCHEMA = 'vmz.cdn.adapter_projection.v0';
|
|
14
13
|
/** HTML: revalidate. Hashed/static assets: long immutable. */
|
|
@@ -86,7 +85,7 @@ export function buildCdnPolicyManifest(staticManifest, opts = {}) {
|
|
|
86
85
|
const body = {
|
|
87
86
|
schema: CDN_POLICY_MANIFEST_SCHEMA,
|
|
88
87
|
applicationId: staticManifest.applicationId || null,
|
|
89
|
-
deliveryProfile: 'static',
|
|
88
|
+
deliveryProfile: 'web-static',
|
|
90
89
|
origin,
|
|
91
90
|
spaFallback: false,
|
|
92
91
|
staticManifestDigest: staticManifest.manifestDigest || null,
|
|
@@ -146,14 +145,14 @@ export function emitCdnPolicy(distDir, staticManifest, opts = {}) {
|
|
|
146
145
|
const policy = buildCdnPolicyManifest(staticManifest, { ...opts, localeArtifact });
|
|
147
146
|
const vmzDir = path.join(distDir, '_vmz');
|
|
148
147
|
fs.mkdirSync(vmzDir, { recursive: true });
|
|
149
|
-
|
|
148
|
+
fs.writeFileSync(path.join(vmzDir, 'cdn-policy-manifest.json'), `${JSON.stringify(policy, null, 2)}\n`, 'utf8');
|
|
150
149
|
const local = projectCdnAdapter(policy, 'local-static');
|
|
151
150
|
const netlify = projectCdnAdapter(policy, 'netlify');
|
|
152
151
|
const adaptersDir = path.join(vmzDir, 'adapters');
|
|
153
152
|
fs.mkdirSync(path.join(adaptersDir, 'local-static'), { recursive: true });
|
|
154
153
|
fs.mkdirSync(path.join(adaptersDir, 'netlify'), { recursive: true });
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
fs.writeFileSync(path.join(adaptersDir, 'local-static', 'projection.json'), `${JSON.stringify(local, null, 2)}\n`, 'utf8');
|
|
155
|
+
fs.writeFileSync(path.join(adaptersDir, 'netlify', 'projection.json'), `${JSON.stringify(netlify, null, 2)}\n`, 'utf8');
|
|
157
156
|
fs.writeFileSync(path.join(adaptersDir, 'netlify', '_headers'), String(netlify.files['_headers'] || ''), 'utf8');
|
|
158
157
|
fs.writeFileSync(path.join(adaptersDir, 'netlify', '_redirects'), String(netlify.files['_redirects'] || ''), 'utf8');
|
|
159
158
|
return { policy, adapters: { 'local-static': local, netlify } };
|
package/dist/cli.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Node CLI command implementations .
|
|
4
4
|
*/
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
|
-
import { existsSync } from 'node:fs';
|
|
6
|
+
import { copyFileSync, existsSync } from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
-
import { HOST_PROTOCOL, createWorkspace, getProtocolVersions,
|
|
8
|
+
import { HOST_PROTOCOL, createWorkspace, getProtocolVersions, resolveCoreRuntimeDist } from './index.js';
|
|
9
9
|
import { createDevSession } from './dev-session.js';
|
|
10
10
|
import { gateGlobalProjectCommand, getInvocationContext, isGlobalAllowedCommand } from './invocation.js';
|
|
11
11
|
import { log } from './log.js';
|
|
@@ -104,10 +104,10 @@ export function printProjectHelp() {
|
|
|
104
104
|
Usage:
|
|
105
105
|
vmz new|init <dir> Scaffold a minimal app (native CLI)
|
|
106
106
|
vmz check [path] Check project via Workspace
|
|
107
|
-
vmz build [path] [options] Build project via Workspace
|
|
107
|
+
vmz build [path] [options] Build project via Workspace
|
|
108
108
|
vmz serve [path] [options] Serve dist (optional --build)
|
|
109
|
-
vmz dev [path] [options]
|
|
110
|
-
vmz format [path] [--check] Format .vmz via N-API (oxc
|
|
109
|
+
vmz dev [path] [options] Long-lived rebuild session (no CLI spawn)
|
|
110
|
+
vmz format [path] [--check] Format .vmz via N-API (oxc codegen)
|
|
111
111
|
vmz lint [path] [--deny-warnings] Lint (= check) via N-API
|
|
112
112
|
vmz test [path] [options] Native test discover / report
|
|
113
113
|
vmz document|docs <cmd> Project /documents domain
|
|
@@ -123,8 +123,7 @@ Usage:
|
|
|
123
123
|
Options:
|
|
124
124
|
--out-dir, -o <dir> Output directory (default: dist)
|
|
125
125
|
--release Release build (omit serve-host; pack minify slot; proof)
|
|
126
|
-
--profile <name> Delivery profile (default from config; builtins: web-ssr|static|web-client|web-hybrid)
|
|
127
|
-
--target <id> browser (default) | mini-program-wechat (pack dist/wechat for WeChat DevTools; build+dev)
|
|
126
|
+
--profile <name> Delivery profile (default from config; builtins: web-ssr|web-static|web-client|web-hybrid)
|
|
128
127
|
--origin <url> Site origin for static-cdn canonical/sitemap
|
|
129
128
|
--host <host> Listen host (default: 127.0.0.1)
|
|
130
129
|
--port <port> Listen port (dev: omit = auto from 5173; set = lock)
|
|
@@ -333,17 +332,11 @@ async function runWithPlugins(ws, project, outDir, fn) {
|
|
|
333
332
|
*/
|
|
334
333
|
async function cmdBuild(args) {
|
|
335
334
|
const pathArg = args._[0] ?? '.';
|
|
336
|
-
const targetRaw = typeof args.target === 'string' ? args.target : 'browser';
|
|
337
|
-
if (targetRaw !== 'browser' && targetRaw !== 'mini-program-wechat') {
|
|
338
|
-
log.error(`unknown --target ${targetRaw} (browser | mini-program-wechat)`);
|
|
339
|
-
return 1;
|
|
340
|
-
}
|
|
341
|
-
const wechatPack = targetRaw === 'mini-program-wechat';
|
|
342
335
|
const { project, outDir } = resolveWorkspaceDirs({
|
|
343
336
|
path: pathArg,
|
|
344
337
|
outDir: typeof args['out-dir'] === 'string' ? args['out-dir'] : undefined,
|
|
345
338
|
});
|
|
346
|
-
log.info(`build ${project} → ${outDir}
|
|
339
|
+
log.info(`build ${project} → ${outDir}`);
|
|
347
340
|
const ws = createWorkspace({ root: project, outDir });
|
|
348
341
|
try {
|
|
349
342
|
const cfg = await loadVmzConfig(project);
|
|
@@ -401,29 +394,6 @@ async function cmdBuild(args) {
|
|
|
401
394
|
if (!docs.ok)
|
|
402
395
|
return 1;
|
|
403
396
|
}
|
|
404
|
-
if (wechatPack) {
|
|
405
|
-
if (typeof ws.lowerMiniprogramWechatPackaging !== 'function') {
|
|
406
|
-
log.error('wechat pack: workspace missing lowerMiniprogramWechatPackaging');
|
|
407
|
-
return 1;
|
|
408
|
-
}
|
|
409
|
-
let report;
|
|
410
|
-
try {
|
|
411
|
-
const raw = ws.lowerMiniprogramWechatPackaging();
|
|
412
|
-
report = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
413
|
-
}
|
|
414
|
-
catch (err) {
|
|
415
|
-
log.error(`wechat pack failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
416
|
-
return 1;
|
|
417
|
-
}
|
|
418
|
-
log.diagnostics(report.diagnostics ?? []);
|
|
419
|
-
if (report.status !== 'ready') {
|
|
420
|
-
log.error(`wechat pack ${report.status || 'failed'}`);
|
|
421
|
-
return 1;
|
|
422
|
-
}
|
|
423
|
-
const packRoot = report.packRoot || 'dist/wechat';
|
|
424
|
-
log.info(`wechat pack ok → ${path.join(project, packRoot)} (open in WeChat DevTools)`);
|
|
425
|
-
return 0;
|
|
426
|
-
}
|
|
427
397
|
let pack = null;
|
|
428
398
|
try {
|
|
429
399
|
pack = packFromDeploymentIr(outDir, {
|
|
@@ -450,7 +420,7 @@ async function cmdBuild(args) {
|
|
|
450
420
|
let assemble = null;
|
|
451
421
|
try {
|
|
452
422
|
if (selected.selection.assembly === 'static-cdn') {
|
|
453
|
-
log.info(`static emit ${outDir}`);
|
|
423
|
+
log.info(`web-static emit ${outDir}`);
|
|
454
424
|
}
|
|
455
425
|
assemble = await assembleDelivery(outDir, {
|
|
456
426
|
selection: selected.selection,
|
|
@@ -464,7 +434,7 @@ async function cmdBuild(args) {
|
|
|
464
434
|
});
|
|
465
435
|
for (const step of assemble.manifest.steps || []) {
|
|
466
436
|
if (step.kind === 'static-cdn') {
|
|
467
|
-
log.info(`static ok (${step.htmlFiles} html, ${step.skipped} skipped, digest=${String(step.digest).slice(0, 12)}…)`);
|
|
437
|
+
log.info(`web-static ok (${step.htmlFiles} html, ${step.skipped} skipped, digest=${String(step.digest).slice(0, 12)}…)`);
|
|
468
438
|
}
|
|
469
439
|
else if (step.kind === 'site-delivery' && step.digest) {
|
|
470
440
|
log.info(`site-delivery ok (digest=${String(step.digest).slice(0, 12)}…)`);
|
|
@@ -501,12 +471,14 @@ async function cmdServe(args) {
|
|
|
501
471
|
});
|
|
502
472
|
const hostJs = path.join(outDir, 'vmz-serve-host.mjs');
|
|
503
473
|
if (!existsSync(hostJs)) {
|
|
504
|
-
|
|
505
|
-
|
|
474
|
+
const coreDist = resolveCoreRuntimeDist();
|
|
475
|
+
const src = coreDist ? path.join(coreDist, 'serve-host.mjs') : null;
|
|
476
|
+
if (src && existsSync(src)) {
|
|
477
|
+
copyFileSync(src, hostJs);
|
|
506
478
|
log.info(`materialized ${hostJs} from @vmz/core (release builds omit it)`);
|
|
507
479
|
}
|
|
508
|
-
|
|
509
|
-
log.error(`missing ${hostJs} — run \`vmz build\` (without --release) or ensure @vmz/core is installed
|
|
480
|
+
else {
|
|
481
|
+
log.error(`missing ${hostJs} — run \`vmz build\` (without --release) or ensure @vmz/core is installed`);
|
|
510
482
|
return 1;
|
|
511
483
|
}
|
|
512
484
|
}
|
|
@@ -521,7 +493,6 @@ async function cmdServe(args) {
|
|
|
521
493
|
VMZ_DIST: outDir,
|
|
522
494
|
VMZ_PORT: String(port),
|
|
523
495
|
VMZ_HOST: host,
|
|
524
|
-
VMZ_NATIVE_NODE: resolveNativePath(),
|
|
525
496
|
},
|
|
526
497
|
stdio: 'inherit',
|
|
527
498
|
});
|
|
@@ -568,11 +539,6 @@ async function cmdDev(args) {
|
|
|
568
539
|
}
|
|
569
540
|
}
|
|
570
541
|
const pollMs = Number(args['poll-ms'] ?? 300);
|
|
571
|
-
const targetRaw = typeof args.target === 'string' ? args.target : 'browser';
|
|
572
|
-
if (targetRaw !== 'browser' && targetRaw !== 'mini-program-wechat') {
|
|
573
|
-
log.error(`unknown --target ${targetRaw} (browser | mini-program-wechat)`);
|
|
574
|
-
return 1;
|
|
575
|
-
}
|
|
576
542
|
const ac = new AbortController();
|
|
577
543
|
const onSig = () => {
|
|
578
544
|
log.info('shutting down…');
|
|
@@ -586,7 +552,6 @@ async function cmdDev(args) {
|
|
|
586
552
|
host,
|
|
587
553
|
port,
|
|
588
554
|
pollMs,
|
|
589
|
-
target: targetRaw,
|
|
590
555
|
signal: ac.signal,
|
|
591
556
|
});
|
|
592
557
|
try {
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
-
* Logical paths stay available for serve/dev; static HTML rewrites to hashed URLs.
|
|
4
|
-
*
|
|
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
5
|
*/
|
|
6
6
|
export declare const CONTENT_ADDRESSED_ASSETS_SCHEMA = "vmz.content_addressed_assets.v0";
|
|
7
|
-
/**
|
|
8
|
-
* Rewrite relative `@import "./foo.css"` to hashed paths under assets/.
|
|
9
|
-
* @param {string} cssText
|
|
10
|
-
* @param {Record<string, string>} rewrites logical (no leading slash) or `/logical` → `assets/hash.ext`
|
|
11
|
-
*/
|
|
12
|
-
export declare function rewriteCssImports(cssText: any, rewrites: any): any;
|
|
13
7
|
/**
|
|
14
8
|
* Emit `assets/<sha256>.<ext>` copies and rewrite HTML href/src to hashed URLs.
|
|
15
9
|
* @param {string} distDir
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A3: content-addressed assets/<hash> layout for immutable CDN objects.
|
|
3
|
-
* Logical paths stay available for serve/dev; static HTML rewrites to hashed URLs.
|
|
4
|
-
*
|
|
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
5
|
*/
|
|
6
6
|
// @ts-nocheck
|
|
7
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';
|
|
12
11
|
export const CONTENT_ADDRESSED_ASSETS_SCHEMA = 'vmz.content_addressed_assets.v0';
|
|
13
12
|
/** Immutable delivery candidates (client-facing bytes). */
|
|
14
13
|
const DEFAULT_CANDIDATES = [
|
|
@@ -16,33 +15,11 @@ const DEFAULT_CANDIDATES = [
|
|
|
16
15
|
'entry-event.js',
|
|
17
16
|
'vmz.css',
|
|
18
17
|
'vmz-designs.css',
|
|
19
|
-
'vmz-style.css',
|
|
20
18
|
'vmz-dom.js',
|
|
21
19
|
'vmz-runtime.js',
|
|
22
20
|
'vmz-http.js',
|
|
23
21
|
'vmz-client-nav.js',
|
|
24
22
|
];
|
|
25
|
-
/** CSS files that may @import other logical CSS; processed after leaf CSS is hashed. */
|
|
26
|
-
const CSS_AGGREGATORS = new Set(['vmz.css']);
|
|
27
|
-
const CSS_IMPORT_RE = /@import\s*(?:url\()?['"]?(\.\/)?([^'")\s;]+)['"]?\)?/gi;
|
|
28
|
-
/**
|
|
29
|
-
* Rewrite relative `@import "./foo.css"` to hashed paths under assets/.
|
|
30
|
-
* @param {string} cssText
|
|
31
|
-
* @param {Record<string, string>} rewrites logical (no leading slash) or `/logical` → `assets/hash.ext`
|
|
32
|
-
*/
|
|
33
|
-
export function rewriteCssImports(cssText, rewrites) {
|
|
34
|
-
return cssText.replace(CSS_IMPORT_RE, (match, _dot, target) => {
|
|
35
|
-
const logical = String(target || '').replace(/^\.\//, '');
|
|
36
|
-
if (!logical)
|
|
37
|
-
return match;
|
|
38
|
-
const hashed = rewrites[logical] || rewrites[`/${logical}`] || rewrites[`assets/${logical}`];
|
|
39
|
-
if (!hashed)
|
|
40
|
-
return match;
|
|
41
|
-
const rel = hashed.startsWith('/') ? hashed.slice(1) : hashed;
|
|
42
|
-
const sibling = rel.startsWith('assets/') ? `./${path.basename(rel)}` : `./${rel}`;
|
|
43
|
-
return `@import"${sibling}"`;
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
23
|
/**
|
|
47
24
|
* Emit `assets/<sha256>.<ext>` copies and rewrite HTML href/src to hashed URLs.
|
|
48
25
|
* @param {string} distDir
|
|
@@ -60,24 +37,36 @@ export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
|
60
37
|
const objects = [];
|
|
61
38
|
/** @type {Record<string, string>} */
|
|
62
39
|
const rewrites = {};
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// Aggregator CSS (vmz.css) must import hashed leaf files — rewrite then hash.
|
|
68
|
-
for (const rel of ordered) {
|
|
69
|
-
if (!CSS_AGGREGATORS.has(rel))
|
|
70
|
-
continue;
|
|
71
|
-
const src = path.join(abs, rel);
|
|
72
|
-
if (!fs.existsSync(src))
|
|
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())
|
|
73
44
|
continue;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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,
|
|
80
67
|
});
|
|
68
|
+
rewrites[`/${logical}`] = `/${assetRel}`;
|
|
69
|
+
rewrites[logical] = assetRel;
|
|
81
70
|
}
|
|
82
71
|
objects.sort((a, b) => (a.logicalPath < b.logicalPath ? -1 : a.logicalPath > b.logicalPath ? 1 : 0));
|
|
83
72
|
let rewrittenHtml = 0;
|
|
@@ -96,78 +85,9 @@ export function emitContentAddressedAssets(distDir, opts = {}) {
|
|
|
96
85
|
const vmzDir = path.join(abs, '_vmz');
|
|
97
86
|
fs.mkdirSync(vmzDir, { recursive: true });
|
|
98
87
|
const outPath = path.join(vmzDir, 'content-addressed-assets.json');
|
|
99
|
-
|
|
88
|
+
fs.writeFileSync(outPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
100
89
|
return { manifest, assetsDir, rewrites, manifestPath: outPath };
|
|
101
90
|
}
|
|
102
|
-
/**
|
|
103
|
-
* @param {string[]} candidates
|
|
104
|
-
*/
|
|
105
|
-
function orderCandidates(candidates) {
|
|
106
|
-
const set = new Set(candidates.map((c) => String(c).replace(/\\/g, '/').replace(/^\//, '')));
|
|
107
|
-
/** @type {string[]} */
|
|
108
|
-
const out = [];
|
|
109
|
-
for (const name of DEFAULT_CANDIDATES) {
|
|
110
|
-
if (set.has(name) && !CSS_AGGREGATORS.has(name))
|
|
111
|
-
out.push(name);
|
|
112
|
-
}
|
|
113
|
-
for (const name of [...set].sort()) {
|
|
114
|
-
if (!CSS_AGGREGATORS.has(name) && !out.includes(name))
|
|
115
|
-
out.push(name);
|
|
116
|
-
}
|
|
117
|
-
if (set.has('vmz.css'))
|
|
118
|
-
out.push('vmz.css');
|
|
119
|
-
return out;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* @param {Array<Record<string, any>>} objects
|
|
123
|
-
* @param {string} logical
|
|
124
|
-
*/
|
|
125
|
-
function removeLogicalObject(objects, logical) {
|
|
126
|
-
const idx = objects.findIndex((o) => o.logicalPath === logical);
|
|
127
|
-
if (idx >= 0)
|
|
128
|
-
objects.splice(idx, 1);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* @param {string} absDist
|
|
132
|
-
* @param {string} rel
|
|
133
|
-
* @param {Record<string, string>} rewrites
|
|
134
|
-
* @param {Array<Record<string, any>>} objects
|
|
135
|
-
* @param {{ transform?: ((buf: Buffer) => Buffer) | null }} opts
|
|
136
|
-
*/
|
|
137
|
-
function ingestCandidate(absDist, rel, rewrites, objects, opts) {
|
|
138
|
-
const logical = String(rel).replace(/\\/g, '/').replace(/^\//, '');
|
|
139
|
-
const src = path.join(absDist, ...logical.split('/'));
|
|
140
|
-
if (!fs.existsSync(src) || !fs.statSync(src).isFile())
|
|
141
|
-
return;
|
|
142
|
-
let buf = fs.readFileSync(src);
|
|
143
|
-
if (typeof opts.transform === 'function') {
|
|
144
|
-
buf = opts.transform(buf);
|
|
145
|
-
}
|
|
146
|
-
const digest = sha256Hex(buf);
|
|
147
|
-
const ext = path.extname(logical) || '';
|
|
148
|
-
const assetRel = `assets/${digest}${ext}`;
|
|
149
|
-
const dest = path.join(absDist, ...assetRel.split('/'));
|
|
150
|
-
if (!fs.existsSync(dest)) {
|
|
151
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
152
|
-
fs.writeFileSync(dest, buf);
|
|
153
|
-
}
|
|
154
|
-
else {
|
|
155
|
-
const existing = sha256Hex(fs.readFileSync(dest));
|
|
156
|
-
if (existing !== digest) {
|
|
157
|
-
// Stale assets/ from a prior partial build can reuse hash filenames with different bytes.
|
|
158
|
-
fs.writeFileSync(dest, buf);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
objects.push({
|
|
162
|
-
logicalPath: logical,
|
|
163
|
-
assetPath: assetRel,
|
|
164
|
-
digest,
|
|
165
|
-
bytes: buf.length,
|
|
166
|
-
immutable: true,
|
|
167
|
-
});
|
|
168
|
-
rewrites[`/${logical}`] = `/${assetRel}`;
|
|
169
|
-
rewrites[logical] = assetRel;
|
|
170
|
-
}
|
|
171
91
|
/**
|
|
172
92
|
* Resolve an immutable object by digest under dist/assets (cross-source reuse).
|
|
173
93
|
* @param {string} distDir
|
|
@@ -203,6 +123,7 @@ export function assertSharedAssetPath(distDir, a, b, ext = '.js') {
|
|
|
203
123
|
const rel = `assets/${da}${ext}`;
|
|
204
124
|
const dest = path.join(distDir, ...rel.split('/'));
|
|
205
125
|
fs.writeFileSync(dest, typeof a === 'string' ? Buffer.from(a) : a);
|
|
126
|
+
// Second write of identical bytes must be reuse, not fork.
|
|
206
127
|
fs.writeFileSync(dest, typeof b === 'string' ? Buffer.from(b) : b);
|
|
207
128
|
const again = resolveAssetByDigest(distDir, da, ext);
|
|
208
129
|
if (!again || again.assetPath !== rel) {
|
|
@@ -217,6 +138,7 @@ function collectCandidates(distDir) {
|
|
|
217
138
|
if (fs.existsSync(path.join(distDir, name)))
|
|
218
139
|
out.push(name);
|
|
219
140
|
}
|
|
141
|
+
// Include top-level *.client.js and pages/**/*.client.js referenced by resume.
|
|
220
142
|
walk(distDir, distDir, (rel) => {
|
|
221
143
|
if (/\.client\.js$/i.test(rel))
|
|
222
144
|
out.push(rel);
|
|
@@ -250,6 +172,7 @@ function rewriteHtmlReferences(distDir, rewrites) {
|
|
|
250
172
|
let text = fs.readFileSync(file, 'utf8');
|
|
251
173
|
let next = text;
|
|
252
174
|
for (const [from, to] of pairs) {
|
|
175
|
+
// href="/x" src="/x" and unquoted variants in attributes
|
|
253
176
|
next = next.split(from).join(to);
|
|
254
177
|
}
|
|
255
178
|
if (next !== text) {
|
|
@@ -13,7 +13,7 @@ export declare const BUILTIN_PROFILES: Readonly<{
|
|
|
13
13
|
host: string;
|
|
14
14
|
assembly: string;
|
|
15
15
|
};
|
|
16
|
-
static: {
|
|
16
|
+
'web-static': {
|
|
17
17
|
host: string;
|
|
18
18
|
assembly: string;
|
|
19
19
|
};
|
|
@@ -32,16 +32,6 @@ export declare function pickSiteAuthoring(raw: any): {
|
|
|
32
32
|
artifact: string;
|
|
33
33
|
sources: any;
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
36
|
-
* `delivery.packaging.wechat` — vendor identity, not WeChat JSON / wx APIs.
|
|
37
|
-
* @param {unknown} raw
|
|
38
|
-
* @param {Array<{ code: string, message: string }>} diagnostics
|
|
39
|
-
*/
|
|
40
|
-
export declare function pickDeliveryPackaging(raw: any, diagnostics: any): {
|
|
41
|
-
wechat?: undefined;
|
|
42
|
-
} | {
|
|
43
|
-
wechat: {};
|
|
44
|
-
};
|
|
45
35
|
export declare function normalizeDeliveryAuthoring(raw: any): {
|
|
46
36
|
ok: boolean;
|
|
47
37
|
table: {
|
package/dist/delivery-profile.js
CHANGED
|
@@ -12,7 +12,7 @@ export const SERVER_RUNTIMES = Object.freeze(['node', 'worker', 'deno', 'bun', '
|
|
|
12
12
|
/** Official built-in aliases when not overridden in config. */
|
|
13
13
|
export const BUILTIN_PROFILES = Object.freeze({
|
|
14
14
|
'web-client': { host: 'browser', assembly: 'local-static' },
|
|
15
|
-
static: { host: 'browser', assembly: 'static-cdn' },
|
|
15
|
+
'web-static': { host: 'browser', assembly: 'static-cdn' },
|
|
16
16
|
'web-ssr': { host: 'browser', assembly: 'server-host', serverRuntime: 'node' },
|
|
17
17
|
'web-hybrid': { host: 'browser', assembly: 'cdn+server', serverRuntime: 'node' },
|
|
18
18
|
});
|
|
@@ -49,67 +49,6 @@ export function pickSiteAuthoring(raw) {
|
|
|
49
49
|
}
|
|
50
50
|
return site;
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
* `delivery.packaging.wechat` — vendor identity, not WeChat JSON / wx APIs.
|
|
54
|
-
* @param {unknown} raw
|
|
55
|
-
* @param {Array<{ code: string, message: string }>} diagnostics
|
|
56
|
-
*/
|
|
57
|
-
export function pickDeliveryPackaging(raw, diagnostics) {
|
|
58
|
-
if (!isPlainObject(raw) || raw.packaging == null)
|
|
59
|
-
return null;
|
|
60
|
-
if (!isPlainObject(raw.packaging)) {
|
|
61
|
-
diagnostics.push({ code: 'delivery.packaging', message: 'delivery.packaging must be an object' });
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
for (const key of Object.keys(raw.packaging)) {
|
|
65
|
-
if (key !== 'wechat') {
|
|
66
|
-
diagnostics.push({
|
|
67
|
-
code: 'delivery.packaging.vendor',
|
|
68
|
-
message: `delivery.packaging.${key} is not a known vendor (wechat)`,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const wechat = raw.packaging.wechat;
|
|
73
|
-
if (wechat == null)
|
|
74
|
-
return {};
|
|
75
|
-
if (!isPlainObject(wechat)) {
|
|
76
|
-
diagnostics.push({
|
|
77
|
-
code: 'delivery.packaging.wechat',
|
|
78
|
-
message: 'delivery.packaging.wechat must be an object',
|
|
79
|
-
});
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
for (const [k, v] of Object.entries(wechat)) {
|
|
83
|
-
if (typeof v === 'function') {
|
|
84
|
-
diagnostics.push({
|
|
85
|
-
code: 'delivery.packaging.executable',
|
|
86
|
-
message: `delivery.packaging.wechat.${k} must be pure data (no functions)`,
|
|
87
|
-
});
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (k !== 'appId' && k !== 'projectName' && k !== 'title') {
|
|
91
|
-
diagnostics.push({
|
|
92
|
-
code: 'delivery.packaging.wechat.field',
|
|
93
|
-
message: `delivery.packaging.wechat.${k} is not a known field (appId|projectName|title)`,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
else if (v != null && typeof v !== 'string') {
|
|
97
|
-
diagnostics.push({
|
|
98
|
-
code: 'delivery.packaging.wechat.type',
|
|
99
|
-
message: `delivery.packaging.wechat.${k} must be a string`,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
const out = {};
|
|
104
|
-
if (typeof wechat.appId === 'string' && wechat.appId.trim())
|
|
105
|
-
out.appId = wechat.appId.trim();
|
|
106
|
-
if (typeof wechat.projectName === 'string' && wechat.projectName.trim()) {
|
|
107
|
-
out.projectName = wechat.projectName.trim();
|
|
108
|
-
}
|
|
109
|
-
if (typeof wechat.title === 'string' && wechat.title.trim())
|
|
110
|
-
out.title = wechat.title.trim();
|
|
111
|
-
return { wechat: out };
|
|
112
|
-
}
|
|
113
52
|
function normalizeProfileEntry(entry, id, diagnostics) {
|
|
114
53
|
if (!isPlainObject(entry)) {
|
|
115
54
|
diagnostics.push({ code: 'delivery.profile.invalid', message: `profiles.${id} must be an object` });
|
|
@@ -244,10 +183,6 @@ export function normalizeDeliveryAuthoring(raw) {
|
|
|
244
183
|
},
|
|
245
184
|
};
|
|
246
185
|
}
|
|
247
|
-
else if (isPlainObject(raw.packaging)) {
|
|
248
|
-
defaultId = String(raw.default || 'web-ssr').trim() || 'web-ssr';
|
|
249
|
-
profileInputs = { ...BUILTIN_PROFILES };
|
|
250
|
-
}
|
|
251
186
|
else {
|
|
252
187
|
return {
|
|
253
188
|
ok: false,
|
|
@@ -271,9 +206,6 @@ export function normalizeDeliveryAuthoring(raw) {
|
|
|
271
206
|
message: `delivery.default '${defaultId}' is not a known profile`,
|
|
272
207
|
});
|
|
273
208
|
}
|
|
274
|
-
if (diagnostics.length)
|
|
275
|
-
return { ok: false, diagnostics };
|
|
276
|
-
const packaging = pickDeliveryPackaging(raw, diagnostics);
|
|
277
209
|
if (diagnostics.length)
|
|
278
210
|
return { ok: false, diagnostics };
|
|
279
211
|
const table = {
|
|
@@ -281,7 +213,6 @@ export function normalizeDeliveryAuthoring(raw) {
|
|
|
281
213
|
default: defaultId,
|
|
282
214
|
profiles,
|
|
283
215
|
sugar,
|
|
284
|
-
...(packaging ? { packaging } : {}),
|
|
285
216
|
};
|
|
286
217
|
table.digest = sha256Hex(canonicalJson(table));
|
|
287
218
|
return { ok: true, table };
|
package/dist/dev-session.d.ts
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
* @property {string} [host]
|
|
17
17
|
* @property {number} [port]
|
|
18
18
|
* @property {number} [pollMs]
|
|
19
|
-
* @property {'browser' | 'mini-program-wechat'} [target]
|
|
20
19
|
* @property {AbortSignal} [signal]
|
|
21
20
|
* @property {typeof createWorkspace} [createWorkspaceFn]
|
|
22
21
|
* @property {(opts: { project: string, outDir: string, host: string, port: number }) => import('node:child_process').ChildProcess} [spawnHostFn]
|