@vmz/vmz 0.1.10 → 0.1.11

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.
@@ -8,6 +8,7 @@ import { checkDocuments, manifestHasErrors } from './document-check.js';
8
8
  import { resolveDocumentDesignsCss } from './document-designs.js';
9
9
  import { enrichDocumentContent, pageHtmlRel } from './document-enrich.js';
10
10
  import { enrichDocumentEvidence } from './document-evidence.js';
11
+ import { docsRouteNone, loadLocaleCommonMessages, loadLocalesRouting, localeNonePickerScript, readSiteGithubUrl, renderHostChromeTemplate, } from './document-host-chrome.js';
11
12
  import { artifactHrefFromHtml, buildDocumentIslands, buildDocumentSearch, collectFenceBodies, renderIslandShellsHtml, } from './document-interactive.js';
12
13
  import { resolveMarkdownEngine } from './document-markdown.js';
13
14
  import { DOCUMENT_VIEW_SCHEMA } from './document-schema.js';
@@ -22,10 +23,12 @@ export async function buildDocuments(opts) {
22
23
  const outDir = path.resolve(opts.outDir || path.join(projectRoot, 'dist', 'documents'));
23
24
  const strict = Boolean(opts.strict);
24
25
  const manifest = checkDocuments({ projectRoot, strict });
26
+ const routing = loadLocalesRouting(projectRoot) || { strategy: 'prefix' };
25
27
  const engine = await resolveMarkdownEngine({ engines: opts.engines, projectRoot });
26
28
  const enriched = enrichDocumentContent(manifest, {
27
29
  analyzeMarkdown: engine.analyzeMarkdown,
28
30
  projectRoot,
31
+ routing,
29
32
  });
30
33
  manifest.diagnostics = enriched.diagnostics;
31
34
  const evidence = await enrichDocumentEvidence(manifest, {
@@ -60,8 +63,8 @@ export async function buildDocuments(opts) {
60
63
  });
61
64
  manifest.search = search;
62
65
  manifest.islands = islands;
63
- const hostChrome = resolveHostSiteChrome(projectRoot);
64
- const useHostShell = Boolean(hostChrome) && (manifest.mounts || []).some((m) => m.mode === 'integrated');
66
+ const hostChromeRaw = resolveHostSiteChromeRaw(projectRoot);
67
+ const useHostShell = Boolean(hostChromeRaw) && (manifest.mounts || []).some((m) => m.mode === 'integrated');
65
68
  fs.mkdirSync(outDir, { recursive: true });
66
69
  const designs = resolveDocumentDesignsCss(projectRoot);
67
70
  /** @type {string | null} */
@@ -127,7 +130,12 @@ export async function buildDocuments(opts) {
127
130
  htmlRel,
128
131
  searchShellHtml: shells.searchHtml,
129
132
  playgroundShellHtml: shells.playgroundHtml,
130
- hostChrome: useHostShell ? hostChrome : null,
133
+ hostChrome: useHostShell
134
+ ? renderHostChromeForLocale(projectRoot, page.identity.locale, routing, enriched.routeBase, hostChromeRaw)
135
+ : null,
136
+ routing,
137
+ routeBase: enriched.routeBase,
138
+ pageKey: page.identity.pageKey,
131
139
  });
132
140
  fs.writeFileSync(htmlAbs, html, 'utf8');
133
141
  written.push({ route: info.route, htmlPath: htmlRel, viewPath: viewRel });
@@ -159,7 +167,7 @@ export async function buildDocuments(opts) {
159
167
  * No-JS readable static HTML: nav + main landmarks, Island shells without scripts.
160
168
  * Integrated mounts reuse host SiteHeader/SiteFooter templates when present.
161
169
  */
162
- function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, designsHref, htmlRel, searchShellHtml = '', playgroundShellHtml = '', hostChrome = null, }) {
170
+ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, designsHref, htmlRel, searchShellHtml = '', playgroundShellHtml = '', hostChrome = null, routing = { strategy: 'prefix' }, routeBase = '/docs', pageKey = 'index', }) {
163
171
  const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
164
172
  const depth = htmlRel.split('/').length - 1;
165
173
  const prefix = depth > 0 ? '../'.repeat(depth) : './';
@@ -174,7 +182,7 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
174
182
  cssHrefs.push(hostChrome ? `/${designsHref}` : prefix + designsHref);
175
183
  const navItems = nav
176
184
  .map((n) => {
177
- const href = relativeHref(htmlRel, n.href, route);
185
+ const href = routing.strategy === 'none' || routing.strategy === 'domain' ? n.href : relativeHref(htmlRel, n.href, route);
178
186
  const current = n.href === route ? ' aria-current="page"' : '';
179
187
  return ` <li><a href="${esc(href)}"${current}>${esc(n.title)}</a></li>`;
180
188
  })
@@ -210,6 +218,7 @@ ${playgroundShellHtml}
210
218
  </div>
211
219
  ${hostChrome.footer}
212
220
  </div>
221
+ ${routing.strategy === 'none' && hostChrome ? localeNonePickerScript() : ''}
213
222
  `;
214
223
  }
215
224
  else {
@@ -239,7 +248,7 @@ ${playgroundShellHtml}
239
248
  * @param {string} projectRoot
240
249
  * @returns {{ header: string, footer: string } | null}
241
250
  */
242
- function resolveHostSiteChrome(projectRoot) {
251
+ function resolveHostSiteChromeRaw(projectRoot) {
243
252
  const headerPath = path.join(projectRoot, 'src', 'components', 'SiteHeader.vmz');
244
253
  const footerPath = path.join(projectRoot, 'src', 'components', 'SiteFooter.vmz');
245
254
  if (!fs.existsSync(headerPath) || !fs.existsSync(footerPath))
@@ -250,6 +259,28 @@ function resolveHostSiteChrome(projectRoot) {
250
259
  return null;
251
260
  return { header, footer };
252
261
  }
262
+ /**
263
+ * @param {string} projectRoot
264
+ * @param {string} localeId
265
+ * @param {{ strategy?: string }} routing
266
+ * @param {string} routeBase
267
+ * @param {{ header: string, footer: string }} raw
268
+ */
269
+ function renderHostChromeForLocale(projectRoot, localeId, routing, routeBase, raw) {
270
+ const messages = loadLocaleCommonMessages(projectRoot, localeId);
271
+ const githubUrl = readSiteGithubUrl(projectRoot);
272
+ const docsRootHref = routing.strategy === 'none' || routing.strategy === 'domain'
273
+ ? `${routeBase.replace(/\/$/, '')}/`
274
+ : `${routeBase.replace(/\/$/, '')}/${localeId}/`;
275
+ const guideHref = routing.strategy === 'none' || routing.strategy === 'domain'
276
+ ? docsRouteNone(routeBase, 'guide/getting-started')
277
+ : `${docsRootHref}guide/getting-started`;
278
+ const opts = { docsRootHref, guideHref, githubUrl, routing };
279
+ return {
280
+ header: renderHostChromeTemplate(raw.header, localeId, messages, opts),
281
+ footer: renderHostChromeTemplate(raw.footer, localeId, messages, opts),
282
+ };
283
+ }
253
284
  /** @param {string} filePath */
254
285
  function extractVmzTemplateHtml(filePath) {
255
286
  const src = fs.readFileSync(filePath, 'utf8');
@@ -2,8 +2,9 @@
2
2
  * @param {string} routeBase e.g. /docs
3
3
  * @param {string} locale
4
4
  * @param {string} pageKey
5
+ * @param {{ strategy?: string }} [routing]
5
6
  */
6
- export declare function pageRoute(routeBase: any, locale: any, pageKey: any): string;
7
+ export declare function pageRoute(routeBase: any, locale: any, pageKey: any, routing?: {}): string;
7
8
  /**
8
9
  * Static file path relative to out dir (posix).
9
10
  * @param {string} routeBase
@@ -9,10 +9,16 @@ import { DIAG } from './document-schema.js';
9
9
  * @param {string} routeBase e.g. /docs
10
10
  * @param {string} locale
11
11
  * @param {string} pageKey
12
+ * @param {{ strategy?: string }} [routing]
12
13
  */
13
- export function pageRoute(routeBase, locale, pageKey) {
14
+ export function pageRoute(routeBase, locale, pageKey, routing = {}) {
14
15
  const base = String(routeBase || '/').replace(/\/$/, '') || '';
15
16
  const key = pageKey === 'index' ? '' : pageKey.replace(/\\/g, '/');
17
+ const strategy = routing.strategy || 'prefix';
18
+ if (strategy === 'none' || strategy === 'domain') {
19
+ const parts = [base.replace(/^\//, ''), key].filter((p) => p !== '');
20
+ return '/' + (parts.length ? parts.join('/') : '');
21
+ }
16
22
  const parts = [base.replace(/^\//, ''), locale, key].filter((p) => p !== '');
17
23
  return '/' + parts.join('/');
18
24
  }
@@ -35,6 +41,7 @@ export function pageHtmlRel(routeBase, locale, pageKey) {
35
41
  */
36
42
  export function enrichDocumentContent(manifest, ctx) {
37
43
  const routeBase = manifest.mounts?.[0]?.routeBase || '/docs';
44
+ const routing = ctx.routing || { strategy: 'prefix' };
38
45
  /** @type {Map<string, { html: string, headings: any[], links: any[], title: string, route: string, anchors: string[] }>} */
39
46
  const byId = new Map();
40
47
  /** @type {import('./document-schema.js').DocumentDiagnostic[]} */
@@ -45,7 +52,7 @@ export function enrichDocumentContent(manifest, ctx) {
45
52
  const abs = path.isAbsolute(page.sourcePath) ? page.sourcePath : path.join(manifest.root, page.sourcePath);
46
53
  const source = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf8') : '';
47
54
  const analyzed = ctx.analyzeMarkdown(source);
48
- const route = pageRoute(routeBase, page.identity.locale, page.identity.pageKey);
55
+ const route = pageRoute(routeBase, page.identity.locale, page.identity.pageKey, routing);
49
56
  const anchors = analyzed.headings.map((h) => h.id);
50
57
  const title = analyzed.headings.find((h) => h.level === 1)?.text || analyzed.headings[0]?.text || page.identity.pageKey;
51
58
  // Duplicate anchors on page
@@ -61,7 +68,11 @@ export function enrichDocumentContent(manifest, ctx) {
61
68
  }
62
69
  seen.add(id);
63
70
  }
64
- if (routeOwners.has(route)) {
71
+ const owner = `${page.identity.locale}:${page.identity.pageKey}`;
72
+ if (routing.strategy === 'none' || routing.strategy === 'domain') {
73
+ routeOwners.set(route, owner);
74
+ }
75
+ else if (routeOwners.has(route)) {
65
76
  diagnostics.push({
66
77
  code: DIAG.ROUTE_DUPLICATE,
67
78
  severity: 'error',
@@ -70,7 +81,7 @@ export function enrichDocumentContent(manifest, ctx) {
70
81
  });
71
82
  }
72
83
  else {
73
- routeOwners.set(route, `${page.identity.locale}:${page.identity.pageKey}`);
84
+ routeOwners.set(route, owner);
74
85
  }
75
86
  page.route = route;
76
87
  page.anchors = anchors;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @param {string} projectRoot
3
+ * @returns {{ strategy?: string, defaultLocale?: string } | null}
4
+ */
5
+ export declare function loadLocalesRouting(projectRoot: any): any;
6
+ /**
7
+ * @param {string} projectRoot
8
+ * @param {string} localeId
9
+ */
10
+ export declare function loadLocaleCommonMessages(projectRoot: any, localeId: any): any;
11
+ /**
12
+ * @param {string} projectRoot
13
+ */
14
+ export declare function readSiteGithubUrl(projectRoot: any): string;
15
+ /**
16
+ * @param {string} template
17
+ * @param {string} localeId
18
+ * @param {Record<string, string>} messages
19
+ * @param {{ docsRootHref: string, guideHref: string, githubUrl: string, routing?: { strategy?: string } }} opts
20
+ */
21
+ export declare function renderHostChromeTemplate(template: any, localeId: any, messages: any, opts: any): any;
22
+ /** Inline locale preference picker for strategy `none` (Host state, not URL). */
23
+ export declare function localeNonePickerScript(): string;
24
+ /**
25
+ * @param {string} routeBase
26
+ * @param {string} pageKey
27
+ */
28
+ export declare function docsRouteNone(routeBase: any, pageKey: any): string;
@@ -0,0 +1,128 @@
1
+ // @ts-nocheck
2
+ /**
3
+ * Integrated DocumentMount — lower host SiteHeader/SiteFooter .vmz templates to
4
+ * static HTML with locale-resolved copy (LocaleId is Host preference, not URL).
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ /**
9
+ * @param {string} projectRoot
10
+ * @returns {{ strategy?: string, defaultLocale?: string } | null}
11
+ */
12
+ export function loadLocalesRouting(projectRoot) {
13
+ const p = path.join(projectRoot, 'locales', 'locales.json5');
14
+ if (!fs.existsSync(p))
15
+ return null;
16
+ try {
17
+ const raw = fs.readFileSync(p, 'utf8');
18
+ let s = raw.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
19
+ const m = s.match(/routing\s*:\s*\{([\s\S]*?)\}/);
20
+ if (!m)
21
+ return null;
22
+ let block = `{${m[1]}}`;
23
+ block = block.replace(/([,{]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":');
24
+ block = block.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_, inner) => JSON.stringify(inner));
25
+ block = block.replace(/,\s*([}\]])/g, '$1');
26
+ return JSON.parse(block);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ /**
33
+ * @param {string} projectRoot
34
+ * @param {string} localeId
35
+ */
36
+ export function loadLocaleCommonMessages(projectRoot, localeId) {
37
+ const p = path.join(projectRoot, 'locales', localeId, 'common.json5');
38
+ if (!fs.existsSync(p))
39
+ return {};
40
+ try {
41
+ let s = fs.readFileSync(p, 'utf8');
42
+ s = s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
43
+ s = s.replace(/([,{]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:/g, '$1"$2":');
44
+ s = s.replace(/'([^'\\]*(?:\\.[^'\\]*)*)'/g, (_, inner) => JSON.stringify(inner));
45
+ s = s.replace(/,\s*([}\]])/g, '$1');
46
+ return JSON.parse(s);
47
+ }
48
+ catch {
49
+ return {};
50
+ }
51
+ }
52
+ /**
53
+ * @param {string} projectRoot
54
+ */
55
+ export function readSiteGithubUrl(projectRoot) {
56
+ const p = path.join(projectRoot, 'src', 'lib', 'site.ts');
57
+ if (!fs.existsSync(p))
58
+ return 'https://github.com/voml/iris-orm';
59
+ const m = fs.readFileSync(p, 'utf8').match(/githubUrl\s*=\s*["']([^"']+)["']/);
60
+ return m?.[1] || 'https://github.com/voml/iris-orm';
61
+ }
62
+ /**
63
+ * @param {string} template
64
+ * @param {string} localeId
65
+ * @param {Record<string, string>} messages
66
+ * @param {{ docsRootHref: string, guideHref: string, githubUrl: string, routing?: { strategy?: string } }} opts
67
+ */
68
+ export function renderHostChromeTemplate(template, localeId, messages, opts) {
69
+ const esc = (s) => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
70
+ const bindings = {
71
+ brandLabel: messages.brand || 'Iris',
72
+ brandFullLabel: `${messages.brand || 'Iris'} ORM`,
73
+ navHomeLabel: messages.navHome || 'Home',
74
+ navDocsLabel: messages.navDocs || 'Docs',
75
+ navPlaygroundLabel: messages.navPlayground || 'Playground',
76
+ navGithubLabel: messages.navGithub || 'GitHub',
77
+ selectLanguageLabel: messages.selectLanguage || 'Language',
78
+ langZhLabel: messages.langZh || '中文',
79
+ langEnLabel: messages.langEn || 'English',
80
+ footerTagLabel: messages.footerTag || messages.brand || 'Iris',
81
+ footerScopeLabel: messages.footerScopeNote || '',
82
+ footerCopyrightLabel: messages.footerCopyright || '',
83
+ footerGuideLabel: messages.footerGuide || messages.ctaInstall || 'Getting started',
84
+ footerColProductLabel: messages.footerColProduct || 'Product',
85
+ footerColProjectLabel: messages.footerColProject || 'Project',
86
+ ctaInstallLabel: messages.ctaInstall || messages.ctaDocs || 'Docs',
87
+ docsRootHref: opts.docsRootHref,
88
+ guideHref: opts.guideHref,
89
+ githubUrl: opts.githubUrl,
90
+ };
91
+ let html = template;
92
+ html = html.replace(/data-home=\{home \? 'true' : 'false'\}/g, 'data-home="false"');
93
+ html = html.replace(/href=\{docsRootHref\}/g, `href="${esc(bindings.docsRootHref)}"`);
94
+ html = html.replace(/href=\{guideHref\}/g, `href="${esc(bindings.guideHref)}"`);
95
+ html = html.replace(/href=\{githubUrl\}/g, `href="${esc(bindings.githubUrl)}"`);
96
+ for (const [key, val] of Object.entries(bindings)) {
97
+ html = html.replace(new RegExp(`\\{${key}\\}`, 'g'), esc(String(val)));
98
+ }
99
+ html = html.replace(/<Link(\s)/g, '<a class="vmz-ui-link"$1');
100
+ html = html.replace(/<\/Link>/g, '</a>');
101
+ html = html.replace(/<Icon[^>]*\/>/g, '');
102
+ html = html.replace(/<div class="locale-switch"[\s\S]*?<\/div>/g, () => {
103
+ const zh = `<button type="button" class="locale-switch__btn${localeId === 'zh-hans' ? ' is-active' : ''}" data-vmz-locale-pick="zh-hans"${localeId === 'zh-hans' ? ' aria-current="true"' : ''}>${esc(bindings.langZhLabel)}</button>`;
104
+ const en = `<button type="button" class="locale-switch__btn${localeId === 'en-us' ? ' is-active' : ''}" data-vmz-locale-pick="en-us"${localeId === 'en-us' ? ' aria-current="true"' : ''}>${esc(bindings.langEnLabel)}</button>`;
105
+ return `<div class="locale-switch" role="group" aria-label="${esc(bindings.selectLanguageLabel)}">${zh}${en}</div>`;
106
+ });
107
+ html = html.replace(/<Button[\s\S]*?onClick=\{\(\) => this\.switchLocale\('zh-hans'\)\}[\s\S]*?>\s*[\s\S]*?<\/Button>/g, `<button type="button" class="locale-switch__btn${localeId === 'zh-hans' ? ' is-active' : ''}" data-vmz-locale-pick="zh-hans"${localeId === 'zh-hans' ? ' aria-current="true"' : ''}>${esc(bindings.langZhLabel)}</button>`);
108
+ html = html.replace(/<Button[\s\S]*?onClick=\{\(\) => this\.switchLocale\('en-us'\)\}[\s\S]*?>\s*[\s\S]*?<\/Button>/g, `<button type="button" class="locale-switch__btn${localeId === 'en-us' ? ' is-active' : ''}" data-vmz-locale-pick="en-us"${localeId === 'en-us' ? ' aria-current="true"' : ''}>${esc(bindings.langEnLabel)}</button>`);
109
+ html = html.replace(/<Button[\s\S]*?<\/Button>/g, '');
110
+ html = html.replace(/aria-current=\{localeId === '[^']+' \? 'true' : null\}/g, '');
111
+ html = html.replace(/aria-label=\{selectLanguageLabel\}/g, `aria-label="${esc(bindings.selectLanguageLabel)}"`);
112
+ html = html.replace(/label=\{[^}]+\}/g, '');
113
+ return html;
114
+ }
115
+ /** Inline locale preference picker for strategy `none` (Host state, not URL). */
116
+ export function localeNonePickerScript() {
117
+ return `<script>(function(){try{document.querySelectorAll("[data-vmz-locale-pick]").forEach(function(btn){btn.addEventListener("click",function(){var id=btn.getAttribute("data-vmz-locale-pick");if(!id)return;try{localStorage.setItem("vmz.locale",id);}catch(e){}try{document.cookie="vmz.locale="+encodeURIComponent(id)+"; path=/; max-age=31536000; SameSite=Lax";}catch(e){}location.reload();});});}catch(e){}})();</script>`;
118
+ }
119
+ /**
120
+ * @param {string} routeBase
121
+ * @param {string} pageKey
122
+ */
123
+ export function docsRouteNone(routeBase, pageKey) {
124
+ const base = String(routeBase || '/').replace(/\/$/, '') || '';
125
+ const key = pageKey === 'index' ? '' : pageKey.replace(/\\/g, '/');
126
+ const parts = [base.replace(/^\//, ''), key].filter((p) => p !== '');
127
+ return '/' + (parts.length ? parts.join('/') : '');
128
+ }
@@ -7,6 +7,8 @@ import fs from 'node:fs';
7
7
  import path from 'node:path';
8
8
  import { buildDocuments } from './document-build.js';
9
9
  import { resolveDocumentsRoot } from './document-check.js';
10
+ import { loadLocalesRouting } from './document-host-chrome.js';
11
+ import { pageHtmlRel } from './document-enrich.js';
10
12
  import { log } from './log.js';
11
13
  import { requireNativeAddon } from './native-addon.js';
12
14
  /**
@@ -40,7 +42,7 @@ export async function buildIntegratedDocuments(opts) {
40
42
  }
41
43
  return { ok: false, error: 'document diagnostics', pages: 0 };
42
44
  }
43
- writeMountRootRedirects(result.manifest, outDir);
45
+ writeMountRootRedirects(result.manifest, outDir, projectRoot);
44
46
  log.info(`document mount: pages=${result.pages.length} → ${path.relative(process.cwd(), outDir) || '.'}`);
45
47
  return { ok: true, pages: result.pages.length };
46
48
  }
@@ -51,22 +53,34 @@ export async function buildIntegratedDocuments(opts) {
51
53
  }
52
54
  }
53
55
  /**
54
- * Emit `{routeBase}/index.html` → defaultLocale landing (for /d/ and /docs/).
56
+ * Emit `{routeBase}/index.html` for integrated mounts.
57
+ * `routing.strategy: none` → copy default-locale docs index (LocaleId is Host state).
58
+ * prefix strategy → redirect HTML to `{routeBase}/{defaultLocale}/`.
55
59
  * @param {import('./document-schema.js').DocumentManifest} manifest
56
60
  * @param {string} outDir
61
+ * @param {string} projectRoot
57
62
  */
58
- function writeMountRootRedirects(manifest, outDir) {
63
+ function writeMountRootRedirects(manifest, outDir, projectRoot) {
59
64
  const defaultLocale = manifest.defaultLocale || manifest.locales?.[0];
60
65
  if (!defaultLocale)
61
66
  return;
67
+ const routing = loadLocalesRouting(projectRoot) || { strategy: 'prefix' };
62
68
  for (const mount of manifest.mounts || []) {
63
69
  if (!mount?.routeBase || mount.routeBase === '/')
64
70
  continue;
65
71
  const base = String(mount.routeBase).replace(/\/$/, '');
66
- const target = `${base}/${defaultLocale}/`;
67
72
  const relDir = base.replace(/^\//, '');
68
73
  const abs = path.join(outDir, relDir, 'index.html');
69
74
  fs.mkdirSync(path.dirname(abs), { recursive: true });
75
+ if (routing.strategy === 'none' || routing.strategy === 'domain') {
76
+ const srcRel = pageHtmlRel(base, defaultLocale, 'index');
77
+ const srcAbs = path.join(outDir, srcRel);
78
+ if (fs.existsSync(srcAbs)) {
79
+ fs.copyFileSync(srcAbs, abs);
80
+ }
81
+ continue;
82
+ }
83
+ const target = `${base}/${defaultLocale}/`;
70
84
  const native = requireNativeAddon();
71
85
  if (typeof native.generateRedirectHtml !== 'function') {
72
86
  throw new Error('vmz native addon missing generateRedirectHtml — rebuild with `pnpm napi:build`');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vmz/vmz",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "description": "VMZ Node toolchain — N-API workspace session + CLI (publish name @vmz/vmz)",
6
6
  "license": "MIT",
@@ -48,15 +48,15 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@vmz/core": "0.1.10",
52
- "@vmz/plugin": "0.1.10",
53
- "@vmz/protocol": "0.1.10",
51
+ "@vmz/core": "0.1.11",
52
+ "@vmz/plugin": "0.1.11",
53
+ "@vmz/protocol": "0.1.11",
54
54
  "jiti": "^2.6.1",
55
55
  "json5": "^2.2.3"
56
56
  },
57
57
  "peerDependencies": {
58
- "@vmz/plugin-markdown-it": "0.1.10",
59
- "@vmz/test": "0.1.10",
58
+ "@vmz/plugin-markdown-it": "0.1.11",
59
+ "@vmz/test": "0.1.11",
60
60
  "typescript": "^5.8.3"
61
61
  },
62
62
  "peerDependenciesMeta": {
@@ -90,12 +90,12 @@
90
90
  "cli"
91
91
  ],
92
92
  "optionalDependencies": {
93
- "@vmz/vmz-win32-x64": "0.1.10",
94
- "@vmz/vmz-win32-arm64": "0.1.10",
95
- "@vmz/vmz-darwin-x64": "0.1.10",
96
- "@vmz/vmz-darwin-arm64": "0.1.10",
97
- "@vmz/vmz-linux-x64": "0.1.10",
98
- "@vmz/vmz-linux-arm64": "0.1.10"
93
+ "@vmz/vmz-win32-x64": "0.1.11",
94
+ "@vmz/vmz-win32-arm64": "0.1.11",
95
+ "@vmz/vmz-darwin-x64": "0.1.11",
96
+ "@vmz/vmz-darwin-arm64": "0.1.11",
97
+ "@vmz/vmz-linux-x64": "0.1.11",
98
+ "@vmz/vmz-linux-arm64": "0.1.11"
99
99
  },
100
100
  "publishConfig": {
101
101
  "access": "public"