@vmz/vmz 0.1.11 → 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.
Files changed (43) hide show
  1. package/dist/build-assemble.js +3 -4
  2. package/dist/cdn-policy.js +4 -5
  3. package/dist/cli.js +15 -50
  4. package/dist/content-addressed-assets.d.ts +2 -8
  5. package/dist/content-addressed-assets.js +34 -110
  6. package/dist/delivery-profile.d.ts +1 -11
  7. package/dist/delivery-profile.js +1 -70
  8. package/dist/dev-session.d.ts +0 -1
  9. package/dist/dev-session.js +64 -220
  10. package/dist/document-build.js +34 -61
  11. package/dist/document-cmd.js +9 -2
  12. package/dist/document-enrich.d.ts +1 -2
  13. package/dist/document-enrich.js +4 -15
  14. package/dist/document-integrate.js +21 -28
  15. package/dist/embedded-packaging.js +1 -2
  16. package/dist/index.d.ts +1 -15
  17. package/dist/index.js +3 -58
  18. package/dist/locale-check.js +61 -28
  19. package/dist/locale-cmd.js +42 -10
  20. package/dist/locale-route-emit.d.ts +1 -4
  21. package/dist/locale-route-emit.js +32 -7
  22. package/dist/locale-router.js +8 -26
  23. package/dist/pack.js +2 -3
  24. package/dist/production-observability.js +2 -3
  25. package/dist/production-test-pack.js +3 -4
  26. package/dist/release-pack.js +18 -5
  27. package/dist/server-artifact.js +3 -4
  28. package/dist/site-delivery.js +3 -4
  29. package/dist/static-emit.js +78 -116
  30. package/dist/test-cmd.js +1 -2
  31. package/package.json +12 -12
  32. package/dist/dev-watch-roots.d.ts +0 -67
  33. package/dist/dev-watch-roots.js +0 -220
  34. package/dist/document-host-chrome.d.ts +0 -28
  35. package/dist/document-host-chrome.js +0 -128
  36. package/dist/native-addon.d.ts +0 -9
  37. package/dist/native-addon.js +0 -84
  38. package/dist/pretty-json.d.ts +0 -19
  39. package/dist/pretty-json.js +0 -43
  40. package/dist/route-path.d.ts +0 -35
  41. package/dist/route-path.js +0 -77
  42. package/dist/wechat-packaging.d.ts +0 -22
  43. package/dist/wechat-packaging.js +0 -59
@@ -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
- writePrettyJsonFile(file, result);
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
- writePrettyJsonFile(file, body);
189
+ writeFileSync(file, `${JSON.stringify(body, null, 2)}\n`, 'utf8');
191
190
  return { proof: body, path: file };
192
191
  }
@@ -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
- writePrettyJsonFile(path.join(vmzDir, 'cdn-policy-manifest.json'), policy);
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
- writePrettyJsonFile(path.join(adaptersDir, 'local-static', 'projection.json'), local);
156
- writePrettyJsonFile(path.join(adaptersDir, 'netlify', 'projection.json'), netlify);
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, materializeServeHostRuntime, resolveCoreRuntimeDist, resolveNativePath, } from './index.js';
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; --target mini-program-wechat packs dist/wechat
107
+ vmz build [path] [options] Build project via Workspace
108
108
  vmz serve [path] [options] Serve dist (optional --build)
109
- vmz dev [path] [options] Rebuild session; --target mini-program-wechat packs dist/wechat
110
- vmz format [path] [--check] Format .vmz via N-API (oxc formatter + EditorConfig)
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}${wechatPack ? ' (target=mini-program-wechat)' : ''}`);
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
- try {
505
- materializeServeHostRuntime(outDir);
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
- catch (err) {
509
- log.error(`missing ${hostJs} — run \`vmz build\` (without --release) or ensure @vmz/core is installed (${err instanceof Error ? err.message : err})`);
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
- * CSS aggregators (vmz.css) rewrite `@import` to hashed sibling paths under assets/.
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
- * CSS aggregators (vmz.css) rewrite `@import` to hashed sibling paths under assets/.
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 ordered = orderCandidates(candidates);
64
- for (const rel of ordered) {
65
- ingestCandidate(abs, rel, rewrites, objects, { transform: null });
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 rewritten = rewriteCssImports(fs.readFileSync(src, 'utf8'), rewrites);
75
- removeLogicalObject(objects, rel);
76
- delete rewrites[`/${rel}`];
77
- delete rewrites[rel];
78
- ingestCandidate(abs, rel, rewrites, objects, {
79
- transform: () => Buffer.from(rewritten, 'utf8'),
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,77 +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
- writePrettyJsonFile(outPath, manifest);
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
- throw new Error(`content-address collision at ${assetRel}`);
158
- }
159
- }
160
- objects.push({
161
- logicalPath: logical,
162
- assetPath: assetRel,
163
- digest,
164
- bytes: buf.length,
165
- immutable: true,
166
- });
167
- rewrites[`/${logical}`] = `/${assetRel}`;
168
- rewrites[logical] = assetRel;
169
- }
170
91
  /**
171
92
  * Resolve an immutable object by digest under dist/assets (cross-source reuse).
172
93
  * @param {string} distDir
@@ -202,6 +123,7 @@ export function assertSharedAssetPath(distDir, a, b, ext = '.js') {
202
123
  const rel = `assets/${da}${ext}`;
203
124
  const dest = path.join(distDir, ...rel.split('/'));
204
125
  fs.writeFileSync(dest, typeof a === 'string' ? Buffer.from(a) : a);
126
+ // Second write of identical bytes must be reuse, not fork.
205
127
  fs.writeFileSync(dest, typeof b === 'string' ? Buffer.from(b) : b);
206
128
  const again = resolveAssetByDigest(distDir, da, ext);
207
129
  if (!again || again.assetPath !== rel) {
@@ -216,6 +138,7 @@ function collectCandidates(distDir) {
216
138
  if (fs.existsSync(path.join(distDir, name)))
217
139
  out.push(name);
218
140
  }
141
+ // Include top-level *.client.js and pages/**/*.client.js referenced by resume.
219
142
  walk(distDir, distDir, (rel) => {
220
143
  if (/\.client\.js$/i.test(rel))
221
144
  out.push(rel);
@@ -249,6 +172,7 @@ function rewriteHtmlReferences(distDir, rewrites) {
249
172
  let text = fs.readFileSync(file, 'utf8');
250
173
  let next = text;
251
174
  for (const [from, to] of pairs) {
175
+ // href="/x" src="/x" and unquoted variants in attributes
252
176
  next = next.split(from).join(to);
253
177
  }
254
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: {
@@ -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 };
@@ -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]