@vmz/vmz 0.0.4 → 0.1.0

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,74 @@
1
+ /**
2
+ * B0 — Delivery profile authoring normalize + CLI --profile resolve.
3
+ * Pure data only; expands legacy site-delivery sugar into profiles[default].
4
+ */
5
+ export declare const DELIVERY_PROFILE_AUTHORING_SCHEMA = "vmz.delivery.authoring.v0";
6
+ export declare const BUILD_PROFILE_SELECTION_SCHEMA = "vmz.build.profile_selection.v0";
7
+ /** Browser-era assembly kinds (04 B5). */
8
+ export declare const ASSEMBLIES: readonly string[];
9
+ export declare const SERVER_RUNTIMES: readonly string[];
10
+ /** Official built-in aliases when not overridden in config. */
11
+ export declare const BUILTIN_PROFILES: Readonly<{
12
+ 'web-client': {
13
+ host: string;
14
+ assembly: string;
15
+ };
16
+ 'web-static': {
17
+ host: string;
18
+ assembly: string;
19
+ };
20
+ 'web-ssr': {
21
+ host: string;
22
+ assembly: string;
23
+ serverRuntime: string;
24
+ };
25
+ 'web-hybrid': {
26
+ host: string;
27
+ assembly: string;
28
+ serverRuntime: string;
29
+ };
30
+ }>;
31
+ export declare function pickSiteAuthoring(raw: any): {
32
+ artifact: string;
33
+ sources: any;
34
+ };
35
+ export declare function normalizeDeliveryAuthoring(raw: any): {
36
+ ok: boolean;
37
+ table: {
38
+ schema: string;
39
+ default: string;
40
+ profiles: {};
41
+ sugar: boolean;
42
+ };
43
+ diagnostics?: undefined;
44
+ } | {
45
+ ok: boolean;
46
+ diagnostics: any[];
47
+ table?: undefined;
48
+ };
49
+ export declare function selectBuildProfile(table: any, cliProfile?: string): {
50
+ ok: boolean;
51
+ diagnostics: {
52
+ code: string;
53
+ message: string;
54
+ }[];
55
+ selection?: undefined;
56
+ profile?: undefined;
57
+ } | {
58
+ ok: boolean;
59
+ selection: {
60
+ schema: string;
61
+ profileId: any;
62
+ host: any;
63
+ assembly: any;
64
+ serverRuntime: any;
65
+ hasSiteSources: boolean;
66
+ authoringDigest: any;
67
+ fromCli: boolean;
68
+ };
69
+ profile: any;
70
+ diagnostics?: undefined;
71
+ };
72
+ export declare function semanticIdsForAssembly(assembly: any): string[];
73
+ export declare function canonicalJson(value: any): string;
74
+ export declare function sha256Hex(text: any): string;
@@ -0,0 +1,279 @@
1
+ /**
2
+ * B0 — Delivery profile authoring normalize + CLI --profile resolve.
3
+ * Pure data only; expands legacy site-delivery sugar into profiles[default].
4
+ */
5
+ // @ts-nocheck
6
+ import crypto from 'node:crypto';
7
+ export const DELIVERY_PROFILE_AUTHORING_SCHEMA = 'vmz.delivery.authoring.v0';
8
+ export const BUILD_PROFILE_SELECTION_SCHEMA = 'vmz.build.profile_selection.v0';
9
+ /** Browser-era assembly kinds (04 B5). */
10
+ export const ASSEMBLIES = Object.freeze(['local-static', 'static-cdn', 'server-host', 'cdn+server', 'rust-embedded']);
11
+ export const SERVER_RUNTIMES = Object.freeze(['node', 'worker', 'deno', 'bun', 'rust-host']);
12
+ /** Official built-in aliases when not overridden in config. */
13
+ export const BUILTIN_PROFILES = Object.freeze({
14
+ 'web-client': { host: 'browser', assembly: 'local-static' },
15
+ 'web-static': { host: 'browser', assembly: 'static-cdn' },
16
+ 'web-ssr': { host: 'browser', assembly: 'server-host', serverRuntime: 'node' },
17
+ 'web-hybrid': { host: 'browser', assembly: 'cdn+server', serverRuntime: 'node' },
18
+ });
19
+ function isPlainObject(v) {
20
+ return v != null && typeof v === 'object' && !Array.isArray(v);
21
+ }
22
+ export function pickSiteAuthoring(raw) {
23
+ if (!isPlainObject(raw))
24
+ return null;
25
+ if (!Array.isArray(raw.sources) || raw.sources.length < 1)
26
+ return null;
27
+ if (typeof raw.artifact !== 'string' || !String(raw.artifact).trim())
28
+ return null;
29
+ const site = {
30
+ artifact: String(raw.artifact),
31
+ sources: raw.sources,
32
+ };
33
+ for (const k of [
34
+ 'siteId',
35
+ 'resolution',
36
+ 'activation',
37
+ 'expectedCompatibility',
38
+ 'failure',
39
+ 'failurePolicy',
40
+ 'update',
41
+ 'updatePolicy',
42
+ 'rollback',
43
+ 'rollbackPolicy',
44
+ 'security',
45
+ 'securityPolicy',
46
+ ]) {
47
+ if (raw[k] !== undefined)
48
+ site[k] = raw[k];
49
+ }
50
+ return site;
51
+ }
52
+ function normalizeProfileEntry(entry, id, diagnostics) {
53
+ if (!isPlainObject(entry)) {
54
+ diagnostics.push({ code: 'delivery.profile.invalid', message: `profiles.${id} must be an object` });
55
+ return null;
56
+ }
57
+ const host = String(entry.host || 'browser');
58
+ if (host !== 'browser') {
59
+ diagnostics.push({
60
+ code: 'delivery.profile.host',
61
+ message: `profiles.${id}.host: only 'browser' is supported before Browser Production (got ${host})`,
62
+ });
63
+ }
64
+ const assembly = String(entry.assembly || '').trim();
65
+ if (!ASSEMBLIES.includes(assembly)) {
66
+ diagnostics.push({
67
+ code: 'delivery.profile.assembly',
68
+ message: `profiles.${id}.assembly must be one of ${ASSEMBLIES.join('|')} (got ${assembly || '(empty)'})`,
69
+ });
70
+ return null;
71
+ }
72
+ let serverRuntime = null;
73
+ if (assembly === 'server-host' || assembly === 'cdn+server') {
74
+ serverRuntime = String(entry.serverRuntime || 'node');
75
+ if (!SERVER_RUNTIMES.includes(serverRuntime)) {
76
+ diagnostics.push({
77
+ code: 'delivery.profile.serverRuntime',
78
+ message: `profiles.${id}.serverRuntime must be one of ${SERVER_RUNTIMES.join('|')}`,
79
+ });
80
+ }
81
+ }
82
+ let sources = null;
83
+ if (entry.sources != null) {
84
+ if (isPlainObject(entry.sources) && Array.isArray(entry.sources.sources)) {
85
+ sources = pickSiteAuthoring(entry.sources);
86
+ }
87
+ else if (Array.isArray(entry.sources)) {
88
+ sources = pickSiteAuthoring({
89
+ artifact: entry.artifact || entry.sourcesArtifact || id,
90
+ sources: entry.sources,
91
+ resolution: entry.resolution,
92
+ activation: entry.activation,
93
+ });
94
+ }
95
+ else {
96
+ diagnostics.push({
97
+ code: 'delivery.profile.sources',
98
+ message: `profiles.${id}.sources must be defineSite({...}) or a sources array with artifact`,
99
+ });
100
+ }
101
+ if (entry.sources != null && sources == null) {
102
+ const already = diagnostics.some((d) => String(d.message || '').includes(`profiles.${id}`));
103
+ if (!already) {
104
+ diagnostics.push({
105
+ code: 'delivery.profile.sources.artifact',
106
+ message: `profiles.${id} site sources require artifact string`,
107
+ });
108
+ }
109
+ }
110
+ }
111
+ return {
112
+ id,
113
+ host: 'browser',
114
+ assembly,
115
+ serverRuntime,
116
+ sources,
117
+ };
118
+ }
119
+ export function normalizeDeliveryAuthoring(raw) {
120
+ const diagnostics = [];
121
+ if (raw == null) {
122
+ const profiles = { ...BUILTIN_PROFILES };
123
+ const normalized = {};
124
+ for (const [id, entry] of Object.entries(profiles)) {
125
+ const n = normalizeProfileEntry(entry, id, diagnostics);
126
+ if (n)
127
+ normalized[id] = n;
128
+ }
129
+ const table = {
130
+ schema: DELIVERY_PROFILE_AUTHORING_SCHEMA,
131
+ default: 'web-ssr',
132
+ profiles: normalized,
133
+ sugar: false,
134
+ };
135
+ table.digest = sha256Hex(canonicalJson(table));
136
+ return { ok: true, table };
137
+ }
138
+ if (!isPlainObject(raw)) {
139
+ return {
140
+ ok: false,
141
+ diagnostics: [{ code: 'delivery.invalid', message: 'delivery must be a plain object' }],
142
+ };
143
+ }
144
+ let profileInputs = {};
145
+ let defaultId = '';
146
+ let sugar = false;
147
+ if (isPlainObject(raw.profiles)) {
148
+ defaultId = String(raw.default || '').trim();
149
+ profileInputs = { ...BUILTIN_PROFILES, ...raw.profiles };
150
+ if (!defaultId) {
151
+ const keys = Object.keys(raw.profiles);
152
+ defaultId = keys[0] || 'web-ssr';
153
+ }
154
+ }
155
+ else if (Array.isArray(raw.sources) || raw.artifact != null || raw.assembly != null) {
156
+ sugar = true;
157
+ const site = pickSiteAuthoring(raw);
158
+ defaultId = String(raw.default || raw.artifact || 'web-ssr').trim() || 'web-ssr';
159
+ const assembly = typeof raw.assembly === 'string' && ASSEMBLIES.includes(raw.assembly) ? raw.assembly : site ? 'rust-embedded' : 'server-host';
160
+ profileInputs = {
161
+ ...BUILTIN_PROFILES,
162
+ [defaultId]: {
163
+ host: raw.host || 'browser',
164
+ assembly,
165
+ serverRuntime: raw.serverRuntime || 'node',
166
+ ...(site
167
+ ? {
168
+ artifact: site.artifact,
169
+ sources: site.sources,
170
+ resolution: site.resolution,
171
+ activation: site.activation,
172
+ expectedCompatibility: site.expectedCompatibility,
173
+ failure: site.failure,
174
+ failurePolicy: site.failurePolicy,
175
+ update: site.update,
176
+ updatePolicy: site.updatePolicy,
177
+ rollback: site.rollback,
178
+ rollbackPolicy: site.rollbackPolicy,
179
+ security: site.security,
180
+ securityPolicy: site.securityPolicy,
181
+ }
182
+ : {}),
183
+ },
184
+ };
185
+ }
186
+ else {
187
+ return {
188
+ ok: false,
189
+ diagnostics: [
190
+ {
191
+ code: 'delivery.shape',
192
+ message: 'delivery must declare profiles{} or legacy { artifact, sources }',
193
+ },
194
+ ],
195
+ };
196
+ }
197
+ const profiles = {};
198
+ for (const [id, entry] of Object.entries(profileInputs)) {
199
+ const n = normalizeProfileEntry(entry, id, diagnostics);
200
+ if (n)
201
+ profiles[id] = n;
202
+ }
203
+ if (!profiles[defaultId]) {
204
+ diagnostics.push({
205
+ code: 'delivery.default',
206
+ message: `delivery.default '${defaultId}' is not a known profile`,
207
+ });
208
+ }
209
+ if (diagnostics.length)
210
+ return { ok: false, diagnostics };
211
+ const table = {
212
+ schema: DELIVERY_PROFILE_AUTHORING_SCHEMA,
213
+ default: defaultId,
214
+ profiles,
215
+ sugar,
216
+ };
217
+ table.digest = sha256Hex(canonicalJson(table));
218
+ return { ok: true, table };
219
+ }
220
+ export function selectBuildProfile(table, cliProfile = '') {
221
+ const id = String(cliProfile || '').trim() || table.default;
222
+ const profile = table.profiles[id];
223
+ if (!profile) {
224
+ return {
225
+ ok: false,
226
+ diagnostics: [
227
+ {
228
+ code: 'delivery.profile.unknown',
229
+ message: `unknown build --profile ${id} (known: ${Object.keys(table.profiles).join(', ')})`,
230
+ },
231
+ ],
232
+ };
233
+ }
234
+ const selection = {
235
+ schema: BUILD_PROFILE_SELECTION_SCHEMA,
236
+ profileId: id,
237
+ host: profile.host,
238
+ assembly: profile.assembly,
239
+ serverRuntime: profile.serverRuntime,
240
+ hasSiteSources: Boolean(profile.sources),
241
+ authoringDigest: table.digest,
242
+ fromCli: Boolean(String(cliProfile || '').trim()),
243
+ };
244
+ selection.digest = sha256Hex(canonicalJson(selection));
245
+ return { ok: true, selection, profile };
246
+ }
247
+ export function semanticIdsForAssembly(assembly) {
248
+ switch (assembly) {
249
+ case 'static-cdn':
250
+ return ['static-delivery', 'asset-graph'];
251
+ case 'server-host':
252
+ return ['server-host', 'asset-graph'];
253
+ case 'cdn+server':
254
+ return ['server-host', 'static-delivery', 'asset-graph'];
255
+ case 'rust-embedded':
256
+ return ['site-fallback', 'asset-graph'];
257
+ case 'local-static':
258
+ return ['asset-graph'];
259
+ default:
260
+ return [];
261
+ }
262
+ }
263
+ export function canonicalJson(value) {
264
+ return JSON.stringify(sortKeys(value));
265
+ }
266
+ function sortKeys(value) {
267
+ if (Array.isArray(value))
268
+ return value.map(sortKeys);
269
+ if (value && typeof value === 'object') {
270
+ const out = {};
271
+ for (const k of Object.keys(value).sort())
272
+ out[k] = sortKeys(value[k]);
273
+ return out;
274
+ }
275
+ return value;
276
+ }
277
+ export function sha256Hex(text) {
278
+ return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
279
+ }
@@ -51,8 +51,9 @@ export function createDevSession(options) {
51
51
  }
52
52
  function emitLocales() {
53
53
  const localeEmit = emitLocaleRuntimeModules(project, outDir);
54
+ // Always surface locale diagnostics (warnings included) — missing /locales must not be silent.
55
+ log.diagnostics(localeEmit.diagnostics ?? []);
54
56
  if (!localeEmit.ok || localeHasErrors({ diagnostics: localeEmit.diagnostics })) {
55
- log.diagnostics(localeEmit.diagnostics ?? []);
56
57
  log.error('locale runtime emit failed');
57
58
  return false;
58
59
  }
@@ -164,11 +164,12 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
164
164
  /** @type {string[]} */
165
165
  const cssHrefs = [];
166
166
  if (hostChrome) {
167
- // Same application stylesheet as landing pages (header/footer chrome).
168
- cssHrefs.push(`${prefix}vmz.css`);
167
+ // Integrated documents are served with pretty directory URLs. Root
168
+ // absolute assets remain correct for both emitted files and rewrites.
169
+ cssHrefs.push('/vmz.css');
169
170
  }
170
171
  if (designsHref)
171
- cssHrefs.push(prefix + designsHref);
172
+ cssHrefs.push(hostChrome ? `/${designsHref}` : prefix + designsHref);
172
173
  const cssLink = cssHrefs.map((href) => ` <link rel="stylesheet" href="${esc(href)}" />`).join('\n') + (cssHrefs.length ? '\n' : '');
173
174
  const navItems = nav
174
175
  .map((n) => {
@@ -200,13 +201,17 @@ ${cssLink}</head>
200
201
  <div class="site site--docs">
201
202
  <a class="skip-link" href="#main">Skip to content</a>
202
203
  ${header}
204
+ <div class="doc-body">
205
+ <aside class="doc-sidebar">
203
206
  ${docsNav}
204
207
  ${searchShellHtml}
205
- <div class="doc-body">
208
+ </aside>
209
+ <div class="doc-content">
206
210
  ${toc}<main id="main">
207
211
  ${bodyHtml}
208
212
  ${playgroundShellHtml}
209
213
  </main>
214
+ </div>
210
215
  </div>
211
216
  ${hostChrome.footer}
212
217
  </div>
@@ -199,6 +199,14 @@ function resolveDocHref(href, fromPageKey, locale, routeBase, pageKeySet) {
199
199
  pk = normalizePageKey(pk);
200
200
  const keys = pageKeySet.get(locale) || new Set();
201
201
  if (!keys.has(pk)) {
202
+ // A directory index and a leaf page share the same normalized PageKey shape.
203
+ // Prefer the regular sibling resolution above, then retry relative to the
204
+ // PageKey itself so `guide/optimizations/index.md` keeps its directory.
205
+ const indexJoined = path.posix.normalize(path.posix.join(fromPageKey || '.', pathPart));
206
+ const indexPk = normalizePageKey(indexJoined.replace(/^\.\//, ''));
207
+ if (keys.has(indexPk)) {
208
+ return { ok: true, locale, pageKey: indexPk, anchor, anchors: [] };
209
+ }
202
210
  return { ok: false, reason: `no PageKey ${pk} in ${locale}` };
203
211
  }
204
212
  return { ok: true, locale, pageKey: pk, anchor, anchors: [] };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * rust-embedded packaging adapter: resource index + baseline closure.
3
+ * Packaging only — does not invent route / MIME / fallback semantics.
4
+ */
5
+ export declare const EMBEDDED_RESOURCE_INDEX_SCHEMA = "vmz.embedded.resource_index.v0";
6
+ /**
7
+ * Walk outDir and build path → digest → relative blob path map.
8
+ * Copies files into `dist/_vmz/embedded-baseline/` (whole release, no file-level mix).
9
+ * @param {string} outDir
10
+ * @param {{ siteId?: string, contractDigest?: string | null }} [opts]
11
+ */
12
+ export declare function emitEmbeddedPackaging(outDir: any, opts?: {}): {
13
+ index: {
14
+ schema: string;
15
+ siteId: any;
16
+ contractDigest: any;
17
+ objectCount: number;
18
+ objects: any[];
19
+ };
20
+ indexPath: string;
21
+ baselineDir: string;
22
+ };
@@ -0,0 +1,113 @@
1
+ /**
2
+ * rust-embedded packaging adapter: resource index + baseline closure.
3
+ * Packaging only — does not invent route / MIME / fallback semantics.
4
+ */
5
+ // @ts-nocheck
6
+ import crypto from 'node:crypto';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ export const EMBEDDED_RESOURCE_INDEX_SCHEMA = 'vmz.embedded.resource_index.v0';
10
+ /** Paths that must never enter the embedded baseline closure. */
11
+ const SKIP_NAMES = new Set([
12
+ 'vmz-serve-host.mjs',
13
+ 'vmz-serve-host.js',
14
+ 'node_modules',
15
+ '.git',
16
+ ]);
17
+ /**
18
+ * Walk outDir and build path → digest → relative blob path map.
19
+ * Copies files into `dist/_vmz/embedded-baseline/` (whole release, no file-level mix).
20
+ * @param {string} outDir
21
+ * @param {{ siteId?: string, contractDigest?: string | null }} [opts]
22
+ */
23
+ export function emitEmbeddedPackaging(outDir, opts = {}) {
24
+ const vmzDir = path.join(outDir, '_vmz');
25
+ const baselineDir = path.join(vmzDir, 'embedded-baseline');
26
+ fs.mkdirSync(baselineDir, { recursive: true });
27
+ /** @type {Array<{ path: string, digest: string, blob: string, bytes: number }>} */
28
+ const objects = [];
29
+ const root = path.resolve(outDir);
30
+ walkFiles(root, (abs) => {
31
+ const rel = toPosix(path.relative(root, abs));
32
+ if (!rel || rel.startsWith('_vmz/embedded-baseline'))
33
+ return;
34
+ if (rel.startsWith('_vmz/embedded-resource-index'))
35
+ return;
36
+ const base = path.basename(abs);
37
+ if (SKIP_NAMES.has(base))
38
+ return;
39
+ // Keep other _vmz manifests inside baseline (contract, capability table, etc.)
40
+ const buf = fs.readFileSync(abs);
41
+ const digest = sha256Hex(buf);
42
+ const blobRel = `objects/${digest.slice(0, 2)}/${digest}`;
43
+ const blobAbs = path.join(baselineDir, blobRel);
44
+ fs.mkdirSync(path.dirname(blobAbs), { recursive: true });
45
+ if (!fs.existsSync(blobAbs))
46
+ fs.writeFileSync(blobAbs, buf);
47
+ // Also mirror tree under baseline/tree for host convenience
48
+ const treeAbs = path.join(baselineDir, 'tree', rel);
49
+ fs.mkdirSync(path.dirname(treeAbs), { recursive: true });
50
+ fs.copyFileSync(abs, treeAbs);
51
+ objects.push({ path: rel, digest, blob: blobRel, bytes: buf.length });
52
+ });
53
+ objects.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
54
+ const index = {
55
+ schema: EMBEDDED_RESOURCE_INDEX_SCHEMA,
56
+ siteId: opts.siteId || null,
57
+ contractDigest: opts.contractDigest || null,
58
+ objectCount: objects.length,
59
+ objects,
60
+ };
61
+ index.indexDigest = sha256Hex(canonicalJson({ ...index, indexDigest: undefined }));
62
+ const indexPath = path.join(vmzDir, 'embedded-resource-index.json');
63
+ fs.writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}\n`, 'utf8');
64
+ // Optional include_bytes entry point for Rust packaging adapters
65
+ const rsPath = path.join(vmzDir, 'embedded_site.rs');
66
+ fs.writeFileSync(rsPath, `// @generated by vmz — rust-embedded packaging adapter (do not edit)
67
+ // Index digest: ${index.indexDigest}
68
+ pub const EMBEDDED_RESOURCE_INDEX: &str = include_str!("embedded-resource-index.json");
69
+ `, 'utf8');
70
+ return { index, indexPath, baselineDir };
71
+ }
72
+ /**
73
+ * @param {string} dir
74
+ * @param {(abs: string) => void} onFile
75
+ */
76
+ function walkFiles(dir, onFile) {
77
+ if (!fs.existsSync(dir))
78
+ return;
79
+ for (const name of fs.readdirSync(dir)) {
80
+ if (SKIP_NAMES.has(name))
81
+ continue;
82
+ const abs = path.join(dir, name);
83
+ const st = fs.statSync(abs);
84
+ if (st.isDirectory()) {
85
+ if (name === 'embedded-baseline' && path.basename(path.dirname(abs)) === '_vmz')
86
+ continue;
87
+ walkFiles(abs, onFile);
88
+ }
89
+ else if (st.isFile()) {
90
+ onFile(abs);
91
+ }
92
+ }
93
+ }
94
+ function toPosix(p) {
95
+ return p.split(path.sep).join('/');
96
+ }
97
+ function sha256Hex(data) {
98
+ return crypto.createHash('sha256').update(data).digest('hex');
99
+ }
100
+ function canonicalJson(value) {
101
+ return JSON.stringify(sortKeys(value));
102
+ }
103
+ function sortKeys(value) {
104
+ if (Array.isArray(value))
105
+ return value.map(sortKeys);
106
+ if (value && typeof value === 'object') {
107
+ const out = {};
108
+ for (const k of Object.keys(value).sort())
109
+ out[k] = sortKeys(value[k]);
110
+ return out;
111
+ }
112
+ return value;
113
+ }
package/dist/index.d.ts CHANGED
@@ -151,10 +151,17 @@ export { STATIC_DELIVERY_MANIFEST_SCHEMA, emitWebStatic, } from './static-emit.j
151
151
  export { CONTENT_ADDRESSED_ASSETS_SCHEMA, emitContentAddressedAssets, resolveAssetByDigest, assertSharedAssetPath, contentAddressedAssetsDigest, } from './content-addressed-assets.js';
152
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
153
  export { SITE_DELIVERY_CONTRACT_SCHEMA, SITE_DELIVERY_RESOLUTION_SCHEMA, defineSite, normalizeSiteDelivery, normalizeSourceProbe, resolveSiteRelease, probeReleaseDirectory, emitSiteDelivery, } from './site-delivery.js';
154
+ export { EMBEDDED_RESOURCE_INDEX_SCHEMA, emitEmbeddedPackaging, } from './embedded-packaging.js';
155
+ export { SERVER_LANG_IDS, SERVER_LANG_ALIASES, SERVER_LANGUAGE_BACKENDS, resolveServerLanguage, assertLangRuntimePair, } from './server-language-backend.js';
156
+ export { DELIVERY_PROFILE_AUTHORING_SCHEMA, BUILD_PROFILE_SELECTION_SCHEMA, ASSEMBLIES, SERVER_RUNTIMES, BUILTIN_PROFILES, pickSiteAuthoring, normalizeDeliveryAuthoring, selectBuildProfile, semanticIdsForAssembly, } from './delivery-profile.js';
157
+ export { PACK_MANIFEST_SCHEMA, packFromDeploymentIr, ensureRuntimeCompanions } from './pack.js';
158
+ export { SERVER_ARTIFACT_SCHEMA, HTTP_CONTRACT_SCHEMA, SERVER_RUNTIME_ADAPTER_SCHEMA, emitServerArtifact, projectServerRuntimeAdapter, } from './server-artifact.js';
159
+ export { BUILD_PROOF_SCHEMA, ASSEMBLE_MANIFEST_SCHEMA, assembleDelivery, emitBuildProof, } from './build-assemble.js';
154
160
  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
161
  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';
156
162
  export { buildApplicationContext, buildFormatterContext, checkLocaleRuntime, checkSsrClientParity, createLocaleSession, formatMessageTemplate, formatterContextDigest, negotiateLocale, resolveMessageVariant, validateFormatterContext, } from './locale-runtime.js';
157
- export { absoluteUrl, assertLocaleCacheKey, buildLocalePageMeta, buildLocaleRouteRealizationTable, checkLocaleRouter, commitLocaleRouteMetaTransition, localeAwareCacheKey, parseLocaleFromPath, planLocalePathNavigation, realizeRoutePath, resolveLinkHref, } from './locale-router.js';
163
+ export { absoluteUrl, assertLocaleCacheKey, buildLocalePageMeta, buildLocaleRouteRealizationTable, checkLocaleRouter, commitLocaleRouteMetaTransition, localeAwareCacheKey, localizeBodyLinks, localizeSameAppHref, parseLocaleFromPath, planLocalePathNavigation, realizeRoutePath, resolveLinkHref, } from './locale-router.js';
164
+ export { LOCALE_ROUTE_REALIZATION_ARTIFACT_SCHEMA, emitLocaleRouteRealization, } from './locale-route-emit.js';
158
165
  export { assertHostMessageInvariant, assertServerErrorEnvelope, assertServerFormatContext, buildLocaleDeliveryResolution, checkLocaleDelivery, fallbackDigest, messageCatalogHash, proveMiniPackageMessages, validateNativeLocalePack, } from './locale-delivery.js';
159
166
  export { checkLocaleConformance, diffLocaleCatalogs, explainLocaleMessage, extractHardcodedText, pseudoLocalizeCatalog, } from './locale-tooling.js';
160
167
  export { applyPlugins, contentHash, defineConfig, definePlugin, loadVmzConfig, } from './plugin-host.js';
package/dist/index.js CHANGED
@@ -333,10 +333,17 @@ export { STATIC_DELIVERY_MANIFEST_SCHEMA, emitWebStatic, } from './static-emit.j
333
333
  export { CONTENT_ADDRESSED_ASSETS_SCHEMA, emitContentAddressedAssets, resolveAssetByDigest, assertSharedAssetPath, contentAddressedAssetsDigest, } from './content-addressed-assets.js';
334
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
335
  export { SITE_DELIVERY_CONTRACT_SCHEMA, SITE_DELIVERY_RESOLUTION_SCHEMA, defineSite, normalizeSiteDelivery, normalizeSourceProbe, resolveSiteRelease, probeReleaseDirectory, emitSiteDelivery, } from './site-delivery.js';
336
+ export { EMBEDDED_RESOURCE_INDEX_SCHEMA, emitEmbeddedPackaging, } from './embedded-packaging.js';
337
+ export { SERVER_LANG_IDS, SERVER_LANG_ALIASES, SERVER_LANGUAGE_BACKENDS, resolveServerLanguage, assertLangRuntimePair, } from './server-language-backend.js';
338
+ export { DELIVERY_PROFILE_AUTHORING_SCHEMA, BUILD_PROFILE_SELECTION_SCHEMA, ASSEMBLIES, SERVER_RUNTIMES, BUILTIN_PROFILES, pickSiteAuthoring, normalizeDeliveryAuthoring, selectBuildProfile, semanticIdsForAssembly, } from './delivery-profile.js';
339
+ export { PACK_MANIFEST_SCHEMA, packFromDeploymentIr, ensureRuntimeCompanions } from './pack.js';
340
+ export { SERVER_ARTIFACT_SCHEMA, HTTP_CONTRACT_SCHEMA, SERVER_RUNTIME_ADAPTER_SCHEMA, emitServerArtifact, projectServerRuntimeAdapter, } from './server-artifact.js';
341
+ export { BUILD_PROOF_SCHEMA, ASSEMBLE_MANIFEST_SCHEMA, assembleDelivery, emitBuildProof, } from './build-assemble.js';
336
342
  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
343
  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';
338
344
  export { buildApplicationContext, buildFormatterContext, checkLocaleRuntime, checkSsrClientParity, createLocaleSession, formatMessageTemplate, formatterContextDigest, negotiateLocale, resolveMessageVariant, validateFormatterContext, } from './locale-runtime.js';
339
- export { absoluteUrl, assertLocaleCacheKey, buildLocalePageMeta, buildLocaleRouteRealizationTable, checkLocaleRouter, commitLocaleRouteMetaTransition, localeAwareCacheKey, parseLocaleFromPath, planLocalePathNavigation, realizeRoutePath, resolveLinkHref, } from './locale-router.js';
345
+ export { absoluteUrl, assertLocaleCacheKey, buildLocalePageMeta, buildLocaleRouteRealizationTable, checkLocaleRouter, commitLocaleRouteMetaTransition, localeAwareCacheKey, localizeBodyLinks, localizeSameAppHref, parseLocaleFromPath, planLocalePathNavigation, realizeRoutePath, resolveLinkHref, } from './locale-router.js';
346
+ export { LOCALE_ROUTE_REALIZATION_ARTIFACT_SCHEMA, emitLocaleRouteRealization, } from './locale-route-emit.js';
340
347
  export { assertHostMessageInvariant, assertServerErrorEnvelope, assertServerFormatContext, buildLocaleDeliveryResolution, checkLocaleDelivery, fallbackDigest, messageCatalogHash, proveMiniPackageMessages, validateNativeLocalePack, } from './locale-delivery.js';
341
348
  export { checkLocaleConformance, diffLocaleCatalogs, explainLocaleMessage, extractHardcodedText, pseudoLocalizeCatalog, } from './locale-tooling.js';
342
349
  export { applyPlugins, contentHash, defineConfig, definePlugin, loadVmzConfig, } from './plugin-host.js';
@@ -250,10 +250,12 @@ export function checkLocales(opts) {
250
250
  const manifestPathJson = path.join(localesRoot, 'locales.json');
251
251
  const manifestPath = fs.existsSync(manifestPathJson5) ? manifestPathJson5 : fs.existsSync(manifestPathJson) ? manifestPathJson : null;
252
252
  if (!fs.existsSync(localesRoot) || !manifestPath) {
253
+ // Discipline nudge: i18n is first-class — missing policy is never silent.
254
+ // Soft for now (warning); production profiles may elevate to error later.
253
255
  diagnostics.push({
254
256
  code: DIAG_MANIFEST_MISSING,
255
- severity: 'error',
256
- message: 'locales/locales.json5 missing (LocaleId policy truth source)',
257
+ severity: 'warning',
258
+ message: 'locales/locales.json5 missing — declare LocaleId policy under /locales (native i18n, not an afterthought)',
257
259
  path: 'locales/locales.json5',
258
260
  });
259
261
  return emptyReport(projectRoot, diagnostics);
@@ -586,9 +588,10 @@ function paramSignature(params) {
586
588
  .join(',');
587
589
  }
588
590
  function emptyReport(projectRoot, diagnostics) {
591
+ const hasErrors = (diagnostics || []).some((d) => d.severity === 'error');
589
592
  return {
590
593
  schema: LOCALE_CHECK_SCHEMA,
591
- status: 'failed',
594
+ status: hasErrors ? 'failed' : 'ready',
592
595
  root: projectRoot,
593
596
  localesRoot: 'locales',
594
597
  manifest: null,
@@ -706,15 +709,15 @@ export function emitLocaleTypedModules(report, outDir) {
706
709
  * @returns {{ ok: boolean, written: string[], diagnostics: any[] }}
707
710
  */
708
711
  export function emitLocaleRuntimeModules(projectRoot, distDir) {
709
- const localesRoot = path.join(projectRoot, 'locales');
710
- if (!fs.existsSync(localesRoot)) {
711
- return { ok: true, written: [], diagnostics: [] };
712
- }
713
712
  const report = checkLocales({ projectRoot, checkUnused: false });
714
713
  if (localeHasErrors(report)) {
715
714
  return { ok: false, written: [], diagnostics: report.diagnostics || [] };
716
715
  }
717
- const defaultLocale = report.manifest?.defaultLocale || 'zh-hans';
716
+ // Missing locales.json5 is warning-only for now — surface diagnostics, skip emit.
717
+ if (!report.manifest) {
718
+ return { ok: true, written: [], diagnostics: report.diagnostics || [] };
719
+ }
720
+ const defaultLocale = report.manifest.defaultLocale || 'zh-hans';
718
721
  const byId = new Map((report.messageCatalog?.messages || []).map((m) => [m.messageId, m]));
719
722
  /** @type {string[]} */
720
723
  const written = [];
@@ -117,8 +117,8 @@ function cmdLocaleList(args) {
117
117
  const projectRoot = resolveProject(args);
118
118
  const report = checkLocales({ projectRoot, checkUnused: false });
119
119
  if (!report.manifest) {
120
- log.error('locales.json5 missing');
121
- return 1;
120
+ console.warn('vmz warn vmz::locale::manifest_missing: locales/locales.json5 missing');
121
+ return localeHasErrors(report) ? 1 : 0;
122
122
  }
123
123
  for (const loc of report.manifest.locales) {
124
124
  const mark = loc.id === report.manifest.defaultLocale ? ' (default)' : '';