@vmz/vmz 0.1.19 → 0.1.21

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.
@@ -10,7 +10,7 @@
10
10
  * 3. Soft reload failure → **auto-respawn** serve-host (fresh ESM graph), not "keep broken host".
11
11
  */
12
12
  import { spawn } from 'node:child_process';
13
- import { existsSync } from 'node:fs';
13
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
14
14
  import path from 'node:path';
15
15
  import { buildIntegratedDocuments, projectHasDocuments } from './document-integrate.js';
16
16
  import { createWorkspace, resolveNativePath } from './index.js';
@@ -18,6 +18,8 @@ import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
18
18
  import { log } from './log.js';
19
19
  import { coalesceRootBurst, collectDevWatchRoots, classifyWatchRoot, isDependencyPath, mergeDirtySets } from './dev-watch-roots.js';
20
20
  import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
21
+ /** Track serve-host so a failed/killed launcher cannot leave an orphan port. */
22
+ const DEV_HOST_PID = '.vmz-dev-host.pid';
21
23
  export function createDevSession(options) {
22
24
  const project = options.project;
23
25
  const outDir = options.outDir;
@@ -141,11 +143,29 @@ export function createDevSession(options) {
141
143
  if (!existsSync(src)) {
142
144
  throw new Error(`vmz dev: missing src/ under ${project}`);
143
145
  }
146
+ // Same as `vmz build`: materialize plugin sources (e.g. `<Shiki>`) before compile.
147
+ // Soft rebuilds reuse the workspace; config/plugin changes still need a process restart
148
+ // until `vmz.config.*` is watched.
149
+ {
150
+ const { loadVmzConfig, applyPlugins } = await import('./plugin-host.js');
151
+ const { plugins, engines } = await loadVmzConfig(project);
152
+ if (plugins.length) {
153
+ await applyPlugins(ws, plugins, { project, outDir, engines });
154
+ }
155
+ }
144
156
  log.info('initial build (N-API workspace, full)…');
145
157
  const initial = rebuild();
146
158
  if (!printReport(initial, 'build')) {
147
159
  throw new Error('vmz dev: initial build failed');
148
160
  }
161
+ // `vmz build` assemble copies `public/**` → outDir; soft `ws.build()` does not.
162
+ // Without this, marketing images and other opaque assets 404 under `vmz dev`.
163
+ {
164
+ const { emitPublicStaticAssets } = await import('./public-static-assets.js');
165
+ emitPublicStaticAssets(outDir, { projectRoot: project });
166
+ }
167
+ // Reclaim orphan serve-host from a previous crashed launcher (same outDir).
168
+ reclaimStaleDevHost(outDir);
149
169
  if (projectHasDocuments(project)) {
150
170
  const docs = await buildIntegratedDocuments({ projectRoot: project, outDir });
151
171
  if (!docs.ok) {
@@ -495,6 +515,7 @@ export function createDevSession(options) {
495
515
  stopped = true;
496
516
  killChild(child);
497
517
  child = null;
518
+ clearDevHostPid(outDir, null);
498
519
  try {
499
520
  ws.dispose();
500
521
  }
@@ -523,7 +544,7 @@ export function listWatchedFiles(srcDir) {
523
544
  function defaultSpawnHost(opts) {
524
545
  const hostJs = path.join(opts.outDir, 'vmz-serve-host.mjs');
525
546
  const node = process.env.VMZ_NODE || process.execPath;
526
- return spawn(node, [hostJs], {
547
+ const child = spawn(node, [hostJs], {
527
548
  cwd: opts.project,
528
549
  env: {
529
550
  ...process.env,
@@ -535,7 +556,13 @@ function defaultSpawnHost(opts) {
535
556
  VMZ_NATIVE_NODE: resolveNativePath(),
536
557
  },
537
558
  stdio: ['ignore', 'inherit', 'inherit'],
559
+ // Keep child in the same process group so launcher exit can reclaim it.
560
+ detached: false,
561
+ windowsHide: true,
538
562
  });
563
+ writeDevHostPid(opts.outDir, child.pid);
564
+ child.once('exit', () => clearDevHostPid(opts.outDir, child.pid));
565
+ return child;
539
566
  }
540
567
  /**
541
568
  * @param {string} host
@@ -559,12 +586,103 @@ async function defaultSoftReload(host, port, payload = {}) {
559
586
  function killChild(child) {
560
587
  if (!child || child.killed)
561
588
  return;
589
+ const pid = child.pid;
590
+ try {
591
+ if (process.platform === 'win32' && pid) {
592
+ // Tree-kill so orphaned grandchildren (if any) do not keep the port.
593
+ spawn('taskkill', ['/pid', String(pid), '/T', '/F'], {
594
+ stdio: 'ignore',
595
+ windowsHide: true,
596
+ });
597
+ }
598
+ else {
599
+ child.kill('SIGTERM');
600
+ }
601
+ }
602
+ catch {
603
+ try {
604
+ child.kill();
605
+ }
606
+ catch {
607
+ /* ignore */
608
+ }
609
+ }
610
+ }
611
+ function devHostPidPath(outDir) {
612
+ return path.join(outDir, DEV_HOST_PID);
613
+ }
614
+ function writeDevHostPid(outDir, pid) {
615
+ if (!outDir || !pid)
616
+ return;
617
+ try {
618
+ writeFileSync(devHostPidPath(outDir), `${pid}\n`, 'utf8');
619
+ }
620
+ catch {
621
+ /* ignore */
622
+ }
623
+ }
624
+ function clearDevHostPid(outDir, pid) {
625
+ if (!outDir)
626
+ return;
627
+ try {
628
+ const p = devHostPidPath(outDir);
629
+ if (!existsSync(p))
630
+ return;
631
+ if (pid != null) {
632
+ const cur = Number(readFileSync(p, 'utf8').trim());
633
+ if (Number.isFinite(cur) && cur !== pid)
634
+ return;
635
+ }
636
+ unlinkSync(p);
637
+ }
638
+ catch {
639
+ /* ignore */
640
+ }
641
+ }
642
+ /** Kill leftover serve-host recorded for this outDir (previous crashed `vmz dev`). */
643
+ function reclaimStaleDevHost(outDir) {
644
+ const p = devHostPidPath(outDir);
645
+ if (!existsSync(p))
646
+ return;
647
+ let pid = 0;
562
648
  try {
563
- child.kill();
649
+ pid = Number(readFileSync(p, 'utf8').trim());
650
+ }
651
+ catch {
652
+ clearDevHostPid(outDir, null);
653
+ return;
654
+ }
655
+ if (!Number.isFinite(pid) || pid <= 0) {
656
+ clearDevHostPid(outDir, null);
657
+ return;
658
+ }
659
+ if (pid === process.pid) {
660
+ clearDevHostPid(outDir, null);
661
+ return;
662
+ }
663
+ try {
664
+ process.kill(pid, 0);
665
+ }
666
+ catch {
667
+ clearDevHostPid(outDir, null);
668
+ return;
669
+ }
670
+ log.warn(`reclaiming stale serve-host pid=${pid} (prior vmz dev left an orphan)`);
671
+ try {
672
+ if (process.platform === 'win32') {
673
+ spawn('taskkill', ['/pid', String(pid), '/T', '/F'], {
674
+ stdio: 'ignore',
675
+ windowsHide: true,
676
+ });
677
+ }
678
+ else {
679
+ process.kill(pid, 'SIGTERM');
680
+ }
564
681
  }
565
682
  catch {
566
683
  /* ignore */
567
684
  }
685
+ clearDevHostPid(outDir, null);
568
686
  }
569
687
  function sleep(ms) {
570
688
  return new Promise((r) => setTimeout(r, ms));
@@ -8,6 +8,7 @@ import { buildDocuments } from './document-build.js';
8
8
  import { resolveDocumentsRoot } from './document-check.js';
9
9
  import { pageHtmlRel } from './document-enrich.js';
10
10
  import { loadLocalesRouting } from './document-routing-config.js';
11
+ import { ensureLocaleImportsRewritten } from './locale-check.js';
11
12
  import { log } from './log.js';
12
13
  import { requireNativeAddon } from './native-addon.js';
13
14
  export function projectHasDocuments(projectRoot) {
@@ -22,6 +23,13 @@ export async function buildIntegratedDocuments(opts) {
22
23
  return { ok: true, skipped: true };
23
24
  }
24
25
  try {
26
+ // Node dynamic import of compiled chrome forbids bare `#locales/*`
27
+ // (no package.json imports). Rewrite must be complete before mount.
28
+ const locales = ensureLocaleImportsRewritten(outDir);
29
+ if (!locales.ok) {
30
+ log.error(locales.error || 'locale import rewrite incomplete');
31
+ return { ok: false, error: locales.error || 'locale rewrite', pages: 0 };
32
+ }
25
33
  const result = await buildDocuments({
26
34
  projectRoot,
27
35
  outDir,
package/dist/index.d.ts CHANGED
@@ -105,6 +105,7 @@ export { DELIVERY_PROFILE_AUTHORING_SCHEMA, BUILD_PROFILE_SELECTION_SCHEMA, ASSE
105
105
  export { PACK_MANIFEST_SCHEMA, packFromDeploymentIr, ensureRuntimeCompanions } from './pack.js';
106
106
  export { SERVER_ARTIFACT_SCHEMA, HTTP_CONTRACT_SCHEMA, SERVER_RUNTIME_ADAPTER_SCHEMA, emitServerArtifact, projectServerRuntimeAdapter, } from './server-artifact.js';
107
107
  export { BUILD_PROOF_SCHEMA, ASSEMBLE_MANIFEST_SCHEMA, assembleDelivery, emitBuildProof, } from './build-assemble.js';
108
+ export { buildProjectToOutDirRoot, type BuildProjectOptions, type BuildProjectResult, } from './project-build.js';
108
109
  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';
109
110
  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';
110
111
  export { buildApplicationContext, buildFormatterContext, checkLocaleRuntime, checkSsrClientParity, createLocaleSession, formatMessageTemplate, formatterContextDigest, negotiateLocale, resolveMessageVariant, validateFormatterContext, } from './locale-runtime.js';
package/dist/index.js CHANGED
@@ -345,6 +345,7 @@ export { DELIVERY_PROFILE_AUTHORING_SCHEMA, BUILD_PROFILE_SELECTION_SCHEMA, ASSE
345
345
  export { PACK_MANIFEST_SCHEMA, packFromDeploymentIr, ensureRuntimeCompanions } from './pack.js';
346
346
  export { SERVER_ARTIFACT_SCHEMA, HTTP_CONTRACT_SCHEMA, SERVER_RUNTIME_ADAPTER_SCHEMA, emitServerArtifact, projectServerRuntimeAdapter, } from './server-artifact.js';
347
347
  export { BUILD_PROOF_SCHEMA, ASSEMBLE_MANIFEST_SCHEMA, assembleDelivery, emitBuildProof, } from './build-assemble.js';
348
+ export { buildProjectToOutDirRoot, } from './project-build.js';
348
349
  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';
349
350
  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';
350
351
  export { buildApplicationContext, buildFormatterContext, checkLocaleRuntime, checkSsrClientParity, createLocaleSession, formatMessageTemplate, formatterContextDigest, negotiateLocale, resolveMessageVariant, validateFormatterContext, } from './locale-runtime.js';
@@ -80,12 +80,25 @@ export declare function emitLocaleTypedModules(report: ReturnType<typeof checkLo
80
80
  *
81
81
  * Variant pick reads `html[data-locale]` (else defaultLocale). Thin bridge until
82
82
  * host LocaleTransition reloads locale-scoped chunks (I2/I4).
83
+ *
84
+ * Contract: after this returns `ok`, `distDir` must not contain bare `#locales/`
85
+ * import specifiers (document SSR / Node import would otherwise require a fake
86
+ * `package.json#imports` map).
83
87
  */
84
88
  export declare function emitLocaleRuntimeModules(projectRoot: string, distDir: string): {
85
89
  ok: boolean;
86
90
  written: string[];
87
91
  diagnostics: LocaleDiagnostic[];
88
92
  };
93
+ /**
94
+ * Rewrite `#locales/*` → relative `locales/*.js` and assert none remain.
95
+ * Call before document SSR (`createRenderHost`) or any Node import of app dist.
96
+ */
97
+ export declare function ensureLocaleImportsRewritten(distDir: string): {
98
+ ok: boolean;
99
+ rewritten: number;
100
+ error?: string;
101
+ };
89
102
  /** MessageId rename plan — WorkspaceEdit-shaped, no parallel rename IR. */
90
103
  export declare function planLocaleRename(report: ReturnType<typeof checkLocales>, fromId: string, toId: string): {
91
104
  schema: string;
@@ -589,6 +589,10 @@ export function emitLocaleTypedModules(report, outDir) {
589
589
  *
590
590
  * Variant pick reads `html[data-locale]` (else defaultLocale). Thin bridge until
591
591
  * host LocaleTransition reloads locale-scoped chunks (I2/I4).
592
+ *
593
+ * Contract: after this returns `ok`, `distDir` must not contain bare `#locales/`
594
+ * import specifiers (document SSR / Node import would otherwise require a fake
595
+ * `package.json#imports` map).
592
596
  */
593
597
  export function emitLocaleRuntimeModules(projectRoot, distDir) {
594
598
  const report = checkLocales({ projectRoot, checkUnused: false });
@@ -597,6 +601,21 @@ export function emitLocaleRuntimeModules(projectRoot, distDir) {
597
601
  }
598
602
  // Missing locales.json5 is warning-only for now — surface diagnostics, skip emit.
599
603
  if (!report.manifest) {
604
+ const leftover = findBareLocaleImports(distDir);
605
+ if (leftover.length) {
606
+ return {
607
+ ok: false,
608
+ written: [],
609
+ diagnostics: [
610
+ ...(report.diagnostics || []),
611
+ {
612
+ severity: 'error',
613
+ code: 'locale::dist_bare_import',
614
+ message: `dist still has #locales/* imports but locales manifest is missing: ${leftover.slice(0, 4).join(', ')}`,
615
+ },
616
+ ],
617
+ };
618
+ }
600
619
  return { ok: true, written: [], diagnostics: report.diagnostics || [] };
601
620
  }
602
621
  const defaultLocale = report.manifest.defaultLocale || 'zh-hans';
@@ -630,12 +649,55 @@ export function emitLocaleRuntimeModules(projectRoot, distDir) {
630
649
  fs.writeFileSync(file, code, 'utf8');
631
650
  written.push(file);
632
651
  }
633
- rewriteLocaleImportsInDist(distDir);
652
+ const ensured = ensureLocaleImportsRewritten(distDir);
653
+ if (!ensured.ok) {
654
+ return {
655
+ ok: false,
656
+ written,
657
+ diagnostics: [
658
+ ...(report.diagnostics || []),
659
+ {
660
+ severity: 'error',
661
+ code: 'locale::dist_bare_import',
662
+ message: ensured.error || 'bare #locales/* imports remain in dist',
663
+ },
664
+ ],
665
+ };
666
+ }
634
667
  return { ok: true, written, diagnostics: report.diagnostics || [] };
635
668
  }
669
+ /**
670
+ * Rewrite `#locales/*` → relative `locales/*.js` and assert none remain.
671
+ * Call before document SSR (`createRenderHost`) or any Node import of app dist.
672
+ */
673
+ export function ensureLocaleImportsRewritten(distDir) {
674
+ const rewritten = rewriteLocaleImportsInDist(distDir);
675
+ const leftover = findBareLocaleImports(distDir);
676
+ if (leftover.length) {
677
+ return {
678
+ ok: false,
679
+ rewritten,
680
+ error: `bare #locales/* remain after rewrite: ${leftover.slice(0, 6).join(', ')}`,
681
+ };
682
+ }
683
+ return { ok: true, rewritten };
684
+ }
685
+ /** True import / dynamic-import of `#locales/…` (not comments mentioning the prefix). */
686
+ const BARE_LOCALE_IMPORT_RE = /(?:from\s*|import\s*\()\s*['"`]#locales\//;
687
+ function findBareLocaleImports(distDir) {
688
+ const hits = [];
689
+ walkDistJs(distDir, (file) => {
690
+ const text = fs.readFileSync(file, 'utf8');
691
+ if (!BARE_LOCALE_IMPORT_RE.test(text))
692
+ return;
693
+ hits.push(path.relative(distDir, file).replace(/\\/g, '/'));
694
+ });
695
+ return hits;
696
+ }
636
697
  function rewriteLocaleImportsInDist(distDir) {
637
698
  const files = [];
638
699
  walkDistJs(distDir, (file) => files.push(file));
700
+ let rewritten = 0;
639
701
  for (const file of files) {
640
702
  const text = fs.readFileSync(file, 'utf8');
641
703
  if (!text.includes('#locales/'))
@@ -649,9 +711,12 @@ function rewriteLocaleImportsInDist(distDir) {
649
711
  rel = `./${rel}`;
650
712
  return `from ${quote}${rel}${quote}`;
651
713
  });
652
- if (next !== text)
714
+ if (next !== text) {
653
715
  fs.writeFileSync(file, next, 'utf8');
716
+ rewritten += 1;
717
+ }
654
718
  }
719
+ return rewritten;
655
720
  }
656
721
  function walkDistJs(dir, fn) {
657
722
  if (!fs.existsSync(dir))
@@ -659,7 +724,8 @@ function walkDistJs(dir, fn) {
659
724
  for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
660
725
  const full = path.join(dir, ent.name);
661
726
  if (ent.isDirectory()) {
662
- if (ent.name === 'node_modules')
727
+ // Skip package trees and pack vendor — only app emit must clear `#locales/*`.
728
+ if (ent.name === 'node_modules' || ent.name === 'vendor')
663
729
  continue;
664
730
  walkDistJs(full, fn);
665
731
  }
package/dist/log.js CHANGED
@@ -22,14 +22,16 @@ export const log = {
22
22
  },
23
23
  diagnostic(d) {
24
24
  const severity = d.severity === 'warning' || d.severity === 'advice' || d.severity === 'error' ? d.severity : 'error';
25
- const code = d.code && String(d.code).length ? String(d.code) : 'diag.message';
25
+ // Only invent transitional `diag.message` when wire has no code; never remap a real code.
26
+ const hasCode = Boolean(d.code && String(d.code).length);
27
+ const code = hasCode ? String(d.code) : 'diag.message';
26
28
  const locale = resolveVmzLocale();
27
29
  const catalog = loadCliCatalog(locale);
28
30
  const line = formatDiagnostic({
29
31
  path: d.path || '',
30
32
  severity,
31
33
  code,
32
- args: d.args || (d.message ? { message: String(d.message) } : undefined),
34
+ args: d.args ?? (!hasCode && d.message ? { message: String(d.message) } : undefined),
33
35
  message: d.message,
34
36
  span: d.span,
35
37
  }, {
package/dist/pack.js CHANGED
@@ -20,7 +20,7 @@ export const PACK_MANIFEST_SCHEMA = 'vmz.pack.manifest.v0';
20
20
  export function ensureRuntimeCompanions(outDir, coreDist) {
21
21
  if (!coreDist)
22
22
  return [];
23
- const names = ['dom-core.js', 'dom-ssr.js', 'dom.client.js'];
23
+ const names = ['dom-core.js', 'dom-ssr.js', 'dom.client.js', 'direct-host-box.js', 'unknown-component.js'];
24
24
  const copied = [];
25
25
  for (const name of names) {
26
26
  const src = path.join(coreDist, name);
@@ -274,13 +274,13 @@ function sourceItem(id, path, content) {
274
274
  * @param {string[]} engines
275
275
  */
276
276
  function buildMathFacade(defaultEngine, engines) {
277
- const defaultLit = JSON.stringify(defaultEngine);
277
+ const defaultLit = jsSingleQuoted(defaultEngine);
278
278
  const branches = engines
279
279
  .map((eng, i) => {
280
280
  const tag = engineToTag(eng);
281
- const kw = i === 0 ? 'if' : 'else-if';
282
- const engLit = JSON.stringify(eng);
283
- return ` <${tag} ${kw}={(engine || ${defaultLit}) === ${engLit}} tex={tex} display={display} />`;
281
+ const kw = i === 0 ? 'v-if' : 'v-else-if';
282
+ const engLit = jsSingleQuoted(eng);
283
+ return ` <${tag} ${kw}="(engine || ${defaultLit}) === ${engLit}" :tex="tex" :display="display" />`;
284
284
  })
285
285
  .join('\n');
286
286
  return `<template>
@@ -301,13 +301,13 @@ export default class Math {
301
301
  * @param {string[]} engines
302
302
  */
303
303
  function buildCodeFacade(defaultEngine, engines) {
304
- const defaultLit = JSON.stringify(defaultEngine);
304
+ const defaultLit = jsSingleQuoted(defaultEngine);
305
305
  const branches = engines
306
306
  .map((eng, i) => {
307
307
  const tag = engineToTag(eng);
308
- const kw = i === 0 ? 'if' : 'else-if';
309
- const engLit = JSON.stringify(eng);
310
- return ` <${tag} ${kw}={(engine || ${defaultLit}) === ${engLit}} code={code} lang={lang} theme={theme} />`;
308
+ const kw = i === 0 ? 'v-if' : 'v-else-if';
309
+ const engLit = jsSingleQuoted(eng);
310
+ return ` <${tag} ${kw}="(engine || ${defaultLit}) === ${engLit}" :code="code" :lang="lang" :theme="theme" />`;
311
311
  })
312
312
  .join('\n');
313
313
  return `<template>
@@ -329,13 +329,13 @@ export default class Code {
329
329
  * @param {string[]} engines
330
330
  */
331
331
  function buildMarkdownFacade(defaultEngine, engines) {
332
- const defaultLit = JSON.stringify(defaultEngine);
332
+ const defaultLit = jsSingleQuoted(defaultEngine);
333
333
  const branches = engines
334
334
  .map((eng, i) => {
335
335
  const tag = engineToTag(eng);
336
- const kw = i === 0 ? 'if' : 'else-if';
337
- const engLit = JSON.stringify(eng);
338
- return ` <${tag} ${kw}={(engine || ${defaultLit}) === ${engLit}} source={source} />`;
336
+ const kw = i === 0 ? 'v-if' : 'v-else-if';
337
+ const engLit = jsSingleQuoted(eng);
338
+ return ` <${tag} ${kw}="(engine || ${defaultLit}) === ${engLit}" :source="source" />`;
339
339
  })
340
340
  .join('\n');
341
341
  return `<template>
@@ -350,6 +350,10 @@ export default class Markdown {
350
350
  </script>
351
351
  `;
352
352
  }
353
+ /** Embed a JS string literal inside a double-quoted Vue attribute. */
354
+ function jsSingleQuoted(value) {
355
+ return `'${String(value).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
356
+ }
353
357
  /** @param {string} engine */
354
358
  function engineToTag(engine) {
355
359
  const map = {
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Shared project delivery build (N-API + pack + assemble).
3
+ * Used by `vmz build` and `vmz test` so Browser Host sees the same
4
+ * `<out-dir>/<profiles.*.name>/` tree (including static HTML).
5
+ */
6
+ export type BuildProjectOptions = {
7
+ /** `--profile` id; empty → delivery default. */
8
+ profile?: string;
9
+ release?: boolean;
10
+ origin?: string;
11
+ /** Skip locale / documents / proof when only need pack+static for tests. */
12
+ quiet?: boolean;
13
+ };
14
+ export type BuildProjectOk = {
15
+ ok: true;
16
+ outDirRoot: string;
17
+ artifactDir: string;
18
+ deliveryName: string;
19
+ profileId: string;
20
+ assembly: string;
21
+ diagnostics: unknown[];
22
+ };
23
+ export type BuildProjectErr = {
24
+ ok: false;
25
+ outDirRoot: string;
26
+ artifactDir: string | null;
27
+ deliveryName: string | null;
28
+ diagnostics: unknown[];
29
+ error: string;
30
+ };
31
+ export type BuildProjectResult = BuildProjectOk | BuildProjectErr;
32
+ /**
33
+ * Build into `--out-dir` **root** the same way as `vmz build . --out-dir <root>`:
34
+ * nest once via `resolveProfileArtifactDir`, then N-API build + pack + assemble
35
+ * (static HTML for `web-static`). Callers resolve the serve root **after** this.
36
+ */
37
+ export declare function buildProjectToOutDirRoot(project: string, outDirRoot: string, opts?: BuildProjectOptions): Promise<BuildProjectResult>;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Shared project delivery build (N-API + pack + assemble).
3
+ * Used by `vmz build` and `vmz test` so Browser Host sees the same
4
+ * `<out-dir>/<profiles.*.name>/` tree (including static HTML).
5
+ */
6
+ import path from 'node:path';
7
+ import { assembleDelivery, emitBuildProof } from './build-assemble.js';
8
+ import { normalizeDeliveryAuthoring, resolveProfileArtifactDir, selectBuildProfile } from './delivery-profile.js';
9
+ import { buildIntegratedDocuments, projectHasDocuments } from './document-integrate.js';
10
+ import { emitLocaleRuntimeModules, localeHasErrors } from './locale-check.js';
11
+ import { emitLocaleRouteRealization } from './locale-route-emit.js';
12
+ import { packFromDeploymentIr } from './pack.js';
13
+ import { loadVmzConfig } from './plugin-host.js';
14
+ function dedupeDiagnostics(list) {
15
+ const seen = new Set();
16
+ const out = [];
17
+ for (const d of list || []) {
18
+ const key = `${d.code || ''}\0${d.path || ''}\0${d.message || ''}\0${d.severity || ''}`;
19
+ if (seen.has(key))
20
+ continue;
21
+ seen.add(key);
22
+ out.push(d);
23
+ }
24
+ return out;
25
+ }
26
+ async function runWithPlugins(ws, project, outDir, fn) {
27
+ const { applyPlugins } = await import('./plugin-host.js');
28
+ const { plugins, engines } = await loadVmzConfig(project);
29
+ if (plugins.length) {
30
+ await applyPlugins(ws, plugins, { project, outDir, engines });
31
+ }
32
+ return await fn();
33
+ }
34
+ /**
35
+ * Build into `--out-dir` **root** the same way as `vmz build . --out-dir <root>`:
36
+ * nest once via `resolveProfileArtifactDir`, then N-API build + pack + assemble
37
+ * (static HTML for `web-static`). Callers resolve the serve root **after** this.
38
+ */
39
+ export async function buildProjectToOutDirRoot(project, outDirRoot, opts = {}) {
40
+ const root = path.resolve(outDirRoot);
41
+ const diagnostics = [];
42
+ let cfg;
43
+ try {
44
+ cfg = await loadVmzConfig(project);
45
+ }
46
+ catch (e) {
47
+ return {
48
+ ok: false,
49
+ outDirRoot: root,
50
+ artifactDir: null,
51
+ deliveryName: null,
52
+ diagnostics,
53
+ error: `loadVmzConfig failed: ${e instanceof Error ? e.message : String(e)}`,
54
+ };
55
+ }
56
+ const norm = normalizeDeliveryAuthoring(cfg.delivery ?? null);
57
+ if (!norm.ok) {
58
+ return {
59
+ ok: false,
60
+ outDirRoot: root,
61
+ artifactDir: null,
62
+ deliveryName: null,
63
+ diagnostics: norm.diagnostics ?? [],
64
+ error: 'delivery authoring invalid',
65
+ };
66
+ }
67
+ const selected = selectBuildProfile(norm.table, opts.profile || '');
68
+ if (!selected.ok) {
69
+ return {
70
+ ok: false,
71
+ outDirRoot: root,
72
+ artifactDir: null,
73
+ deliveryName: null,
74
+ diagnostics: selected.diagnostics ?? [],
75
+ error: `unknown build --profile ${opts.profile || norm.table.default}`,
76
+ };
77
+ }
78
+ const deliveryName = String(selected.profile.name || selected.selection.profileId || '');
79
+ const artifactDir = resolveProfileArtifactDir(root, selected.profile);
80
+ const assembly = String(selected.selection.assembly || '');
81
+ const profileId = String(selected.selection.profileId || '');
82
+ // Dynamic import avoids init cycle with `./index.js` re-exports.
83
+ const { createWorkspace, resolveCoreRuntimeDist } = await import('./index.js');
84
+ const ws = createWorkspace({ root: project, outDir: artifactDir });
85
+ try {
86
+ const code = await runWithPlugins(ws, project, artifactDir, () => {
87
+ const report = ws.build(Boolean(opts.release));
88
+ const diags = report.diagnostics ?? [];
89
+ diagnostics.push(...diags);
90
+ const errors = diags.filter((d) => d && (d.severity === 'error' || d.level === 'error'));
91
+ return errors.length ? 1 : 0;
92
+ });
93
+ if (code !== 0) {
94
+ return {
95
+ ok: false,
96
+ outDirRoot: root,
97
+ artifactDir,
98
+ deliveryName,
99
+ diagnostics,
100
+ error: 'workspace build reported errors',
101
+ };
102
+ }
103
+ const localeEmit = emitLocaleRuntimeModules(project, artifactDir);
104
+ const localeRoutes = emitLocaleRouteRealization(project, artifactDir, {
105
+ origin: opts.origin,
106
+ });
107
+ const localeDiags = dedupeDiagnostics([
108
+ ...(localeEmit.diagnostics ?? []),
109
+ ...(localeRoutes.diagnostics ?? []),
110
+ ]);
111
+ diagnostics.push(...localeDiags);
112
+ if (!localeEmit.ok || localeHasErrors({ diagnostics: localeEmit.diagnostics })) {
113
+ return {
114
+ ok: false,
115
+ outDirRoot: root,
116
+ artifactDir,
117
+ deliveryName,
118
+ diagnostics,
119
+ error: 'locale runtime emit failed',
120
+ };
121
+ }
122
+ if (!localeRoutes.ok) {
123
+ return {
124
+ ok: false,
125
+ outDirRoot: root,
126
+ artifactDir,
127
+ deliveryName,
128
+ diagnostics,
129
+ error: 'locale route realization emit failed',
130
+ };
131
+ }
132
+ if (projectHasDocuments(project)) {
133
+ const docs = await buildIntegratedDocuments({ projectRoot: project, outDir: artifactDir });
134
+ if (!docs.ok) {
135
+ return {
136
+ ok: false,
137
+ outDirRoot: root,
138
+ artifactDir,
139
+ deliveryName,
140
+ diagnostics,
141
+ error: 'integrated documents build failed',
142
+ };
143
+ }
144
+ }
145
+ let pack;
146
+ try {
147
+ pack = packFromDeploymentIr(artifactDir, {
148
+ release: Boolean(opts.release),
149
+ profileId,
150
+ assembly,
151
+ coreDist: resolveCoreRuntimeDist(),
152
+ projectRoot: project,
153
+ });
154
+ }
155
+ catch (err) {
156
+ return {
157
+ ok: false,
158
+ outDirRoot: root,
159
+ artifactDir,
160
+ deliveryName,
161
+ diagnostics,
162
+ error: `pack failed: ${err instanceof Error ? err.message : String(err)}`,
163
+ };
164
+ }
165
+ try {
166
+ const assemble = await assembleDelivery(artifactDir, {
167
+ selection: selected.selection,
168
+ profile: {
169
+ ...selected.profile,
170
+ sources: selected.profile.sources ||
171
+ (norm.table.sugar ? norm.table.profiles[norm.table.default]?.sources : null),
172
+ },
173
+ siteId: cfg.application?.id || undefined,
174
+ origin: opts.origin,
175
+ pack: pack.manifest,
176
+ projectRoot: project,
177
+ });
178
+ if (!opts.quiet) {
179
+ emitBuildProof(artifactDir, {
180
+ selection: selected.selection,
181
+ pack: pack.manifest,
182
+ assemble: assemble.manifest,
183
+ release: Boolean(opts.release),
184
+ });
185
+ }
186
+ }
187
+ catch (err) {
188
+ return {
189
+ ok: false,
190
+ outDirRoot: root,
191
+ artifactDir,
192
+ deliveryName,
193
+ diagnostics,
194
+ error: `assemble failed: ${err instanceof Error ? err.message : String(err)}`,
195
+ };
196
+ }
197
+ return {
198
+ ok: true,
199
+ outDirRoot: root,
200
+ artifactDir,
201
+ deliveryName,
202
+ profileId,
203
+ assembly,
204
+ diagnostics,
205
+ };
206
+ }
207
+ finally {
208
+ ws.dispose();
209
+ }
210
+ }
package/dist/test-cmd.js CHANGED
@@ -5,7 +5,8 @@
5
5
  import fs from 'node:fs';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
- import { createWorkspace, loadVmzConfig, normalizeDeliveryAuthoring, resolveProfileArtifactDir, selectBuildProfile } from './index.js';
8
+ import { createWorkspace } from './index.js';
9
+ import { buildProjectToOutDirRoot } from './project-build.js';
9
10
  import { log } from './log.js';
10
11
  import { generatePrettyJson } from './pretty-json.js';
11
12
  export function registerTestCommand(cli) {
@@ -39,7 +40,7 @@ export async function cmdTest(args) {
39
40
  log.error(e instanceof Error ? e.message : String(e));
40
41
  return 1;
41
42
  }
42
- const { discoverTestManifests, buildTestReport, parseModes, buildForCompile, runCompileManifest, runLogicManifest, runSsrManifest, runResumeManifest, runBrowserManifest, runDeploymentManifest, resolveDeliveryServeRoot, } = test;
43
+ const { discoverTestManifests, buildTestReport, parseModes, runCompileManifest, runLogicManifest, runSsrManifest, runResumeManifest, runBrowserManifest, runDeploymentManifest, resolveDeliveryServeRoot, } = test;
43
44
  const project = path.resolve(String(args._[0] || '.'));
44
45
  let modes;
45
46
  try {
@@ -174,35 +175,28 @@ export async function cmdTest(args) {
174
175
  let buildError = null;
175
176
  let deliveryName = null;
176
177
  if (needsBuild) {
177
- // Align with `vmz build` / `vmz serve`: artifacts live under
178
- // `<out-dir>/<profiles.*.name>` (default name = profile id).
178
+ // Same contract as `vmz build . --out-dir <root>`: build into the
179
+ // out-dir **root** (nest + pack + static assemble inside). Do **not**
180
+ // pre-pass `resolveProfileArtifactDir` as createWorkspace outDir —
181
+ // that path skipped assemble and left Browser Host without HTML.
179
182
  const outDirRoot = outDirArg ? path.resolve(outDirArg) : fs.mkdtempSync(path.join(os.tmpdir(), 'vmz-test-'));
180
- let buildTarget = outDirRoot;
181
- try {
182
- const cfg = await loadVmzConfig(project);
183
- const norm = normalizeDeliveryAuthoring(cfg.delivery ?? null);
184
- if (norm.ok) {
185
- const selected = selectBuildProfile(norm.table, '');
186
- if (selected.ok) {
187
- deliveryName = String(selected.profile.name || selected.selection.profileId || '');
188
- buildTarget = resolveProfileArtifactDir(outDirRoot, selected.profile);
189
- }
190
- }
191
- }
192
- catch (e) {
193
- log.warn(`delivery profile resolve skipped: ${e instanceof Error ? e.message : String(e)}`);
194
- }
195
- fs.mkdirSync(buildTarget, { recursive: true });
196
- const built = buildForCompile(project, buildTarget, {
197
- createWorkspace,
198
- deliveryName,
199
- });
200
- buildOut = resolveDeliveryServeRoot(built.outDir, deliveryName);
183
+ fs.mkdirSync(outDirRoot, { recursive: true });
184
+ const built = await buildProjectToOutDirRoot(project, outDirRoot);
201
185
  buildDiags = built.diagnostics || [];
202
- if (!built.ok) {
186
+ if (built.ok === false) {
187
+ deliveryName = built.deliveryName;
203
188
  buildError = built.error || 'build failed';
189
+ buildOut = outDirRoot;
204
190
  log.error(buildError);
205
191
  }
192
+ else {
193
+ deliveryName = built.deliveryName;
194
+ buildOut = resolveDeliveryServeRoot(built.outDirRoot, built.deliveryName);
195
+ if (!fs.existsSync(path.join(buildOut, 'index.html')) && built.assembly === 'web-static') {
196
+ buildError = `web-static assemble missing index.html under ${buildOut}`;
197
+ log.error(buildError);
198
+ }
199
+ }
206
200
  }
207
201
  tests = [];
208
202
  for (const m of selected) {
@@ -100,6 +100,5 @@
100
100
  "cli.err.global_needs_project": "this `vmz` is a global install (mode=global); project commands need a project install.",
101
101
  "cli.err.global_install_hint": "Install in the app: pnpm add -D @vmz/vmz",
102
102
  "cli.err.global_run_hint": "Then run: pnpm exec vmz <command>",
103
- "cli.err.global_developer_hint": "(developer mode: run from vmz-framework packages/runtimes/vmz source — full CLI)",
104
- "diag.message": "{message}"
103
+ "cli.err.global_developer_hint": "(developer mode: run from vmz-framework packages/runtimes/vmz source — full CLI)"
105
104
  }
@@ -0,0 +1,154 @@
1
+ {
2
+ "diag.message": "{message}",
3
+ "diag.detail": "{detail}",
4
+
5
+ "vmz::template::jsx_rejected": "JSX / single-brace expressions are rejected in `<template>`: {detail}",
6
+ "vmz::template::illegal_directive": "Illegal or unsupported directive in `<template>`: {detail}",
7
+ "vmz::template::parse_failed": "Failed to parse `<template>`: {detail}",
8
+ "vmz::template::invalid_expr": "Invalid template expression: {detail}",
9
+ "vmz::template::each_missing_key": "`v-for` on `<{tag}>` should declare a `:key`",
10
+ "vmz::template::each_static_key": "`v-for` on `<{tag}>` must not use a static key `{key}`",
11
+ "vmz::template::each_literal_key": "`v-for` on `<{tag}>` must not use a literal key `{key}`",
12
+ "vmz::template::each_object_key": "`v-for` on `<{tag}>` object form should bind `:key` to `{as}`",
13
+
14
+ "vmz::style::unknown_design_token": "Unknown design token `{name}`: {detail}",
15
+ "vmz::style::unused_design_token": "Unused design token `{name}` (`{var}`): {detail}",
16
+ "vmz::style::unreferenced_global_style": "Unreferenced global style `{path}`: {detail}",
17
+ "vmz::style::token_json_invalid": "Invalid design token JSON: {detail}",
18
+ "vmz::style::theme_id_collision": "Style theme id collision: `{id}`",
19
+ "vmz::style::theme_json_unreadable": "Style theme JSON is unreadable",
20
+ "vmz::style::theme_json_invalid": "Style theme JSON is invalid",
21
+ "vmz::style::prefers_scheme_empty": "Empty prefers-color-scheme mapping for `{scheme}`",
22
+
23
+ "vmz::locale::manifest_missing": "Locale manifest missing: {detail}",
24
+ "vmz::locale::id_invalid": "{detail}",
25
+ "vmz::locale::id_collision": "{detail}",
26
+ "vmz::locale::default_missing": "{detail}",
27
+ "vmz::locale::fallback_cycle": "{detail}",
28
+ "vmz::locale::fallback_unknown": "{detail}",
29
+ "vmz::locale::dir_orphan": "{detail}",
30
+ "vmz::locale::dir_missing": "{detail}",
31
+ "vmz::locale::layout_illegal": "{detail}",
32
+ "vmz::locale::message_missing_default": "{detail}",
33
+ "vmz::locale::message_missing_variant": "{detail}",
34
+ "vmz::locale::message_parameter_mismatch": "{detail}",
35
+ "vmz::locale::message_syntax_invalid": "{detail}",
36
+ "vmz::locale::message_array_forbidden": "{detail}",
37
+ "vmz::locale::message_unused": "{detail}",
38
+ "vmz::locale::message_html_forbidden": "{detail}",
39
+ "vmz::locale::catalog_parse": "{detail}",
40
+ "vmz::locale::catalog_conflict": "{detail}",
41
+ "vmz::locale::formatter_context_incomplete": "{detail}",
42
+ "vmz::locale::formatter_version_mismatch": "{detail}",
43
+ "vmz::locale::digest_mismatch": "{detail}",
44
+ "vmz::locale::transition_partial": "{detail}",
45
+ "vmz::locale::transition_unsupported": "{detail}",
46
+ "vmz::locale::transition_load_failed": "{detail}",
47
+ "vmz::locale::machine_default_forbidden": "{detail}",
48
+ "vmz::locale::message_mixed_locale": "{detail}",
49
+ "vmz::locale::stale_generation": "{detail}",
50
+ "vmz::locale::route_collision": "{detail}",
51
+ "vmz::locale::canonical_missing": "{detail}",
52
+ "vmz::locale::hreflang_incomplete": "{detail}",
53
+ "vmz::locale::meta_locale_mismatch": "{detail}",
54
+ "vmz::locale::link_hardcoded_path": "{detail}",
55
+ "vmz::locale::cache_key_steals_content": "{detail}",
56
+ "vmz::locale::prefix_omit_without_redirect": "{detail}",
57
+ "vmz::locale::delivery_full_bundle": "{detail}",
58
+ "vmz::locale::chunk_hash_mismatch": "{detail}",
59
+ "vmz::locale::native_pack_unsigned": "{detail}",
60
+ "vmz::locale::native_pack_has_js": "{detail}",
61
+ "vmz::locale::native_pack_app_mismatch": "{detail}",
62
+ "vmz::locale::mini_cross_package_unproven": "{detail}",
63
+ "vmz::locale::server_translated_error": "{detail}",
64
+ "vmz::locale::server_format_without_context": "{detail}",
65
+ "vmz::locale::host_message_divergence": "{detail}",
66
+ "vmz::locale::message_dynamic_id_unbounded": "{detail}",
67
+ "vmz::locale::hardcoded_text": "{detail}",
68
+ "vmz::locale::pseudo_production_forbidden": "{detail}",
69
+ "vmz::locale::conformance_divergence": "{detail}",
70
+ "vmz::locale::explain_unknown": "{detail}",
71
+
72
+ "vmz::format::would_reformat": "File would be reformatted",
73
+ "vmz::format::failed": "Format failed: {detail}",
74
+
75
+ "vmz::convention::layout_suffix": "Named layout `{stem}` should end with `Layout`",
76
+ "vmz::convention::pascal_case_file": "Prefer PascalCase file name `{hint}` (got `{name}`)",
77
+ "vmz::convention::prefer_application_shell": "Prefer `Application.vmz` as the application shell",
78
+ "vmz::convention::prefer_pascal_index": "Prefer `Index.vmz` over `index.vmz` for pages",
79
+
80
+ "vmz::io::read_failed": "Failed to read file: {error}",
81
+ "vmz::sfc::parse_failed": "Failed to parse `.vmz` SFC: {detail}",
82
+
83
+ "vmz::script::client_parse_failed": "Failed to parse client script: {detail}",
84
+ "vmz::script::client_export_required": "Client script must `export default class`",
85
+ "vmz::script::forbidden_factory": "Forbidden factory `{name}` (use class + public props)",
86
+ "vmz::script::server_parse_failed": "Failed to parse server script: {detail}",
87
+ "vmz::script::server_export_required": "Server script must export a default class or function",
88
+ "vmz::script::server_struct_required": "Rust server script must declare a public struct",
89
+
90
+ "vmz::router::link_invalid": "Invalid route link: {detail}",
91
+ "vmz::router::route_table_invalid": "Invalid route table: {detail}",
92
+ "vmz::router::path_deviation": "Route path `{explicit}` differs from file `{file}`",
93
+
94
+ "vmz::runtime::missing": "Missing runtime `{name}` under `{root}`",
95
+ "vmz::emit::server_tree_failed": "Failed to emit server tree: {error}",
96
+ "vmz::emit::client_module_failed": "Failed to emit client module: {error}",
97
+ "vmz::emit::client_js_print_failed": "Failed to print client JS: {error}",
98
+ "vmz::emit::client_failed": "Failed to emit client: {error}",
99
+ "vmz::emit::server_failed": "Failed to emit server: {error}",
100
+
101
+ "vmz::program::unknown_widen": "Unknown widen via `{via}` for field `{field}`: {reason}",
102
+
103
+ "vmz::server::server_slice_not_browser_safe": "Server slice is not browser-safe: {detail}",
104
+ "vmz::server::client_mock_provider_forbidden": "{detail}",
105
+ "vmz::server::secret_client_leak": "{detail}",
106
+ "vmz::server::secret_binding_missing": "{detail}",
107
+
108
+ "vmz::tw::theme_load_failed": "Failed to load Tailwind theme: {detail}",
109
+ "vmz::tw::dynamic_style_tw": "Dynamic `style:tw` expression `{expr}` cannot be collected statically",
110
+ "vmz::tw::unbalanced_at_tailwind": "Unbalanced `@tailwind` block: {detail}",
111
+
112
+ "vmz::scss::compile_failed": "SCSS compile failed: {detail}",
113
+
114
+ "vmz::plugin::analyzer": "Plugin `{plugin}` analyzer `{id}`: {detail}",
115
+
116
+ "vmz::dx::class_name_mismatch": "{detail}",
117
+
118
+ "vmz::application::duplicate_id": "{detail}",
119
+ "vmz::application::unknown_reference": "{detail}",
120
+ "vmz::application::mount_collision": "{detail}",
121
+ "vmz::application::invalid_schema": "{detail}",
122
+ "vmz::application::invalid_config": "{detail}",
123
+ "vmz::application::invalid_descriptor": "{detail}",
124
+ "vmz::application::invalid_route_base": "{detail}",
125
+ "vmz::application::duplicate_mount": "{detail}",
126
+ "vmz::application::non_relocatable_url": "{detail}",
127
+ "vmz::application::invalid_base": "{detail}",
128
+ "vmz::application::artifact_integrity": "{detail}",
129
+ "vmz::application::cross_runtime_reference": "{detail}",
130
+ "vmz::application::isolation_unproven": "{detail}",
131
+ "vmz::application::failure_containment": "{detail}",
132
+ "vmz::application::route_not_public": "{detail}",
133
+ "vmz::application::mount_unreachable": "{detail}",
134
+ "vmz::application::session_shared": "{detail}",
135
+ "vmz::application::proxy_misroute": "{detail}",
136
+ "vmz::application::affected_leak": "{detail}",
137
+
138
+ "document::config::missing": "{detail}",
139
+ "document::config::invalid": "{detail}",
140
+ "document::config::default_locale": "{detail}",
141
+ "document::fallback::silent_forbidden": "{detail}",
142
+
143
+ "vmz::target::dom_leak_in_plan": "{detail}",
144
+ "vmz::target::unknown_view_op": "{detail}",
145
+ "vmz::target::platform_unsupported": "{detail}",
146
+ "vmz::target::profile_invalid": "{detail}",
147
+ "vmz::target::artifact_invalid": "{detail}",
148
+ "vmz::target::mini_missing_adapter": "{detail}",
149
+ "vmz::target::mini_adapter_semantic_core": "{detail}",
150
+ "vmz::target::mini_platform_private_schema": "{detail}",
151
+ "vmz::target::mini_shared_fork": "{detail}",
152
+ "vmz::target::mini_static_slice_catalog_only": "{detail}",
153
+ "vmz::target::mini_route_server_style_catalog_only": "{detail}"
154
+ }
@@ -1,4 +1,7 @@
1
1
  {
2
2
  "defaultLocale": "en-US",
3
- "locales": [{ "id": "en-US", "label": "English", "direction": "ltr" }]
3
+ "locales": [{ "id": "en-US", "label": "English", "direction": "ltr" }, { "id": "zh-CN", "label": "简体中文", "direction": "ltr" }],
4
+ "fallback": {
5
+ "zh-CN": ["en-US"]
6
+ }
4
7
  }
@@ -0,0 +1,154 @@
1
+ {
2
+ "diag.message": "{message}",
3
+ "diag.detail": "{detail}",
4
+
5
+ "vmz::template::jsx_rejected": "`<template>` 拒绝 JSX / 单花括号表达式:{detail}",
6
+ "vmz::template::illegal_directive": "`<template>` 中存在非法或不支持的指令:{detail}",
7
+ "vmz::template::parse_failed": "解析 `<template>` 失败:{detail}",
8
+ "vmz::template::invalid_expr": "模板表达式无效:{detail}",
9
+ "vmz::template::each_missing_key": "`<{tag}>` 上的 `v-for` 应声明 `:key`",
10
+ "vmz::template::each_static_key": "`<{tag}>` 上的 `v-for` 不得使用静态 key `{key}`",
11
+ "vmz::template::each_literal_key": "`<{tag}>` 上的 `v-for` 不得使用字面量 key `{key}`",
12
+ "vmz::template::each_object_key": "`<{tag}>` 上的 `v-for` 对象形式应将 `:key` 绑定到 `{as}`",
13
+
14
+ "vmz::style::unknown_design_token": "未知设计令牌 `{name}`:{detail}",
15
+ "vmz::style::unused_design_token": "未使用的设计令牌 `{name}`(`{var}`):{detail}",
16
+ "vmz::style::unreferenced_global_style": "未被引用的全局样式 `{path}`:{detail}",
17
+ "vmz::style::token_json_invalid": "设计令牌 JSON 无效:{detail}",
18
+ "vmz::style::theme_id_collision": "样式主题 id 冲突:`{id}`",
19
+ "vmz::style::theme_json_unreadable": "无法读取样式主题 JSON",
20
+ "vmz::style::theme_json_invalid": "样式主题 JSON 无效",
21
+ "vmz::style::prefers_scheme_empty": "`{scheme}` 的 prefers-color-scheme 映射为空",
22
+
23
+ "vmz::locale::manifest_missing": "缺少 locale 清单:{detail}",
24
+ "vmz::locale::id_invalid": "{detail}",
25
+ "vmz::locale::id_collision": "{detail}",
26
+ "vmz::locale::default_missing": "{detail}",
27
+ "vmz::locale::fallback_cycle": "{detail}",
28
+ "vmz::locale::fallback_unknown": "{detail}",
29
+ "vmz::locale::dir_orphan": "{detail}",
30
+ "vmz::locale::dir_missing": "{detail}",
31
+ "vmz::locale::layout_illegal": "{detail}",
32
+ "vmz::locale::message_missing_default": "{detail}",
33
+ "vmz::locale::message_missing_variant": "{detail}",
34
+ "vmz::locale::message_parameter_mismatch": "{detail}",
35
+ "vmz::locale::message_syntax_invalid": "{detail}",
36
+ "vmz::locale::message_array_forbidden": "{detail}",
37
+ "vmz::locale::message_unused": "{detail}",
38
+ "vmz::locale::message_html_forbidden": "{detail}",
39
+ "vmz::locale::catalog_parse": "{detail}",
40
+ "vmz::locale::catalog_conflict": "{detail}",
41
+ "vmz::locale::formatter_context_incomplete": "{detail}",
42
+ "vmz::locale::formatter_version_mismatch": "{detail}",
43
+ "vmz::locale::digest_mismatch": "{detail}",
44
+ "vmz::locale::transition_partial": "{detail}",
45
+ "vmz::locale::transition_unsupported": "{detail}",
46
+ "vmz::locale::transition_load_failed": "{detail}",
47
+ "vmz::locale::machine_default_forbidden": "{detail}",
48
+ "vmz::locale::message_mixed_locale": "{detail}",
49
+ "vmz::locale::stale_generation": "{detail}",
50
+ "vmz::locale::route_collision": "{detail}",
51
+ "vmz::locale::canonical_missing": "{detail}",
52
+ "vmz::locale::hreflang_incomplete": "{detail}",
53
+ "vmz::locale::meta_locale_mismatch": "{detail}",
54
+ "vmz::locale::link_hardcoded_path": "{detail}",
55
+ "vmz::locale::cache_key_steals_content": "{detail}",
56
+ "vmz::locale::prefix_omit_without_redirect": "{detail}",
57
+ "vmz::locale::delivery_full_bundle": "{detail}",
58
+ "vmz::locale::chunk_hash_mismatch": "{detail}",
59
+ "vmz::locale::native_pack_unsigned": "{detail}",
60
+ "vmz::locale::native_pack_has_js": "{detail}",
61
+ "vmz::locale::native_pack_app_mismatch": "{detail}",
62
+ "vmz::locale::mini_cross_package_unproven": "{detail}",
63
+ "vmz::locale::server_translated_error": "{detail}",
64
+ "vmz::locale::server_format_without_context": "{detail}",
65
+ "vmz::locale::host_message_divergence": "{detail}",
66
+ "vmz::locale::message_dynamic_id_unbounded": "{detail}",
67
+ "vmz::locale::hardcoded_text": "{detail}",
68
+ "vmz::locale::pseudo_production_forbidden": "{detail}",
69
+ "vmz::locale::conformance_divergence": "{detail}",
70
+ "vmz::locale::explain_unknown": "{detail}",
71
+
72
+ "vmz::format::would_reformat": "文件将被重新格式化",
73
+ "vmz::format::failed": "格式化失败:{detail}",
74
+
75
+ "vmz::convention::layout_suffix": "命名布局 `{stem}` 应以 `Layout` 结尾",
76
+ "vmz::convention::pascal_case_file": "文件名建议使用 PascalCase `{hint}`(当前为 `{name}`)",
77
+ "vmz::convention::prefer_application_shell": "应用壳优先使用 `Application.vmz`",
78
+ "vmz::convention::prefer_pascal_index": "页面优先使用 `Index.vmz` 而非 `index.vmz`",
79
+
80
+ "vmz::io::read_failed": "读取文件失败:{error}",
81
+ "vmz::sfc::parse_failed": "解析 `.vmz` SFC 失败:{detail}",
82
+
83
+ "vmz::script::client_parse_failed": "解析 client script 失败:{detail}",
84
+ "vmz::script::client_export_required": "client script 必须 `export default class`",
85
+ "vmz::script::forbidden_factory": "禁止使用工厂函数 `{name}`(请用 class + public props)",
86
+ "vmz::script::server_parse_failed": "解析 server script 失败:{detail}",
87
+ "vmz::script::server_export_required": "server script 必须导出默认 class 或函数",
88
+ "vmz::script::server_struct_required": "Rust server script 必须声明 public struct",
89
+
90
+ "vmz::router::link_invalid": "路由链接无效:{detail}",
91
+ "vmz::router::route_table_invalid": "路由表无效:{detail}",
92
+ "vmz::router::path_deviation": "路由路径 `{explicit}` 与文件 `{file}` 不一致",
93
+
94
+ "vmz::runtime::missing": "在 `{root}` 下缺少运行时 `{name}`",
95
+ "vmz::emit::server_tree_failed": "生成 server tree 失败:{error}",
96
+ "vmz::emit::client_module_failed": "生成 client module 失败:{error}",
97
+ "vmz::emit::client_js_print_failed": "打印 client JS 失败:{error}",
98
+ "vmz::emit::client_failed": "生成 client 失败:{error}",
99
+ "vmz::emit::server_failed": "生成 server 失败:{error}",
100
+
101
+ "vmz::program::unknown_widen": "字段 `{field}` 经 `{via}` 未知 widen:{reason}",
102
+
103
+ "vmz::server::server_slice_not_browser_safe": "server slice 对浏览器不安全:{detail}",
104
+ "vmz::server::client_mock_provider_forbidden": "{detail}",
105
+ "vmz::server::secret_client_leak": "{detail}",
106
+ "vmz::server::secret_binding_missing": "{detail}",
107
+
108
+ "vmz::tw::theme_load_failed": "加载 Tailwind 主题失败:{detail}",
109
+ "vmz::tw::dynamic_style_tw": "动态 `style:tw` 表达式 `{expr}` 无法静态收集",
110
+ "vmz::tw::unbalanced_at_tailwind": "`@tailwind` 块括号不匹配:{detail}",
111
+
112
+ "vmz::scss::compile_failed": "SCSS 编译失败:{detail}",
113
+
114
+ "vmz::plugin::analyzer": "插件 `{plugin}` 分析器 `{id}`:{detail}",
115
+
116
+ "vmz::dx::class_name_mismatch": "{detail}",
117
+
118
+ "vmz::application::duplicate_id": "{detail}",
119
+ "vmz::application::unknown_reference": "{detail}",
120
+ "vmz::application::mount_collision": "{detail}",
121
+ "vmz::application::invalid_schema": "{detail}",
122
+ "vmz::application::invalid_config": "{detail}",
123
+ "vmz::application::invalid_descriptor": "{detail}",
124
+ "vmz::application::invalid_route_base": "{detail}",
125
+ "vmz::application::duplicate_mount": "{detail}",
126
+ "vmz::application::non_relocatable_url": "{detail}",
127
+ "vmz::application::invalid_base": "{detail}",
128
+ "vmz::application::artifact_integrity": "{detail}",
129
+ "vmz::application::cross_runtime_reference": "{detail}",
130
+ "vmz::application::isolation_unproven": "{detail}",
131
+ "vmz::application::failure_containment": "{detail}",
132
+ "vmz::application::route_not_public": "{detail}",
133
+ "vmz::application::mount_unreachable": "{detail}",
134
+ "vmz::application::session_shared": "{detail}",
135
+ "vmz::application::proxy_misroute": "{detail}",
136
+ "vmz::application::affected_leak": "{detail}",
137
+
138
+ "document::config::missing": "{detail}",
139
+ "document::config::invalid": "{detail}",
140
+ "document::config::default_locale": "{detail}",
141
+ "document::fallback::silent_forbidden": "{detail}",
142
+
143
+ "vmz::target::dom_leak_in_plan": "{detail}",
144
+ "vmz::target::unknown_view_op": "{detail}",
145
+ "vmz::target::platform_unsupported": "{detail}",
146
+ "vmz::target::profile_invalid": "{detail}",
147
+ "vmz::target::artifact_invalid": "{detail}",
148
+ "vmz::target::mini_missing_adapter": "{detail}",
149
+ "vmz::target::mini_adapter_semantic_core": "{detail}",
150
+ "vmz::target::mini_platform_private_schema": "{detail}",
151
+ "vmz::target::mini_shared_fork": "{detail}",
152
+ "vmz::target::mini_static_slice_catalog_only": "{detail}",
153
+ "vmz::target::mini_route_server_style_catalog_only": "{detail}"
154
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/vmz",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
6
6
  "license": "MIT",
@@ -48,16 +48,16 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@vmz/commander": "0.1.19",
52
- "@vmz/core": "0.1.19",
53
- "@vmz/diagnostic": "0.1.19",
54
- "@vmz/plugin": "0.1.19",
55
- "@vmz/protocol": "0.1.19",
51
+ "@vmz/commander": "0.1.21",
52
+ "@vmz/core": "0.1.21",
53
+ "@vmz/diagnostic": "0.1.21",
54
+ "@vmz/plugin": "0.1.21",
55
+ "@vmz/protocol": "0.1.21",
56
56
  "jiti": "^2.6.1"
57
57
  },
58
58
  "peerDependencies": {
59
- "@vmz/plugin-markdown-it": "0.1.19",
60
- "@vmz/test": "0.1.19",
59
+ "@vmz/plugin-markdown-it": "0.1.21",
60
+ "@vmz/test": "0.1.21",
61
61
  "typescript": "^5.8.3"
62
62
  },
63
63
  "peerDependenciesMeta": {
@@ -92,12 +92,12 @@
92
92
  "cli"
93
93
  ],
94
94
  "optionalDependencies": {
95
- "@vmz/vmz-win32-x64": "0.1.19",
96
- "@vmz/vmz-win32-arm64": "0.1.19",
97
- "@vmz/vmz-darwin-x64": "0.1.19",
98
- "@vmz/vmz-darwin-arm64": "0.1.19",
99
- "@vmz/vmz-linux-x64": "0.1.19",
100
- "@vmz/vmz-linux-arm64": "0.1.19"
95
+ "@vmz/vmz-win32-x64": "0.1.21",
96
+ "@vmz/vmz-win32-arm64": "0.1.21",
97
+ "@vmz/vmz-darwin-x64": "0.1.21",
98
+ "@vmz/vmz-darwin-arm64": "0.1.21",
99
+ "@vmz/vmz-linux-x64": "0.1.21",
100
+ "@vmz/vmz-linux-arm64": "0.1.21"
101
101
  },
102
102
  "publishConfig": {
103
103
  "access": "public"