@vmz/vmz 0.0.3 → 0.0.4

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