@vmz/vmz 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-session.js +121 -3
- package/dist/document-integrate.js +8 -0
- package/dist/index.js +1 -0
- package/dist/locale-check.d.ts +13 -0
- package/dist/locale-check.js +69 -3
- package/dist/locale-router.d.ts +3 -11
- package/dist/locale-router.js +2 -52
- package/dist/log.js +4 -2
- package/dist/pack.js +1 -1
- package/dist/plugin-host.js +16 -12
- package/dist/route-path.d.ts +2 -2
- package/dist/route-path.js +7 -5
- package/dist/server-artifact.d.ts +5 -53
- package/dist/server-artifact.js +28 -167
- package/dist/static-emit.js +12 -40
- package/locales/en-US/cli.json +1 -2
- package/locales/en-US/diag.json +154 -0
- package/locales/locales.json +4 -1
- package/locales/zh-CN/diag.json +154 -0
- package/package.json +14 -14
package/dist/dev-session.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.js
CHANGED
|
@@ -127,6 +127,7 @@ export const SERVE_HOST_RUNTIME_FILES = [
|
|
|
127
127
|
['deployment-registry.js', 'deployment-registry.js'],
|
|
128
128
|
['render-host.js', 'render-host.js'],
|
|
129
129
|
['route-layout-chain.js', 'route-layout-chain.js'],
|
|
130
|
+
['localize-body-links.js', 'localize-body-links.js'],
|
|
130
131
|
];
|
|
131
132
|
/** Copy serve-host + registry bootstrap modules from `@vmz/core` into app outDir. */
|
|
132
133
|
export function materializeServeHostRuntime(outDir, coreDist = resolveCoreRuntimeDist()) {
|
package/dist/locale-check.d.ts
CHANGED
|
@@ -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;
|
package/dist/locale-check.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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/locale-router.d.ts
CHANGED
|
@@ -130,17 +130,9 @@ export declare function parseLocaleFromPath(pathname: string, supportedLocales:
|
|
|
130
130
|
localeId: string;
|
|
131
131
|
restPath: string;
|
|
132
132
|
};
|
|
133
|
-
export
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
} | string>;
|
|
137
|
-
defaultLocale?: string;
|
|
138
|
-
routing?: LocaleRoutingOpts;
|
|
139
|
-
}
|
|
140
|
-
/** Rewrite a same-app href to the given LocaleId via route realization. */
|
|
141
|
-
export declare function localizeSameAppHref(href: string, localeId: string, artifact: LocaleHrefArtifact): string;
|
|
142
|
-
/** Rewrite `<a data-vmz-route href>` in HTML body to retain `localeId`. */
|
|
143
|
-
export declare function localizeBodyLinks(html: string, localeId: string, artifact: LocaleHrefArtifact, escapeAttr?: (s: string) => string): string;
|
|
133
|
+
export type { LocaleHrefArtifact } from '@vmz/core/localize-body-links';
|
|
134
|
+
/** Rewrite a same-app href — single implementation in `@vmz/core/localize-body-links`. */
|
|
135
|
+
export { localizeBodyLinks, localizeSameAppHref } from '@vmz/core/localize-body-links';
|
|
144
136
|
/** Plan redirect / negotiation for an incoming URL (omit-prefix aware). */
|
|
145
137
|
export declare function planLocalePathNavigation(input: {
|
|
146
138
|
pathname: string;
|
package/dist/locale-router.js
CHANGED
|
@@ -228,58 +228,8 @@ export function parseLocaleFromPath(pathname, supportedLocales) {
|
|
|
228
228
|
}
|
|
229
229
|
return { localeId: null, restPath: normalizePath(pathname) };
|
|
230
230
|
}
|
|
231
|
-
/** Rewrite a same-app href
|
|
232
|
-
export
|
|
233
|
-
if (!href || !localeId || !artifact)
|
|
234
|
-
return href;
|
|
235
|
-
if (href.startsWith('#') || /^(mailto|tel|javascript):/i.test(href))
|
|
236
|
-
return href;
|
|
237
|
-
if (/^[a-z][a-z0-9+.-]*:/i.test(href) && !href.startsWith('/'))
|
|
238
|
-
return href;
|
|
239
|
-
let pathname = String(href);
|
|
240
|
-
let search = '';
|
|
241
|
-
let hash = '';
|
|
242
|
-
const hashIdx = pathname.indexOf('#');
|
|
243
|
-
if (hashIdx >= 0) {
|
|
244
|
-
hash = pathname.slice(hashIdx);
|
|
245
|
-
pathname = pathname.slice(0, hashIdx);
|
|
246
|
-
}
|
|
247
|
-
const qIdx = pathname.indexOf('?');
|
|
248
|
-
if (qIdx >= 0) {
|
|
249
|
-
search = pathname.slice(qIdx);
|
|
250
|
-
pathname = pathname.slice(0, qIdx);
|
|
251
|
-
}
|
|
252
|
-
if (!pathname)
|
|
253
|
-
pathname = '/';
|
|
254
|
-
const supported = (artifact.locales || []).map((l) => (typeof l === 'string' ? l : l.id)).filter(Boolean);
|
|
255
|
-
const defaultLocale = artifact.defaultLocale || artifact.routing?.defaultLocale;
|
|
256
|
-
const routing = {
|
|
257
|
-
strategy: artifact.routing?.strategy || 'prefix',
|
|
258
|
-
defaultPrefix: artifact.routing?.defaultPrefix || 'include',
|
|
259
|
-
defaultLocale,
|
|
260
|
-
};
|
|
261
|
-
const parsed = parseLocaleFromPath(pathname, supported);
|
|
262
|
-
const rest = parsed.restPath || '/';
|
|
263
|
-
const realized = realizeRoutePath(localeId, rest, routing);
|
|
264
|
-
return `${realized.path}${search}${hash}`;
|
|
265
|
-
}
|
|
266
|
-
/** Rewrite `<a data-vmz-route href>` in HTML body to retain `localeId`. */
|
|
267
|
-
export function localizeBodyLinks(html, localeId, artifact, escapeAttr = (s) => String(s).replace(/&/g, '&').replace(/"/g, '"')) {
|
|
268
|
-
if (!html || !localeId || !artifact)
|
|
269
|
-
return html;
|
|
270
|
-
return String(html).replace(/<a\b([^>]*)>/gi, (full, attrs) => {
|
|
271
|
-
if (!/\bdata-vmz-route\s*=/.test(attrs))
|
|
272
|
-
return full;
|
|
273
|
-
const hm = attrs.match(/\bhref\s*=\s*"([^"]*)"/i);
|
|
274
|
-
if (!hm)
|
|
275
|
-
return full;
|
|
276
|
-
const next = localizeSameAppHref(hm[1], localeId, artifact);
|
|
277
|
-
if (next === hm[1])
|
|
278
|
-
return full;
|
|
279
|
-
const newAttrs = attrs.replace(/\bhref\s*=\s*"[^"]*"/i, `href="${escapeAttr(next)}"`);
|
|
280
|
-
return `<a${newAttrs}>`;
|
|
281
|
-
});
|
|
282
|
-
}
|
|
231
|
+
/** Rewrite a same-app href — single implementation in `@vmz/core/localize-body-links`. */
|
|
232
|
+
export { localizeBodyLinks, localizeSameAppHref } from '@vmz/core/localize-body-links';
|
|
283
233
|
/** Plan redirect / negotiation for an incoming URL (omit-prefix aware). */
|
|
284
234
|
export function planLocalePathNavigation(input) {
|
|
285
235
|
const routing = {
|
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
|
-
|
|
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
|
|
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);
|
package/dist/plugin-host.js
CHANGED
|
@@ -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 =
|
|
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 =
|
|
283
|
-
return ` <${tag} ${kw}=
|
|
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 =
|
|
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 =
|
|
310
|
-
return ` <${tag} ${kw}=
|
|
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 =
|
|
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 =
|
|
338
|
-
return ` <${tag} ${kw}=
|
|
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 = {
|
package/dist/route-path.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export declare function isRouteBoundaryStem(stem: string): boolean;
|
|
|
6
6
|
export declare function isRouteGroupDir(seg: string): boolean;
|
|
7
7
|
/**
|
|
8
8
|
* File-route fallback (`pages/home` → `/home`, `pages/index` → `/`).
|
|
9
|
-
*
|
|
9
|
+
* Kept for tooling diagnostics only — production hosts must use Plan `pathPattern`.
|
|
10
10
|
*/
|
|
11
11
|
export declare function filePathPatternFromChunk(chunkId: string): string;
|
|
12
12
|
export type DeploymentPageUnit = {
|
|
@@ -17,7 +17,7 @@ export type DeploymentPageUnit = {
|
|
|
17
17
|
pathPattern?: string;
|
|
18
18
|
routeId?: string;
|
|
19
19
|
};
|
|
20
|
-
/** Canonical Browser HTTP pattern for a page unit. Mini must not read this. */
|
|
20
|
+
/** Canonical Browser HTTP pattern for a page unit. Mini must not read this. Requires Plan `pathPattern`. */
|
|
21
21
|
export declare function unitBrowserPathPattern(unit: DeploymentPageUnit | null | undefined): string;
|
|
22
22
|
export type PathSeg = {
|
|
23
23
|
kind: 'static';
|
package/dist/route-path.js
CHANGED
|
@@ -10,7 +10,7 @@ export function isRouteGroupDir(seg) {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* File-route fallback (`pages/home` → `/home`, `pages/index` → `/`).
|
|
13
|
-
*
|
|
13
|
+
* Kept for tooling diagnostics only — production hosts must use Plan `pathPattern`.
|
|
14
14
|
*/
|
|
15
15
|
export function filePathPatternFromChunk(chunkId) {
|
|
16
16
|
const rel = String(chunkId || '').replace(/^pages\//, '');
|
|
@@ -28,12 +28,14 @@ export function filePathPatternFromChunk(chunkId) {
|
|
|
28
28
|
}
|
|
29
29
|
return segs.length ? `/${segs.join('/')}` : '/';
|
|
30
30
|
}
|
|
31
|
-
/** Canonical Browser HTTP pattern for a page unit. Mini must not read this. */
|
|
31
|
+
/** Canonical Browser HTTP pattern for a page unit. Mini must not read this. Requires Plan `pathPattern`. */
|
|
32
32
|
export function unitBrowserPathPattern(unit) {
|
|
33
33
|
const explicit = String(unit?.pathPattern || '').trim();
|
|
34
|
-
if (explicit)
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
if (!explicit) {
|
|
35
|
+
const chunkId = String(unit?.chunkId || '');
|
|
36
|
+
throw new Error(`unitBrowserPathPattern: page unit ${chunkId || '(unknown)'} missing pathPattern (plan-only host)`);
|
|
37
|
+
}
|
|
38
|
+
return explicit.startsWith('/') ? explicit : `/${explicit}`;
|
|
37
39
|
}
|
|
38
40
|
export function parsePathPattern(pattern) {
|
|
39
41
|
const raw = String(pattern || '').trim();
|
|
@@ -1,19 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* P4 ServerArtifact —
|
|
3
|
-
*
|
|
2
|
+
* P4 ServerArtifact — thin host write of Rust/N-API normalized Plan.
|
|
3
|
+
* Assembly / digests / adapters live in vmz-artifacts via N-API.
|
|
4
4
|
*/
|
|
5
5
|
export declare const SERVER_ARTIFACT_SCHEMA = "vmz.server.artifact.v0";
|
|
6
6
|
export declare const HTTP_CONTRACT_SCHEMA = "vmz.http.contract.v0";
|
|
7
7
|
export declare const SERVER_RUNTIME_ADAPTER_SCHEMA = "vmz.server.runtime_adapter.v0";
|
|
8
|
-
/**
|
|
9
|
-
* @param {string} outDir
|
|
10
|
-
* @param {{
|
|
11
|
-
* profileId?: string | null,
|
|
12
|
-
* assembly?: string | null,
|
|
13
|
-
* serverRuntime?: string | null,
|
|
14
|
-
* packDigest?: string | null,
|
|
15
|
-
* }} [opts]
|
|
16
|
-
*/
|
|
17
8
|
interface ServerArtifactOpts {
|
|
18
9
|
profileId?: string | null;
|
|
19
10
|
assembly?: string | null;
|
|
@@ -42,6 +33,7 @@ interface ServerArtifactBody {
|
|
|
42
33
|
packDigest: string | null;
|
|
43
34
|
adapters: Record<string, unknown>;
|
|
44
35
|
artifactDigest?: string;
|
|
36
|
+
[key: string]: unknown;
|
|
45
37
|
}
|
|
46
38
|
export declare function emitServerArtifact(outDir: string, opts?: ServerArtifactOpts): {
|
|
47
39
|
artifact: ServerArtifactBody;
|
|
@@ -49,47 +41,7 @@ export declare function emitServerArtifact(outDir: string, opts?: ServerArtifact
|
|
|
49
41
|
httpContractDigest: string;
|
|
50
42
|
};
|
|
51
43
|
/**
|
|
52
|
-
*
|
|
53
|
-
* @param {string} adapterId
|
|
44
|
+
* Project a runtime adapter document from a normalized artifact (N-API).
|
|
54
45
|
*/
|
|
55
|
-
export declare function projectServerRuntimeAdapter(artifact:
|
|
56
|
-
host: string;
|
|
57
|
-
invoke: string;
|
|
58
|
-
status: string;
|
|
59
|
-
schema: string;
|
|
60
|
-
adapterId: string;
|
|
61
|
-
artifactDigest: any;
|
|
62
|
-
httpContractDigest: any;
|
|
63
|
-
spaFallback: boolean;
|
|
64
|
-
entry: any;
|
|
65
|
-
publicRouteCount: any;
|
|
66
|
-
internalCapabilityCount: any;
|
|
67
|
-
} | {
|
|
68
|
-
host: string;
|
|
69
|
-
invoke: string;
|
|
70
|
-
status: string;
|
|
71
|
-
note: string;
|
|
72
|
-
schema: string;
|
|
73
|
-
adapterId: string;
|
|
74
|
-
artifactDigest: any;
|
|
75
|
-
httpContractDigest: any;
|
|
76
|
-
spaFallback: boolean;
|
|
77
|
-
entry: any;
|
|
78
|
-
publicRouteCount: any;
|
|
79
|
-
internalCapabilityCount: any;
|
|
80
|
-
} | {
|
|
81
|
-
host: string;
|
|
82
|
-
invoke: string;
|
|
83
|
-
status: string;
|
|
84
|
-
note: string;
|
|
85
|
-
consumes: string[];
|
|
86
|
-
schema: string;
|
|
87
|
-
adapterId: string;
|
|
88
|
-
artifactDigest: any;
|
|
89
|
-
httpContractDigest: any;
|
|
90
|
-
spaFallback: boolean;
|
|
91
|
-
entry: any;
|
|
92
|
-
publicRouteCount: any;
|
|
93
|
-
internalCapabilityCount: any;
|
|
94
|
-
};
|
|
46
|
+
export declare function projectServerRuntimeAdapter(artifact: Record<string, unknown>, adapterId: string): any;
|
|
95
47
|
export {};
|