@vmz/vmz 0.1.13 → 0.1.15
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/author-input.d.ts +19 -0
- package/dist/author-input.js +52 -0
- package/dist/build-assemble.js +18 -7
- package/dist/cdn-policy.js +5 -4
- package/dist/cli.js +106 -38
- package/dist/content-addressed-assets.d.ts +23 -2
- package/dist/content-addressed-assets.js +178 -32
- package/dist/delivery-profile.d.ts +30 -2
- package/dist/delivery-profile.js +139 -5
- package/dist/dev-session.d.ts +1 -0
- package/dist/dev-session.js +269 -65
- package/dist/dev-watch-roots.d.ts +80 -0
- package/dist/dev-watch-roots.js +245 -0
- package/dist/document-build.d.ts +4 -3
- package/dist/document-build.js +104 -82
- package/dist/document-check.d.ts +24 -5
- package/dist/document-check.js +64 -101
- package/dist/document-cmd.js +2 -9
- package/dist/document-enrich.d.ts +2 -1
- package/dist/document-enrich.js +15 -4
- package/dist/document-integrate.js +29 -21
- package/dist/document-layout-render.d.ts +20 -0
- package/dist/document-layout-render.js +87 -0
- package/dist/document-routing-config.d.ts +17 -0
- package/dist/document-routing-config.js +31 -0
- package/dist/document-schema.js +8 -1
- package/dist/embedded-packaging.js +2 -1
- package/dist/index.d.ts +18 -2
- package/dist/index.js +63 -4
- package/dist/locale-check.js +81 -183
- package/dist/locale-cmd.js +13 -45
- package/dist/locale-route-emit.d.ts +4 -1
- package/dist/locale-route-emit.js +7 -32
- package/dist/locale-router.js +26 -8
- package/dist/native-addon.d.ts +9 -0
- package/dist/native-addon.js +84 -0
- package/dist/pack.js +3 -2
- package/dist/pretty-json.d.ts +19 -0
- package/dist/pretty-json.js +43 -0
- package/dist/production-observability.js +3 -2
- package/dist/production-test-pack.js +4 -3
- package/dist/public-static-assets.d.ts +24 -0
- package/dist/public-static-assets.js +140 -0
- package/dist/release-pack.js +5 -18
- package/dist/route-path.d.ts +35 -0
- package/dist/route-path.js +77 -0
- package/dist/server-artifact.js +4 -3
- package/dist/site-delivery.js +4 -3
- package/dist/site-favicon.d.ts +31 -0
- package/dist/site-favicon.js +140 -0
- package/dist/static-emit.d.ts +21 -0
- package/dist/static-emit.js +134 -97
- package/dist/test-cmd.js +2 -1
- package/dist/wechat-packaging.d.ts +22 -0
- package/dist/wechat-packaging.js +59 -0
- package/package.json +13 -14
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* Dev watch helpers: coalesce multi-file bursts without dropping dirty set,
|
|
4
|
+
* and derive extra watch roots from the compile graph (deployment unit sources).
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { diffFingerprints, fileFingerprintMap } from './watch-diff.js';
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ changed: string[], deleted: string[] }} DirtySet
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Merge dirty sets: later change cancels delete and vice versa.
|
|
14
|
+
* @param {DirtySet} a
|
|
15
|
+
* @param {DirtySet} b
|
|
16
|
+
* @returns {DirtySet}
|
|
17
|
+
*/
|
|
18
|
+
export function mergeDirtySets(a, b) {
|
|
19
|
+
const changed = new Set(a?.changed || []);
|
|
20
|
+
const deleted = new Set(a?.deleted || []);
|
|
21
|
+
for (const f of b?.changed || []) {
|
|
22
|
+
changed.add(f);
|
|
23
|
+
deleted.delete(f);
|
|
24
|
+
}
|
|
25
|
+
for (const f of b?.deleted || []) {
|
|
26
|
+
deleted.add(f);
|
|
27
|
+
changed.delete(f);
|
|
28
|
+
}
|
|
29
|
+
return { changed: [...changed], deleted: [...deleted] };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wait until `root` stops changing; return the **accumulated** dirty set since `initial`.
|
|
33
|
+
* Updates `fingerprints` for `root` as it polls. Does **not** discard `initial`.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} root
|
|
36
|
+
* @param {Map<string, Map<string, string>>} fingerprints
|
|
37
|
+
* @param {DirtySet} initial
|
|
38
|
+
* @param {{ sleep?: (ms: number) => Promise<void>, maxRounds?: number, settleMs?: number }} [opts]
|
|
39
|
+
* @returns {Promise<DirtySet>}
|
|
40
|
+
*/
|
|
41
|
+
export async function coalesceRootBurst(root, fingerprints, initial, opts = {}) {
|
|
42
|
+
const sleepFn = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
43
|
+
const maxRounds = opts.maxRounds ?? 20;
|
|
44
|
+
const settleMs = opts.settleMs ?? 220;
|
|
45
|
+
let accumulated = {
|
|
46
|
+
changed: [...(initial?.changed || [])],
|
|
47
|
+
deleted: [...(initial?.deleted || [])],
|
|
48
|
+
};
|
|
49
|
+
let guard = 0;
|
|
50
|
+
while (guard++ < maxRounds) {
|
|
51
|
+
await sleepFn(settleMs);
|
|
52
|
+
const prev = fingerprints.get(root) || new Map();
|
|
53
|
+
const next = fileFingerprintMap(root);
|
|
54
|
+
const diff = diffFingerprints(prev, next);
|
|
55
|
+
fingerprints.set(root, next);
|
|
56
|
+
if (!diff.changed.length && !diff.deleted.length)
|
|
57
|
+
break;
|
|
58
|
+
accumulated = mergeDirtySets(accumulated, diff);
|
|
59
|
+
}
|
|
60
|
+
return accumulated;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Walk up from a file to find a package.json directory.
|
|
64
|
+
* @param {string} file
|
|
65
|
+
* @returns {string | null}
|
|
66
|
+
*/
|
|
67
|
+
export function findPackageRoot(file) {
|
|
68
|
+
let dir = path.dirname(path.resolve(file));
|
|
69
|
+
for (let i = 0; i < 24; i++) {
|
|
70
|
+
if (existsSync(path.join(dir, 'package.json')))
|
|
71
|
+
return dir;
|
|
72
|
+
const parent = path.dirname(dir);
|
|
73
|
+
if (parent === dir)
|
|
74
|
+
break;
|
|
75
|
+
dir = parent;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Prefer package/src when present; otherwise the directory containing the source file.
|
|
81
|
+
* @param {string} sourceFile
|
|
82
|
+
* @returns {string | null}
|
|
83
|
+
*/
|
|
84
|
+
export function watchRootForSourceFile(sourceFile) {
|
|
85
|
+
const abs = path.resolve(sourceFile);
|
|
86
|
+
if (!existsSync(abs))
|
|
87
|
+
return null;
|
|
88
|
+
const pkg = findPackageRoot(abs);
|
|
89
|
+
if (pkg) {
|
|
90
|
+
const src = path.join(pkg, 'src');
|
|
91
|
+
if (existsSync(src))
|
|
92
|
+
return src;
|
|
93
|
+
return pkg;
|
|
94
|
+
}
|
|
95
|
+
return path.dirname(abs);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Absolute roots for workspace / file / link deps that have a src tree.
|
|
99
|
+
* @param {string} project
|
|
100
|
+
* @returns {string[]}
|
|
101
|
+
*/
|
|
102
|
+
export function localLinkDependencyRoots(project) {
|
|
103
|
+
const pkgPath = path.join(path.resolve(project), 'package.json');
|
|
104
|
+
if (!existsSync(pkgPath))
|
|
105
|
+
return [];
|
|
106
|
+
/** @type {string[]} */
|
|
107
|
+
const roots = [];
|
|
108
|
+
try {
|
|
109
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
110
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
111
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
112
|
+
if (typeof spec !== 'string')
|
|
113
|
+
continue;
|
|
114
|
+
let target = null;
|
|
115
|
+
if (spec.startsWith('workspace:')) {
|
|
116
|
+
// Resolve via node_modules (pnpm links workspace packages there).
|
|
117
|
+
const nm = path.join(path.resolve(project), 'node_modules', ...name.split('/'));
|
|
118
|
+
if (existsSync(path.join(nm, 'package.json')))
|
|
119
|
+
target = nm;
|
|
120
|
+
}
|
|
121
|
+
else if (spec.startsWith('file:') || spec.startsWith('link:')) {
|
|
122
|
+
const rel = spec.replace(/^(file|link):/, '');
|
|
123
|
+
target = path.resolve(project, rel);
|
|
124
|
+
}
|
|
125
|
+
if (!target)
|
|
126
|
+
continue;
|
|
127
|
+
try {
|
|
128
|
+
target = realpathSync(target);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
/* keep as-is */
|
|
132
|
+
}
|
|
133
|
+
const src = path.join(target, 'src');
|
|
134
|
+
if (existsSync(src))
|
|
135
|
+
roots.push(src);
|
|
136
|
+
else if (existsSync(target))
|
|
137
|
+
roots.push(target);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* ignore */
|
|
142
|
+
}
|
|
143
|
+
return roots;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Collect watch roots: project src/locales/documents + compile-graph external sources
|
|
147
|
+
* + local link/workspace package roots. Never adds a bare registry node_modules tree.
|
|
148
|
+
*
|
|
149
|
+
* @param {{ project: string, outDir: string }} opts
|
|
150
|
+
* @returns {{ roots: string[], dependencyRoots: string[], applicationRoots: string[] }}
|
|
151
|
+
*/
|
|
152
|
+
export function collectDevWatchRoots(opts) {
|
|
153
|
+
const project = path.resolve(opts.project);
|
|
154
|
+
const outDir = path.resolve(opts.outDir);
|
|
155
|
+
const src = path.join(project, 'src');
|
|
156
|
+
const docsRoot = path.join(project, 'documents');
|
|
157
|
+
const localesRoot = path.join(project, 'locales');
|
|
158
|
+
const designsRoot = path.join(project, 'designs');
|
|
159
|
+
/** @type {string[]} */
|
|
160
|
+
const applicationRoots = [];
|
|
161
|
+
if (existsSync(src))
|
|
162
|
+
applicationRoots.push(src);
|
|
163
|
+
if (existsSync(docsRoot))
|
|
164
|
+
applicationRoots.push(docsRoot);
|
|
165
|
+
if (existsSync(localesRoot))
|
|
166
|
+
applicationRoots.push(localesRoot);
|
|
167
|
+
if (existsSync(designsRoot))
|
|
168
|
+
applicationRoots.push(designsRoot);
|
|
169
|
+
/** @type {Set<string>} */
|
|
170
|
+
const depSet = new Set();
|
|
171
|
+
const depJson = path.join(outDir, 'vmz-deployment.json');
|
|
172
|
+
if (existsSync(depJson)) {
|
|
173
|
+
try {
|
|
174
|
+
const dep = JSON.parse(readFileSync(depJson, 'utf8'));
|
|
175
|
+
for (const unit of dep.units || []) {
|
|
176
|
+
const source = unit?.source;
|
|
177
|
+
if (typeof source !== 'string' || !source)
|
|
178
|
+
continue;
|
|
179
|
+
let abs = path.resolve(source);
|
|
180
|
+
try {
|
|
181
|
+
abs = realpathSync(abs);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
/* keep */
|
|
185
|
+
}
|
|
186
|
+
const underProject = abs === project || abs.startsWith(project + path.sep) || abs.startsWith(project + '/');
|
|
187
|
+
if (underProject)
|
|
188
|
+
continue;
|
|
189
|
+
const root = watchRootForSourceFile(abs);
|
|
190
|
+
if (root)
|
|
191
|
+
depSet.add(path.resolve(root));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* ignore corrupt deployment */
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
for (const r of localLinkDependencyRoots(project)) {
|
|
199
|
+
depSet.add(path.resolve(r));
|
|
200
|
+
}
|
|
201
|
+
// Drop dependency roots that are already under an application root.
|
|
202
|
+
const dependencyRoots = [...depSet].filter((r) => {
|
|
203
|
+
return !applicationRoots.some((app) => r === app || r.startsWith(app + path.sep) || r.startsWith(app + '/'));
|
|
204
|
+
});
|
|
205
|
+
const roots = [...applicationRoots];
|
|
206
|
+
for (const r of dependencyRoots) {
|
|
207
|
+
if (!roots.includes(r))
|
|
208
|
+
roots.push(r);
|
|
209
|
+
}
|
|
210
|
+
return { roots, dependencyRoots, applicationRoots };
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Classify which watch bucket a root belongs to.
|
|
214
|
+
* @param {string} root
|
|
215
|
+
* @param {{
|
|
216
|
+
* src: string,
|
|
217
|
+
* docsRoot: string,
|
|
218
|
+
* localesRoot: string,
|
|
219
|
+
* designsRoot: string,
|
|
220
|
+
* dependencyRoots: string[],
|
|
221
|
+
* }} ctx
|
|
222
|
+
* @returns {'src' | 'locales' | 'docs' | 'designs' | 'dep' | 'other'}
|
|
223
|
+
*/
|
|
224
|
+
export function classifyWatchRoot(root, ctx) {
|
|
225
|
+
if (root === ctx.src)
|
|
226
|
+
return 'src';
|
|
227
|
+
if (root === ctx.localesRoot)
|
|
228
|
+
return 'locales';
|
|
229
|
+
if (root === ctx.docsRoot)
|
|
230
|
+
return 'docs';
|
|
231
|
+
if (root === ctx.designsRoot)
|
|
232
|
+
return 'designs';
|
|
233
|
+
if ((ctx.dependencyRoots || []).includes(root))
|
|
234
|
+
return 'dep';
|
|
235
|
+
return 'other';
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Classify whether a changed file lives under a dependency watch root (not app src).
|
|
239
|
+
* @param {string} file
|
|
240
|
+
* @param {string[]} dependencyRoots
|
|
241
|
+
*/
|
|
242
|
+
export function isDependencyPath(file, dependencyRoots) {
|
|
243
|
+
const abs = path.resolve(file);
|
|
244
|
+
return (dependencyRoots || []).some((r) => abs === r || abs.startsWith(r + path.sep) || abs.startsWith(r + '/'));
|
|
245
|
+
}
|
package/dist/document-build.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {{ projectRoot: string, outDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
2
|
+
* @param {{ projectRoot: string, outDir?: string, appDistDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
3
3
|
*/
|
|
4
4
|
export declare function buildDocuments(opts: any): Promise<{
|
|
5
5
|
ok: boolean;
|
|
@@ -8,7 +8,7 @@ export declare function buildDocuments(opts: any): Promise<{
|
|
|
8
8
|
root: string;
|
|
9
9
|
defaultLocale: any;
|
|
10
10
|
locales: any[];
|
|
11
|
-
localeLabels:
|
|
11
|
+
localeLabels: any;
|
|
12
12
|
collections: any[];
|
|
13
13
|
mounts: any[];
|
|
14
14
|
pages: {
|
|
@@ -57,6 +57,7 @@ export declare function buildDocuments(opts: any): Promise<{
|
|
|
57
57
|
outDir: string;
|
|
58
58
|
designs: string;
|
|
59
59
|
designsCss: any;
|
|
60
|
+
hostShell: string;
|
|
60
61
|
pages: any[];
|
|
61
62
|
evidence: string;
|
|
62
63
|
search: string;
|
|
@@ -65,7 +66,7 @@ export declare function buildDocuments(opts: any): Promise<{
|
|
|
65
66
|
root: string;
|
|
66
67
|
defaultLocale: any;
|
|
67
68
|
locales: any[];
|
|
68
|
-
localeLabels:
|
|
69
|
+
localeLabels: any;
|
|
69
70
|
collections: any[];
|
|
70
71
|
mounts: any[];
|
|
71
72
|
pages: {
|
package/dist/document-build.js
CHANGED
|
@@ -9,21 +9,27 @@ import { resolveDocumentDesignsCss } from './document-designs.js';
|
|
|
9
9
|
import { enrichDocumentContent, pageHtmlRel } from './document-enrich.js';
|
|
10
10
|
import { enrichDocumentEvidence } from './document-evidence.js';
|
|
11
11
|
import { artifactHrefFromHtml, buildDocumentIslands, buildDocumentSearch, collectFenceBodies, renderIslandShellsHtml, } from './document-interactive.js';
|
|
12
|
+
import { assertIntegratedDistReady, renderCompiledDocumentLayout } from './document-layout-render.js';
|
|
12
13
|
import { resolveMarkdownEngine } from './document-markdown.js';
|
|
14
|
+
import { loadLocalesRouting } from './document-routing-config.js';
|
|
13
15
|
import { DOCUMENT_VIEW_SCHEMA } from './document-schema.js';
|
|
14
16
|
import { createWorkspace } from './index.js';
|
|
17
|
+
import { requireNativeAddon } from './native-addon.js';
|
|
18
|
+
import { writePrettyJsonFile } from './pretty-json.js';
|
|
15
19
|
/**
|
|
16
|
-
* @param {{ projectRoot: string, outDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
20
|
+
* @param {{ projectRoot: string, outDir?: string, appDistDir?: string, strict?: boolean, engines?: { markdown?: string } }} opts
|
|
17
21
|
*/
|
|
18
22
|
export async function buildDocuments(opts) {
|
|
19
23
|
const projectRoot = path.resolve(opts.projectRoot);
|
|
20
24
|
const outDir = path.resolve(opts.outDir || path.join(projectRoot, 'dist', 'documents'));
|
|
21
25
|
const strict = Boolean(opts.strict);
|
|
22
26
|
const manifest = checkDocuments({ projectRoot, strict });
|
|
27
|
+
const routing = loadLocalesRouting(projectRoot) || { strategy: 'prefix' };
|
|
23
28
|
const engine = await resolveMarkdownEngine({ engines: opts.engines, projectRoot });
|
|
24
29
|
const enriched = enrichDocumentContent(manifest, {
|
|
25
30
|
analyzeMarkdown: engine.analyzeMarkdown,
|
|
26
31
|
projectRoot,
|
|
32
|
+
routing,
|
|
27
33
|
});
|
|
28
34
|
manifest.diagnostics = enriched.diagnostics;
|
|
29
35
|
const evidence = await enrichDocumentEvidence(manifest, {
|
|
@@ -58,8 +64,12 @@ export async function buildDocuments(opts) {
|
|
|
58
64
|
});
|
|
59
65
|
manifest.search = search;
|
|
60
66
|
manifest.islands = islands;
|
|
61
|
-
const
|
|
62
|
-
const
|
|
67
|
+
const integratedMount = (manifest.mounts || []).some((m) => m.mode === 'integrated');
|
|
68
|
+
const appDistDir = resolveAppDistDir(opts, outDir);
|
|
69
|
+
const useCompiledShell = Boolean(integratedMount && appDistDir);
|
|
70
|
+
if (integratedMount && appDistDir) {
|
|
71
|
+
assertIntegratedDistReady(appDistDir);
|
|
72
|
+
}
|
|
63
73
|
fs.mkdirSync(outDir, { recursive: true });
|
|
64
74
|
const designs = resolveDocumentDesignsCss(projectRoot);
|
|
65
75
|
/** @type {string | null} */
|
|
@@ -90,6 +100,20 @@ export async function buildDocuments(opts) {
|
|
|
90
100
|
pageKey: page.identity.pageKey,
|
|
91
101
|
locale: page.identity.locale,
|
|
92
102
|
});
|
|
103
|
+
const slotHtml = buildDocumentSlotHtml({
|
|
104
|
+
nav,
|
|
105
|
+
bodyHtml: info.html,
|
|
106
|
+
headings: info.headings,
|
|
107
|
+
htmlRel,
|
|
108
|
+
route: info.route,
|
|
109
|
+
routing,
|
|
110
|
+
searchShellHtml: shells.searchHtml,
|
|
111
|
+
playgroundShellHtml: shells.playgroundHtml,
|
|
112
|
+
});
|
|
113
|
+
let compiledLayoutHtml = null;
|
|
114
|
+
if (useCompiledShell) {
|
|
115
|
+
compiledLayoutHtml = await renderCompiledDocumentLayout(appDistDir, page.identity.locale, slotHtml);
|
|
116
|
+
}
|
|
93
117
|
const view = {
|
|
94
118
|
schema: DOCUMENT_VIEW_SCHEMA,
|
|
95
119
|
pageKey: page.identity.pageKey,
|
|
@@ -103,7 +127,7 @@ export async function buildDocuments(opts) {
|
|
|
103
127
|
designsCss: designsHref,
|
|
104
128
|
noJsReadable: true,
|
|
105
129
|
hydrate: 'island-only',
|
|
106
|
-
hostShell:
|
|
130
|
+
hostShell: useCompiledShell ? 'compiled-layout' : false,
|
|
107
131
|
islands: ['DocumentSearch'].concat((islands.islands || [])
|
|
108
132
|
.filter((isl) => isl.kind === 'playground' &&
|
|
109
133
|
isl.fence?.locale === page.identity.locale &&
|
|
@@ -113,7 +137,7 @@ export async function buildDocuments(opts) {
|
|
|
113
137
|
const viewRel = path.posix.join('views', page.identity.locale, `${page.identity.pageKey === 'index' ? 'index' : page.identity.pageKey}.view.json`);
|
|
114
138
|
const viewAbs = path.join(outDir, viewRel);
|
|
115
139
|
fs.mkdirSync(path.dirname(viewAbs), { recursive: true });
|
|
116
|
-
|
|
140
|
+
writePrettyJsonFile(viewAbs, view);
|
|
117
141
|
const html = renderStaticHtml({
|
|
118
142
|
title: info.title,
|
|
119
143
|
locale: page.identity.locale,
|
|
@@ -125,7 +149,9 @@ export async function buildDocuments(opts) {
|
|
|
125
149
|
htmlRel,
|
|
126
150
|
searchShellHtml: shells.searchHtml,
|
|
127
151
|
playgroundShellHtml: shells.playgroundHtml,
|
|
128
|
-
|
|
152
|
+
routing,
|
|
153
|
+
compiledLayoutHtml,
|
|
154
|
+
useCompiledShell,
|
|
129
155
|
});
|
|
130
156
|
fs.writeFileSync(htmlAbs, html, 'utf8');
|
|
131
157
|
written.push({ route: info.route, htmlPath: htmlRel, viewPath: viewRel });
|
|
@@ -141,39 +167,38 @@ export async function buildDocuments(opts) {
|
|
|
141
167
|
outDir: path.relative(projectRoot, outDir).replace(/\\/g, '/') || '.',
|
|
142
168
|
designs: designs.source,
|
|
143
169
|
designsCss: designsHref,
|
|
170
|
+
hostShell: useCompiledShell ? 'compiled-layout' : 'standalone',
|
|
144
171
|
pages: written,
|
|
145
172
|
evidence: 'document.evidence.json',
|
|
146
173
|
search: 'document.search.json',
|
|
147
174
|
islands: 'document.islands.json',
|
|
148
175
|
},
|
|
149
176
|
};
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
177
|
+
writePrettyJsonFile(path.join(outDir, 'document.manifest.json'), manifestOut);
|
|
178
|
+
writePrettyJsonFile(path.join(outDir, 'document.evidence.json'), evidence.evidence);
|
|
179
|
+
writePrettyJsonFile(path.join(outDir, 'document.search.json'), search);
|
|
180
|
+
writePrettyJsonFile(path.join(outDir, 'document.islands.json'), islands);
|
|
154
181
|
return { ok: true, manifest: manifestOut, outDir, pages: written, search, islands };
|
|
155
182
|
}
|
|
156
183
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
184
|
+
* @param {{ appDistDir?: string }} opts
|
|
185
|
+
* @param {string} outDir
|
|
159
186
|
*/
|
|
160
|
-
function
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
/** @type {string[]} */
|
|
165
|
-
const cssHrefs = [];
|
|
166
|
-
if (hostChrome) {
|
|
167
|
-
// Integrated documents are served with pretty directory URLs. Root
|
|
168
|
-
// absolute assets remain correct for both emitted files and rewrites.
|
|
169
|
-
cssHrefs.push('/vmz.css');
|
|
187
|
+
function resolveAppDistDir(opts, outDir) {
|
|
188
|
+
if (opts.appDistDir) {
|
|
189
|
+
const p = path.resolve(opts.appDistDir);
|
|
190
|
+
return fs.existsSync(path.join(p, 'vmz-dom.js')) ? p : null;
|
|
170
191
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
192
|
+
return fs.existsSync(path.join(outDir, 'vmz-dom.js')) ? outDir : null;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Document main column + sidebar (injected into DocumentLayout slot).
|
|
196
|
+
*/
|
|
197
|
+
function buildDocumentSlotHtml({ nav, bodyHtml, headings, htmlRel, route, routing, searchShellHtml = '', playgroundShellHtml = '' }) {
|
|
198
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
174
199
|
const navItems = nav
|
|
175
200
|
.map((n) => {
|
|
176
|
-
const href = relativeHref(htmlRel, n.href, route);
|
|
201
|
+
const href = routing.strategy === 'none' || routing.strategy === 'domain' ? n.href : relativeHref(htmlRel, n.href, route);
|
|
177
202
|
const current = n.href === route ? ' aria-current="page"' : '';
|
|
178
203
|
return ` <li><a href="${esc(href)}"${current}>${esc(n.title)}</a></li>`;
|
|
179
204
|
})
|
|
@@ -188,21 +213,7 @@ function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, desig
|
|
|
188
213
|
${navItems}
|
|
189
214
|
</ul>
|
|
190
215
|
</nav>`;
|
|
191
|
-
|
|
192
|
-
const header = hostChrome.header.replace(/(<a\s+href="\/d\/?")([^>]*>文档<\/a>)/, '$1 aria-current="page"$2');
|
|
193
|
-
return `<!DOCTYPE html>
|
|
194
|
-
<html lang="${esc(locale)}">
|
|
195
|
-
<head>
|
|
196
|
-
<meta charset="utf-8" />
|
|
197
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
198
|
-
<title>${esc(title)}</title>
|
|
199
|
-
${cssLink}</head>
|
|
200
|
-
<body data-vmz-hydrate="island-only">
|
|
201
|
-
<div class="site site--docs">
|
|
202
|
-
<a class="skip-link" href="#main">Skip to content</a>
|
|
203
|
-
${header}
|
|
204
|
-
<div class="doc-body">
|
|
205
|
-
<aside class="doc-sidebar">
|
|
216
|
+
return ` <aside class="doc-sidebar">
|
|
206
217
|
${docsNav}
|
|
207
218
|
${searchShellHtml}
|
|
208
219
|
</aside>
|
|
@@ -211,54 +222,65 @@ ${searchShellHtml}
|
|
|
211
222
|
${bodyHtml}
|
|
212
223
|
${playgroundShellHtml}
|
|
213
224
|
</main>
|
|
214
|
-
</div
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
225
|
+
</div>`;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* No-JS readable static HTML: nav + main landmarks, Island shells without scripts.
|
|
229
|
+
* Integrated mounts wrap content in compiled DocumentLayout (SiteHeader/SiteFooter SSR).
|
|
230
|
+
*/
|
|
231
|
+
function renderStaticHtml({ title, locale, route, nav, bodyHtml, headings, designsHref, htmlRel, searchShellHtml = '', playgroundShellHtml = '', routing = { strategy: 'prefix' }, compiledLayoutHtml, useCompiledShell = false, }) {
|
|
232
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
233
|
+
const depth = htmlRel.split('/').length - 1;
|
|
234
|
+
const prefix = depth > 0 ? '../'.repeat(depth) : './';
|
|
235
|
+
/** @type {string[]} */
|
|
236
|
+
const cssHrefs = [];
|
|
237
|
+
if (useCompiledShell) {
|
|
238
|
+
cssHrefs.push('/vmz.css');
|
|
221
239
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
240
|
+
if (designsHref)
|
|
241
|
+
cssHrefs.push(useCompiledShell ? `/${designsHref}` : prefix + designsHref);
|
|
242
|
+
/** @type {string} */
|
|
243
|
+
let bodyInner;
|
|
244
|
+
if (useCompiledShell) {
|
|
245
|
+
bodyInner = compiledLayoutHtml;
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
const navItems = nav
|
|
249
|
+
.map((n) => {
|
|
250
|
+
const href = routing.strategy === 'none' || routing.strategy === 'domain' ? n.href : relativeHref(htmlRel, n.href, route);
|
|
251
|
+
const current = n.href === route ? ' aria-current="page"' : '';
|
|
252
|
+
return ` <li><a href="${esc(href)}"${current}>${esc(n.title)}</a></li>`;
|
|
253
|
+
})
|
|
254
|
+
.join('\n');
|
|
255
|
+
const toc = headings.length > 1
|
|
256
|
+
? `<nav aria-label="On this page" class="toc">\n <ol>\n${headings
|
|
257
|
+
.map((h) => ` <li class="h${h.level}"><a href="#${esc(h.id)}">${esc(h.text)}</a></li>`)
|
|
258
|
+
.join('\n')}\n </ol>\n </nav>\n`
|
|
259
|
+
: '';
|
|
260
|
+
bodyInner = ` <a class="skip-link" href="#main">Skip to content</a>
|
|
261
|
+
<nav aria-label="Documents" class="doc-subnav">
|
|
262
|
+
<ul>
|
|
263
|
+
${navItems}
|
|
264
|
+
</ul>
|
|
265
|
+
</nav>
|
|
232
266
|
${searchShellHtml}
|
|
233
267
|
${toc}<main id="main">
|
|
234
268
|
${bodyHtml}
|
|
235
269
|
${playgroundShellHtml}
|
|
236
270
|
</main>
|
|
237
|
-
</body>
|
|
238
|
-
</html>
|
|
239
271
|
`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const footer = extractVmzTemplateHtml(footerPath);
|
|
253
|
-
if (!header || !footer)
|
|
254
|
-
return null;
|
|
255
|
-
return { header, footer };
|
|
256
|
-
}
|
|
257
|
-
/** @param {string} filePath */
|
|
258
|
-
function extractVmzTemplateHtml(filePath) {
|
|
259
|
-
const src = fs.readFileSync(filePath, 'utf8');
|
|
260
|
-
const m = src.match(/<template>([\s\S]*?)<\/template>/);
|
|
261
|
-
return m ? m[1].trim() : '';
|
|
272
|
+
}
|
|
273
|
+
const native = requireNativeAddon();
|
|
274
|
+
if (typeof native.generateHtmlShell !== 'function') {
|
|
275
|
+
throw new Error('vmz native addon missing generateHtmlShell — rebuild with `pnpm napi:build`');
|
|
276
|
+
}
|
|
277
|
+
return native.generateHtmlShell({
|
|
278
|
+
title,
|
|
279
|
+
lang: locale,
|
|
280
|
+
cssHrefs,
|
|
281
|
+
bodyHtml: bodyInner,
|
|
282
|
+
bodyAttrs: ['data-vmz-hydrate', 'island-only'],
|
|
283
|
+
});
|
|
262
284
|
}
|
|
263
285
|
function relativeHref(fromHtmlRel, toRoute, _fromRoute) {
|
|
264
286
|
const toParts = String(toRoute).replace(/^\//, '').split('/').filter(Boolean);
|
package/dist/document-check.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build DocumentManifest + run / --strict checks.
|
|
3
|
+
*
|
|
4
|
+
* Author documents config is parsed by Rust DocumentRoutePlan; TS only
|
|
5
|
+
* merges filesystem scan (pageKeys) and coverage diagnostics.
|
|
3
6
|
*/
|
|
4
7
|
/**
|
|
5
8
|
* Resolve project documents root.
|
|
@@ -7,14 +10,30 @@
|
|
|
7
10
|
*/
|
|
8
11
|
export declare function resolveDocumentsRoot(projectRoot: any): string;
|
|
9
12
|
/**
|
|
10
|
-
*
|
|
13
|
+
* Load normalized DocumentRoutePlan from Rust (author JSON5/JSON/declaration).
|
|
14
|
+
* @param {string} projectRoot
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadDocumentsRoutePlan(projectRoot: any): any;
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated Prefer loadDocumentsRoutePlan(projectRoot). Kept for call sites that
|
|
19
|
+
* only need the plan-shaped config fields.
|
|
11
20
|
* @param {string} documentsRoot
|
|
12
|
-
* @returns {{ config: Record<string, any> | null, diagnostics: any[], configPath: string | null }}
|
|
13
21
|
*/
|
|
14
22
|
export declare function loadDocumentsConfig(documentsRoot: any): {
|
|
15
|
-
config:
|
|
16
|
-
|
|
23
|
+
config: {
|
|
24
|
+
defaultLocale: any;
|
|
25
|
+
locales: {
|
|
26
|
+
[k: string]: {
|
|
27
|
+
label: unknown;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
collections: {
|
|
31
|
+
[k: string]: any;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
diagnostics: any;
|
|
17
35
|
configPath: string;
|
|
36
|
+
plan: any;
|
|
18
37
|
};
|
|
19
38
|
/**
|
|
20
39
|
* @param {object} opts
|
|
@@ -27,7 +46,7 @@ export declare function checkDocuments(opts: any): {
|
|
|
27
46
|
root: string;
|
|
28
47
|
defaultLocale: any;
|
|
29
48
|
locales: any[];
|
|
30
|
-
localeLabels:
|
|
49
|
+
localeLabels: any;
|
|
31
50
|
collections: any[];
|
|
32
51
|
mounts: any[];
|
|
33
52
|
pages: {
|