@dogsbay/format-vitepress 0.2.0-beta.131 → 0.2.0-beta.133
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/README.md +9 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +15 -62
- package/dist/config-node.d.ts +20 -0
- package/dist/config-node.js +51 -0
- package/dist/config.d.ts +14 -20
- package/dist/config.js +7 -51
- package/dist/import.d.ts +28 -0
- package/dist/import.js +76 -0
- package/dist/index.d.ts +10 -5
- package/dist/index.js +9 -4
- package/dist/loaders/folder.d.ts +31 -0
- package/dist/loaders/folder.js +90 -0
- package/dist/loaders/load.d.ts +49 -21
- package/dist/loaders/load.js +160 -235
- package/dist/migrate.d.ts +40 -0
- package/dist/migrate.js +83 -0
- package/dist/parse.js +5 -1
- package/dist/translate.d.ts +15 -1
- package/dist/translate.js +120 -55
- package/package.json +12 -4
- package/dist/literal.d.ts +0 -54
- package/dist/literal.js +0 -844
package/README.md
CHANGED
|
@@ -32,6 +32,15 @@ import { importVitepress } from "@dogsbay/format-vitepress/cli";
|
|
|
32
32
|
const { pages, nav } = await importVitepress("./docs", { hrefPrefix: "/docs" });
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
In a browser (no Node, nothing from the repository is run), a project as a map of files to
|
|
36
|
+
the Dogsbay project `dogsbay convert --to dogsbay-md` writes, under `content/`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { migrate } from "@dogsbay/format-vitepress/migrate";
|
|
40
|
+
|
|
41
|
+
const { files, ledger } = await migrate({ files: new Map([[".vitepress/config.ts", "…"], ["index.md", "# Home"]]) });
|
|
42
|
+
```
|
|
43
|
+
|
|
35
44
|
Or the pieces:
|
|
36
45
|
|
|
37
46
|
```ts
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FormatPlugin for VitePress as a source format.
|
|
3
|
+
*
|
|
4
|
+
* The project on disk is read into a corpus (`loaders/folder.ts`), then imported by
|
|
5
|
+
* `importVitepressCorpus`, the import the browser runs too (`./migrate`). This file adds
|
|
6
|
+
* the disk and the console summary.
|
|
7
|
+
*
|
|
8
|
+
* VitePress content is `.md`, so this plugin MUST be registered before the
|
|
9
|
+
* generic dogsbay-md detector. `detectVitepress` requires an actual
|
|
10
|
+
* `.vitepress/config.*`, so it cannot claim a plain-Markdown corpus.
|
|
11
|
+
*/
|
|
1
12
|
import type { FormatPlugin, ExportPage, NavItem } from "@dogsbay/types";
|
|
2
13
|
export interface VitepressImportResult {
|
|
3
14
|
pages: ExportPage[];
|
package/dist/cli.js
CHANGED
|
@@ -1,71 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Orchestrates: detect corpus → loadVitepress (config + page walk + nav) →
|
|
5
|
-
* translate each page (parseVitepress) → assemble ExportPage[] + NavItem[].
|
|
6
|
-
*
|
|
7
|
-
* VitePress content is `.md`, so this plugin MUST be registered before the
|
|
8
|
-
* generic dogsbay-md detector. `detectVitepress` requires an actual
|
|
9
|
-
* `.vitepress/config.*`, so it cannot claim a plain-Markdown corpus.
|
|
10
|
-
*/
|
|
11
|
-
import { readFileSync } from "node:fs";
|
|
12
|
-
import { extractHeadings } from "@dogsbay/format-dogsbay-md";
|
|
13
|
-
import { parseVitepress } from "./parse.js";
|
|
14
|
-
import { loadVitepress, detectVitepress, translateOptionsFor, titleFromSlug, } from "./loaders/load.js";
|
|
1
|
+
import { importVitepressCorpus } from "./import.js";
|
|
2
|
+
import { detectVitepress, readVitepressFolder } from "./loaders/folder.js";
|
|
15
3
|
export async function importVitepress(source, opts = {}) {
|
|
16
4
|
const hrefPrefix = typeof opts.hrefPrefix === "string" ? opts.hrefPrefix : "";
|
|
17
|
-
const
|
|
18
|
-
hrefPrefix,
|
|
5
|
+
const { corpus, config, warnings } = await readVitepressFolder(source, {
|
|
19
6
|
evalConfig: opts.evalConfig === true || opts.evalConfig === "true",
|
|
20
7
|
});
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
for (const ps of loaded.pages) {
|
|
26
|
-
try {
|
|
27
|
-
const content = readFileSync(ps.filePath, "utf-8");
|
|
28
|
-
const tOpts = translateOptionsFor(ps, loaded.srcDir, loaded.root, loaded.warnings);
|
|
29
|
-
const { tree, frontmatter, body, ledger, components: comps } = parseVitepress(content, tOpts);
|
|
30
|
-
ledgerTotal += ledger.length;
|
|
31
|
-
for (const [name, n] of Object.entries(comps)) {
|
|
32
|
-
components[name] = (components[name] ?? 0) + n;
|
|
33
|
-
}
|
|
34
|
-
const headings = extractHeadings(tree);
|
|
35
|
-
const h1 = headings.find((h) => h.depth === 1);
|
|
36
|
-
const fmTitle = typeof frontmatter.title === "string" && frontmatter.title.trim()
|
|
37
|
-
? frontmatter.title.trim()
|
|
38
|
-
: undefined;
|
|
39
|
-
// A `layout: home` page carries its name in `hero.name`, not a heading.
|
|
40
|
-
const heroName = typeof frontmatter.hero?.name === "string"
|
|
41
|
-
? (frontmatter.hero.name)
|
|
42
|
-
: undefined;
|
|
43
|
-
pages.push({
|
|
44
|
-
slug: ps.slug,
|
|
45
|
-
title: fmTitle || h1?.text || heroName || titleFromSlug(ps.slug),
|
|
46
|
-
tree,
|
|
47
|
-
headings,
|
|
48
|
-
frontmatter,
|
|
49
|
-
meta: (ledger.length
|
|
50
|
-
? { vitepressLedger: ledger }
|
|
51
|
-
: {}),
|
|
52
|
-
bodyMarkdown: body,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
catch (err) {
|
|
56
|
-
failed += 1;
|
|
57
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
-
console.warn(`vitepress import: failed to convert ${ps.filePath} (slug "${ps.slug}"), skipping: ${msg}`);
|
|
59
|
-
}
|
|
8
|
+
const imported = importVitepressCorpus(corpus, { hrefPrefix, config, warnings });
|
|
9
|
+
const failed = imported.ledger.filter((e) => e.code === "page-failed");
|
|
10
|
+
for (const e of failed) {
|
|
11
|
+
console.warn(`vitepress import: failed to convert ${corpus.absolute(e.file ?? "")}, skipping: ${e.message}`);
|
|
60
12
|
}
|
|
61
|
-
|
|
13
|
+
const ledgerTotal = imported.ledger.filter((e) => e.code === "unmapped-construct").length;
|
|
14
|
+
reportSummary(imported.warnings, failed.length, imported.pages.length + failed.length, ledgerTotal, imported.components, imported.navSource);
|
|
62
15
|
return {
|
|
63
|
-
pages,
|
|
64
|
-
nav:
|
|
65
|
-
srcDir:
|
|
66
|
-
navSource:
|
|
67
|
-
assetMounts:
|
|
68
|
-
siteName:
|
|
16
|
+
pages: imported.pages,
|
|
17
|
+
nav: imported.nav,
|
|
18
|
+
srcDir: corpus.absolute(imported.srcDir),
|
|
19
|
+
navSource: imported.navSource,
|
|
20
|
+
assetMounts: imported.assetDirs.map((a) => ({ ...a, dir: corpus.absolute(a.dir) })),
|
|
21
|
+
...(imported.siteName ? { siteName: imported.siteName } : {}),
|
|
69
22
|
};
|
|
70
23
|
}
|
|
71
24
|
/**
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type VitePressConfig } from "./config.js";
|
|
2
|
+
/**
|
|
3
|
+
* Locate `.vitepress/config.*`.
|
|
4
|
+
*
|
|
5
|
+
* VitePress also permits `.vitepress/config/index.*` — the split-config layout
|
|
6
|
+
* used by larger sites (Element Plus, Vue's own docs).
|
|
7
|
+
*/
|
|
8
|
+
export declare function findConfigFile(vitepressDir: string): string | undefined;
|
|
9
|
+
/** Read the config file statically (no execution). */
|
|
10
|
+
export declare function readConfig(configPath: string): VitePressConfig;
|
|
11
|
+
/**
|
|
12
|
+
* Read the config by IMPORTING it. Opt-in (`--eval-config`).
|
|
13
|
+
*
|
|
14
|
+
* SECURITY: this EXECUTES JavaScript/TypeScript from the source repository,
|
|
15
|
+
* and needs a loader that understands TS (`tsx`, or a `.mjs`/`.js` config).
|
|
16
|
+
* Acceptable for a local migration CLI pointed at a repo you already trust
|
|
17
|
+
* enough to `npm install`; not acceptable for untrusted input. All execution
|
|
18
|
+
* funnels through this one function so it can later be sandboxed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function evalConfig(configPath: string): Promise<VitePressConfig>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node only: `.vitepress/config.*` on disk. `readConfig` reads it statically (no execution);
|
|
3
|
+
* `evalConfig` imports it, opt-in (`--eval-config`).
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { asObject } from "@dogsbay/literal";
|
|
9
|
+
import { parseConfig, shapeConfig } from "./config.js";
|
|
10
|
+
const CONFIG_EXTS = [".ts", ".mts", ".mjs", ".js", ".cts", ".cjs"];
|
|
11
|
+
/**
|
|
12
|
+
* Locate `.vitepress/config.*`.
|
|
13
|
+
*
|
|
14
|
+
* VitePress also permits `.vitepress/config/index.*` — the split-config layout
|
|
15
|
+
* used by larger sites (Element Plus, Vue's own docs).
|
|
16
|
+
*/
|
|
17
|
+
export function findConfigFile(vitepressDir) {
|
|
18
|
+
for (const ext of CONFIG_EXTS) {
|
|
19
|
+
const p = join(vitepressDir, "config" + ext);
|
|
20
|
+
if (existsSync(p))
|
|
21
|
+
return p;
|
|
22
|
+
}
|
|
23
|
+
for (const ext of CONFIG_EXTS) {
|
|
24
|
+
const p = join(vitepressDir, "config", "index" + ext);
|
|
25
|
+
if (existsSync(p))
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
/** Read the config file statically (no execution). */
|
|
31
|
+
export function readConfig(configPath) {
|
|
32
|
+
return parseConfig(readFileSync(configPath, "utf-8"), configPath);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read the config by IMPORTING it. Opt-in (`--eval-config`).
|
|
36
|
+
*
|
|
37
|
+
* SECURITY: this EXECUTES JavaScript/TypeScript from the source repository,
|
|
38
|
+
* and needs a loader that understands TS (`tsx`, or a `.mjs`/`.js` config).
|
|
39
|
+
* Acceptable for a local migration CLI pointed at a repo you already trust
|
|
40
|
+
* enough to `npm install`; not acceptable for untrusted input. All execution
|
|
41
|
+
* funnels through this one function so it can later be sandboxed.
|
|
42
|
+
*/
|
|
43
|
+
export async function evalConfig(configPath) {
|
|
44
|
+
const mod = (await import(pathToFileURL(configPath).href));
|
|
45
|
+
const raw = (mod.default ?? mod);
|
|
46
|
+
const root = asObject(raw);
|
|
47
|
+
if (!root) {
|
|
48
|
+
throw new Error(`${configPath}: default export is not an object`);
|
|
49
|
+
}
|
|
50
|
+
return shapeConfig(root, configPath, []);
|
|
51
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read `.vitepress/config.*` into the typed slice this importer needs. No Node: the text
|
|
3
|
+
* comes from the corpus (`config-node.ts` has the file-system half).
|
|
4
|
+
*
|
|
5
|
+
* STATICALLY, by default — see `@dogsbay/literal` for why. `--eval-config` swaps in
|
|
6
|
+
* a real `import()` for repos whose config outruns the literal subset AND
|
|
7
|
+
* whose dependencies are installed; that path executes code from the source
|
|
8
|
+
* repository and is therefore opt-in, never the default.
|
|
9
|
+
*/
|
|
10
|
+
import { type LiteralValue } from "@dogsbay/literal";
|
|
1
11
|
/** A sidebar/nav entry as VitePress declares it. */
|
|
2
12
|
export interface RawNavItem {
|
|
3
13
|
text?: string;
|
|
@@ -22,27 +32,11 @@ export interface VitePressConfig {
|
|
|
22
32
|
nav?: RawNavItem[];
|
|
23
33
|
/** Locale keys other than `root`, so the loader can report what it skipped. */
|
|
24
34
|
locales?: string[];
|
|
25
|
-
/**
|
|
35
|
+
/** The config file that was read (a corpus path, or absolute from `readConfig`). */
|
|
26
36
|
configPath: string;
|
|
27
37
|
/** Expressions the static reader could not evaluate. */
|
|
28
38
|
unresolved: string[];
|
|
29
39
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* VitePress also permits `.vitepress/config/index.*` — the split-config layout
|
|
34
|
-
* used by larger sites (Element Plus, Vue's own docs).
|
|
35
|
-
*/
|
|
36
|
-
export declare function findConfigFile(vitepressDir: string): string | undefined;
|
|
37
|
-
/** Read the config file statically (no execution). */
|
|
38
|
-
export declare function readConfig(configPath: string): VitePressConfig;
|
|
39
|
-
/**
|
|
40
|
-
* Read the config by IMPORTING it. Opt-in (`--eval-config`).
|
|
41
|
-
*
|
|
42
|
-
* SECURITY: this EXECUTES JavaScript/TypeScript from the source repository,
|
|
43
|
-
* and needs a loader that understands TS (`tsx`, or a `.mjs`/`.js` config).
|
|
44
|
-
* Acceptable for a local migration CLI pointed at a repo you already trust
|
|
45
|
-
* enough to `npm install`; not acceptable for untrusted input. All execution
|
|
46
|
-
* funnels through this one function so it can later be sandboxed.
|
|
47
|
-
*/
|
|
48
|
-
export declare function evalConfig(configPath: string): Promise<VitePressConfig>;
|
|
40
|
+
export declare function shapeConfig(root: Record<string, LiteralValue>, configPath: string, unresolved: string[]): VitePressConfig;
|
|
41
|
+
/** Read a config file's text statically (no execution). `configPath` is for messages. */
|
|
42
|
+
export declare function parseConfig(src: string, configPath: string): VitePressConfig;
|
package/dist/config.js
CHANGED
|
@@ -1,38 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Read `.vitepress/config.*` into the typed slice this importer needs.
|
|
2
|
+
* Read `.vitepress/config.*` into the typed slice this importer needs. No Node: the text
|
|
3
|
+
* comes from the corpus (`config-node.ts` has the file-system half).
|
|
3
4
|
*
|
|
4
|
-
* STATICALLY, by default — see
|
|
5
|
+
* STATICALLY, by default — see `@dogsbay/literal` for why. `--eval-config` swaps in
|
|
5
6
|
* a real `import()` for repos whose config outruns the literal subset AND
|
|
6
7
|
* whose dependencies are installed; that path executes code from the source
|
|
7
8
|
* repository and is therefore opt-in, never the default.
|
|
8
9
|
*/
|
|
9
|
-
import {
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { evaluateModule, asArray, asObject, asString, UNRESOLVED } from "./literal.js";
|
|
13
|
-
const CONFIG_BASENAMES = ["config"];
|
|
14
|
-
const CONFIG_EXTS = [".ts", ".mts", ".mjs", ".js", ".cts", ".cjs"];
|
|
15
|
-
/**
|
|
16
|
-
* Locate `.vitepress/config.*`.
|
|
17
|
-
*
|
|
18
|
-
* VitePress also permits `.vitepress/config/index.*` — the split-config layout
|
|
19
|
-
* used by larger sites (Element Plus, Vue's own docs).
|
|
20
|
-
*/
|
|
21
|
-
export function findConfigFile(vitepressDir) {
|
|
22
|
-
for (const base of CONFIG_BASENAMES) {
|
|
23
|
-
for (const ext of CONFIG_EXTS) {
|
|
24
|
-
const p = join(vitepressDir, base + ext);
|
|
25
|
-
if (existsSync(p))
|
|
26
|
-
return p;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
for (const ext of CONFIG_EXTS) {
|
|
30
|
-
const p = join(vitepressDir, "config", "index" + ext);
|
|
31
|
-
if (existsSync(p))
|
|
32
|
-
return p;
|
|
33
|
-
}
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
10
|
+
import { evaluateModule, asArray, asObject, asString, UNRESOLVED } from "@dogsbay/literal";
|
|
36
11
|
/**
|
|
37
12
|
* VitePress merges `locales.root` over the top-level config, and each locale's
|
|
38
13
|
* `themeConfig` over the top-level `themeConfig`. Vitest puts its entire
|
|
@@ -105,7 +80,7 @@ function toRawSidebar(v) {
|
|
|
105
80
|
}
|
|
106
81
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
107
82
|
}
|
|
108
|
-
function shapeConfig(root, configPath, unresolved) {
|
|
83
|
+
export function shapeConfig(root, configPath, unresolved) {
|
|
109
84
|
const { theme, localeKeys } = mergedThemeConfig(root);
|
|
110
85
|
const cfg = { configPath, unresolved };
|
|
111
86
|
const title = asString(root.title);
|
|
@@ -146,9 +121,8 @@ function shapeConfig(root, configPath, unresolved) {
|
|
|
146
121
|
cfg.locales = localeKeys;
|
|
147
122
|
return cfg;
|
|
148
123
|
}
|
|
149
|
-
/** Read
|
|
150
|
-
export function
|
|
151
|
-
const src = readFileSync(configPath, "utf-8");
|
|
124
|
+
/** Read a config file's text statically (no execution). `configPath` is for messages. */
|
|
125
|
+
export function parseConfig(src, configPath) {
|
|
152
126
|
const { value, unresolved } = evaluateModule(src);
|
|
153
127
|
const root = asObject(value);
|
|
154
128
|
if (!root) {
|
|
@@ -162,21 +136,3 @@ export function readConfig(configPath) {
|
|
|
162
136
|
}
|
|
163
137
|
return shapeConfig(root, configPath, unresolved);
|
|
164
138
|
}
|
|
165
|
-
/**
|
|
166
|
-
* Read the config by IMPORTING it. Opt-in (`--eval-config`).
|
|
167
|
-
*
|
|
168
|
-
* SECURITY: this EXECUTES JavaScript/TypeScript from the source repository,
|
|
169
|
-
* and needs a loader that understands TS (`tsx`, or a `.mjs`/`.js` config).
|
|
170
|
-
* Acceptable for a local migration CLI pointed at a repo you already trust
|
|
171
|
-
* enough to `npm install`; not acceptable for untrusted input. All execution
|
|
172
|
-
* funnels through this one function so it can later be sandboxed.
|
|
173
|
-
*/
|
|
174
|
-
export async function evalConfig(configPath) {
|
|
175
|
-
const mod = (await import(pathToFileURL(configPath).href));
|
|
176
|
-
const raw = (mod.default ?? mod);
|
|
177
|
-
const root = asObject(raw);
|
|
178
|
-
if (!root) {
|
|
179
|
-
throw new Error(`${configPath}: default export is not an object`);
|
|
180
|
-
}
|
|
181
|
-
return shapeConfig(root, configPath, []);
|
|
182
|
-
}
|
package/dist/import.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VitePress corpus → pages, nav, asset directories and a ledger, over a {@link Corpus} (no
|
|
3
|
+
* Node, quiet). The plugin's `import()` (`convert`, `site build`) and the browser
|
|
4
|
+
* (`./migrate`) both run it.
|
|
5
|
+
*/
|
|
6
|
+
import type { ExportPage, ImportLedgerEntry, NavItem } from "@dogsbay/types";
|
|
7
|
+
import type { Corpus } from "@dogsbay/corpus";
|
|
8
|
+
import { type VitepressAssetDir, type VitepressLoadOptions } from "./loaders/load.js";
|
|
9
|
+
export interface VitepressCorpusImport {
|
|
10
|
+
pages: ExportPage[];
|
|
11
|
+
nav: NavItem[];
|
|
12
|
+
/** The project folder (holding `.vitepress`), relative to the corpus root. */
|
|
13
|
+
root: string;
|
|
14
|
+
/** The folder page slugs are relative to (`srcDir`), relative to the corpus root. */
|
|
15
|
+
srcDir: string;
|
|
16
|
+
navSource: "sidebar" | "directory";
|
|
17
|
+
/** Asset directories to mirror, relative to the corpus root. */
|
|
18
|
+
assetDirs: VitepressAssetDir[];
|
|
19
|
+
/** Site title from the config, when it had one. */
|
|
20
|
+
siteName?: string;
|
|
21
|
+
/** What the loader could not read or resolve (config, rewrites, snippets), as written. */
|
|
22
|
+
warnings: string[];
|
|
23
|
+
/** Vue component tag → occurrences removed. */
|
|
24
|
+
components: Record<string, number>;
|
|
25
|
+
/** Per-page constructs mapped with caveats, pages that failed, and the loader's warnings. */
|
|
26
|
+
ledger: ImportLedgerEntry[];
|
|
27
|
+
}
|
|
28
|
+
export declare function importVitepressCorpus(corpus: Corpus, opts?: VitepressLoadOptions): VitepressCorpusImport;
|
package/dist/import.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { extractHeadings } from "@dogsbay/format-dogsbay-md/browser";
|
|
2
|
+
import { dirOf } from "@dogsbay/detect";
|
|
3
|
+
import { parseVitepress } from "./parse.js";
|
|
4
|
+
import { loadVitepressCorpus, optionsForDir, titleFromSlug, } from "./loaders/load.js";
|
|
5
|
+
export function importVitepressCorpus(corpus, opts = {}) {
|
|
6
|
+
const loaded = loadVitepressCorpus(corpus, opts);
|
|
7
|
+
const listed = new Set(corpus.paths);
|
|
8
|
+
const pages = [];
|
|
9
|
+
const components = {};
|
|
10
|
+
const ledger = [];
|
|
11
|
+
for (const ps of loaded.pages) {
|
|
12
|
+
try {
|
|
13
|
+
const content = corpus.text(ps.path);
|
|
14
|
+
if (content === undefined)
|
|
15
|
+
throw new Error("the file could not be read");
|
|
16
|
+
const tOpts = optionsForDir(corpus, listed, dirOf(ps.path), loaded.srcDir, loaded.root);
|
|
17
|
+
const { tree, frontmatter, body, ledger: notes, components: comps } = parseVitepress(content, tOpts);
|
|
18
|
+
for (const n of notes) {
|
|
19
|
+
ledger.push({ file: ps.path, line: n.line, severity: "warning", code: "unmapped-construct", message: `${n.construct}: ${n.note}` });
|
|
20
|
+
}
|
|
21
|
+
for (const [name, n] of Object.entries(comps)) {
|
|
22
|
+
components[name] = (components[name] ?? 0) + n;
|
|
23
|
+
}
|
|
24
|
+
const headings = extractHeadings(tree);
|
|
25
|
+
const h1 = headings.find((h) => h.depth === 1);
|
|
26
|
+
const fmTitle = typeof frontmatter.title === "string" && frontmatter.title.trim()
|
|
27
|
+
? frontmatter.title.trim()
|
|
28
|
+
: undefined;
|
|
29
|
+
// A `layout: home` page carries its name in `hero.name`, not a heading.
|
|
30
|
+
const heroName = typeof frontmatter.hero?.name === "string"
|
|
31
|
+
? (frontmatter.hero.name)
|
|
32
|
+
: undefined;
|
|
33
|
+
pages.push({
|
|
34
|
+
slug: ps.slug,
|
|
35
|
+
title: fmTitle || h1?.text || heroName || titleFromSlug(ps.slug),
|
|
36
|
+
tree,
|
|
37
|
+
headings,
|
|
38
|
+
frontmatter,
|
|
39
|
+
meta: (notes.length
|
|
40
|
+
? { vitepressLedger: notes }
|
|
41
|
+
: {}),
|
|
42
|
+
bodyMarkdown: body,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
ledger.push({
|
|
47
|
+
file: ps.path,
|
|
48
|
+
severity: "warning",
|
|
49
|
+
code: "page-failed",
|
|
50
|
+
message: `not converted, skipped (slug "${ps.slug}"): ${err instanceof Error ? err.message : String(err)}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const w of loaded.warnings)
|
|
55
|
+
ledger.push({ severity: "warning", code: "vitepress-load", message: w });
|
|
56
|
+
const removed = Object.entries(components).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
57
|
+
if (removed.length) {
|
|
58
|
+
ledger.push({
|
|
59
|
+
severity: "info",
|
|
60
|
+
code: "vue-components-removed",
|
|
61
|
+
message: `Vue components removed: ${removed.map(([n, c]) => `${n}×${c}`).join(", ")}`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
pages,
|
|
66
|
+
nav: loaded.nav,
|
|
67
|
+
root: loaded.root,
|
|
68
|
+
srcDir: loaded.srcDir,
|
|
69
|
+
navSource: loaded.navSource,
|
|
70
|
+
assetDirs: loaded.assetDirs,
|
|
71
|
+
...(loaded.config.title ? { siteName: loaded.config.title } : {}),
|
|
72
|
+
warnings: loaded.warnings,
|
|
73
|
+
components,
|
|
74
|
+
ledger,
|
|
75
|
+
};
|
|
76
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,13 +5,18 @@
|
|
|
5
5
|
* containers, code-fence conventions, snippets and includes to Dogsbay-MD,
|
|
6
6
|
* then parsing with the canonical dogsbay-md parser (zero new node types).
|
|
7
7
|
*
|
|
8
|
-
* Nav comes from `.vitepress/config.*`, read STATICALLY — see
|
|
8
|
+
* Nav comes from `.vitepress/config.*`, read STATICALLY — see `@dogsbay/literal`. The
|
|
9
|
+
* import runs over a corpus (`importVitepressCorpus`), so the CLI and the browser
|
|
10
|
+
* (`./migrate`) share it.
|
|
9
11
|
*
|
|
10
12
|
* See plans/format-vitepress.md.
|
|
11
13
|
*/
|
|
12
14
|
export { parseVitepress, type ParseVitepressResult } from "./parse.js";
|
|
13
|
-
export { vitepressToDogsbayMd, translateFenceInfo, splitRegion, type TranslateResult, type TranslateLedgerEntry, type TranslateOptions, type SnippetRequest, } from "./translate.js";
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
15
|
+
export { vitepressToDogsbayMd, translateFenceInfo, splitRegion, type TranslateResult, type TranslateLedgerEntry, type TranslateOptions, type SnippetRequest, type SnippetMiss, } from "./translate.js";
|
|
16
|
+
export { parseConfig, type VitePressConfig, type RawNavItem, type RawSidebar } from "./config.js";
|
|
17
|
+
export { findConfigFile, readConfig, evalConfig } from "./config-node.js";
|
|
18
|
+
export { evaluateModule, UNRESOLVED, type EvalResult, type LiteralValue, } from "@dogsbay/literal";
|
|
16
19
|
export { buildNav, itemsToNav, navFromSlugs, linkToSlug, slugToHref, type NavContext, } from "./nav.js";
|
|
17
|
-
export { loadVitepress, detectVitepress, findVitepressDir,
|
|
20
|
+
export { loadVitepress, detectVitepress, findVitepressDir, readVitepressFolder } from "./loaders/folder.js";
|
|
21
|
+
export { importVitepressCorpus, type VitepressCorpusImport } from "./import.js";
|
|
22
|
+
export { loadVitepressCorpus, findVitepressProject, optionsForDir, pathToSlug, globToRegExp, extractRegion, dedent, translateOptionsFor, titleFromSlug, type VitepressLoadResult, type VitepressPageSource, type VitepressLoadOptions, type VitepressAssetDir, } from "./loaders/load.js";
|
package/dist/index.js
CHANGED
|
@@ -5,13 +5,18 @@
|
|
|
5
5
|
* containers, code-fence conventions, snippets and includes to Dogsbay-MD,
|
|
6
6
|
* then parsing with the canonical dogsbay-md parser (zero new node types).
|
|
7
7
|
*
|
|
8
|
-
* Nav comes from `.vitepress/config.*`, read STATICALLY — see
|
|
8
|
+
* Nav comes from `.vitepress/config.*`, read STATICALLY — see `@dogsbay/literal`. The
|
|
9
|
+
* import runs over a corpus (`importVitepressCorpus`), so the CLI and the browser
|
|
10
|
+
* (`./migrate`) share it.
|
|
9
11
|
*
|
|
10
12
|
* See plans/format-vitepress.md.
|
|
11
13
|
*/
|
|
12
14
|
export { parseVitepress } from "./parse.js";
|
|
13
15
|
export { vitepressToDogsbayMd, translateFenceInfo, splitRegion, } from "./translate.js";
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
+
export { parseConfig } from "./config.js";
|
|
17
|
+
export { findConfigFile, readConfig, evalConfig } from "./config-node.js";
|
|
18
|
+
export { evaluateModule, UNRESOLVED, } from "@dogsbay/literal";
|
|
16
19
|
export { buildNav, itemsToNav, navFromSlugs, linkToSlug, slugToHref, } from "./nav.js";
|
|
17
|
-
export { loadVitepress, detectVitepress, findVitepressDir,
|
|
20
|
+
export { loadVitepress, detectVitepress, findVitepressDir, readVitepressFolder } from "./loaders/folder.js";
|
|
21
|
+
export { importVitepressCorpus } from "./import.js";
|
|
22
|
+
export { loadVitepressCorpus, findVitepressProject, optionsForDir, pathToSlug, globToRegExp, extractRegion, dedent, translateOptionsFor, titleFromSlug, } from "./loaders/load.js";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type FolderCorpus } from "@dogsbay/corpus/folder";
|
|
2
|
+
import type { VitePressConfig } from "../config.js";
|
|
3
|
+
import { type VitepressLoadResult } from "./load.js";
|
|
4
|
+
/**
|
|
5
|
+
* `.vitepress/config.*` is the only unambiguous marker. Requiring it is what
|
|
6
|
+
* lets this plugin sit safely in the registry among the other Markdown
|
|
7
|
+
* importers: it cannot claim a plain-Markdown, MkDocs or GitBook corpus.
|
|
8
|
+
*/
|
|
9
|
+
export declare function findVitepressDir(source: string): string | undefined;
|
|
10
|
+
export declare function detectVitepress(source: string): boolean;
|
|
11
|
+
export interface VitepressFolder {
|
|
12
|
+
/** The project (the folder holding `.vitepress`) as a corpus. */
|
|
13
|
+
corpus: FolderCorpus;
|
|
14
|
+
/** The config, read statically or (`evalConfig`) by importing it. */
|
|
15
|
+
config: VitePressConfig;
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The VitePress project under `source` as a corpus rooted at its project folder, with
|
|
20
|
+
* `srcDir` walked first (a `srcDir` reached through a link is listed under its own name).
|
|
21
|
+
*/
|
|
22
|
+
export declare function readVitepressFolder(source: string, opts?: {
|
|
23
|
+
evalConfig?: boolean;
|
|
24
|
+
}): Promise<VitepressFolder>;
|
|
25
|
+
/** `loadVitepressCorpus` over a folder, with the corpus it read. */
|
|
26
|
+
export declare function loadVitepress(source: string, opts?: {
|
|
27
|
+
hrefPrefix?: string;
|
|
28
|
+
evalConfig?: boolean;
|
|
29
|
+
}): Promise<VitepressLoadResult & {
|
|
30
|
+
corpus: FolderCorpus;
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node only: a VitePress project on disk as a corpus (`@dogsbay/corpus/folder`, rooted at the
|
|
3
|
+
* folder holding `.vitepress`, its `srcDir` walked first), and the CLI's on-disk detector.
|
|
4
|
+
* Everything else in the loader reads the corpus, so the CLI and the browser import run the
|
|
5
|
+
* same code.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { normalizePath } from "@dogsbay/detect";
|
|
10
|
+
import { readFolder } from "@dogsbay/corpus/folder";
|
|
11
|
+
import { evalConfig, findConfigFile, readConfig } from "../config-node.js";
|
|
12
|
+
import { loadVitepressCorpus } from "./load.js";
|
|
13
|
+
/**
|
|
14
|
+
* `.vitepress/config.*` is the only unambiguous marker. Requiring it is what
|
|
15
|
+
* lets this plugin sit safely in the registry among the other Markdown
|
|
16
|
+
* importers: it cannot claim a plain-Markdown, MkDocs or GitBook corpus.
|
|
17
|
+
*/
|
|
18
|
+
export function findVitepressDir(source) {
|
|
19
|
+
const candidates = [source, join(source, "docs"), join(source, "site"), join(source, "website")];
|
|
20
|
+
for (const dir of candidates) {
|
|
21
|
+
const vp = join(dir, ".vitepress");
|
|
22
|
+
if (existsSync(vp) && findConfigFile(vp))
|
|
23
|
+
return vp;
|
|
24
|
+
}
|
|
25
|
+
// One level down, for monorepos whose docs live in an unconventional dir.
|
|
26
|
+
let entries = [];
|
|
27
|
+
try {
|
|
28
|
+
entries = readdirSync(source).sort();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
for (const name of entries) {
|
|
34
|
+
if (name.startsWith(".") || name === "node_modules")
|
|
35
|
+
continue;
|
|
36
|
+
const vp = join(source, name, ".vitepress");
|
|
37
|
+
try {
|
|
38
|
+
if (statSync(vp).isDirectory() && findConfigFile(vp))
|
|
39
|
+
return vp;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* not a directory */
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
export function detectVitepress(source) {
|
|
48
|
+
try {
|
|
49
|
+
if (!existsSync(source) || !statSync(source).isDirectory())
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return findVitepressDir(source) !== undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The VitePress project under `source` as a corpus rooted at its project folder, with
|
|
59
|
+
* `srcDir` walked first (a `srcDir` reached through a link is listed under its own name).
|
|
60
|
+
*/
|
|
61
|
+
export async function readVitepressFolder(source, opts = {}) {
|
|
62
|
+
const vitepressDir = findVitepressDir(source);
|
|
63
|
+
if (!vitepressDir)
|
|
64
|
+
throw new Error(`No .vitepress/config.* found under ${source}`);
|
|
65
|
+
const configPath = findConfigFile(vitepressDir);
|
|
66
|
+
if (!configPath)
|
|
67
|
+
throw new Error(`No config file in ${vitepressDir}`);
|
|
68
|
+
const warnings = [];
|
|
69
|
+
let config;
|
|
70
|
+
if (opts.evalConfig) {
|
|
71
|
+
try {
|
|
72
|
+
config = await evalConfig(configPath);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
76
|
+
warnings.push(`--eval-config failed (${msg}); falling back to static reading`);
|
|
77
|
+
config = readConfig(configPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
config = readConfig(configPath);
|
|
82
|
+
}
|
|
83
|
+
const srcDir = config.srcDir ? normalizePath(config.srcDir) : undefined;
|
|
84
|
+
return { corpus: readFolder(dirname(vitepressDir), Infinity, srcDir ? [srcDir] : []), config, warnings };
|
|
85
|
+
}
|
|
86
|
+
/** `loadVitepressCorpus` over a folder, with the corpus it read. */
|
|
87
|
+
export async function loadVitepress(source, opts = {}) {
|
|
88
|
+
const { corpus, config, warnings } = await readVitepressFolder(source, opts);
|
|
89
|
+
return { ...loadVitepressCorpus(corpus, { hrefPrefix: opts.hrefPrefix, config, warnings }), corpus };
|
|
90
|
+
}
|