@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 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
- * FormatPlugin for VitePress as a source format.
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 loaded = await loadVitepress(source, {
18
- hrefPrefix,
5
+ const { corpus, config, warnings } = await readVitepressFolder(source, {
19
6
  evalConfig: opts.evalConfig === true || opts.evalConfig === "true",
20
7
  });
21
- const pages = [];
22
- const components = {};
23
- let ledgerTotal = 0;
24
- let failed = 0;
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
- reportSummary(loaded.warnings, failed, loaded.pages.length, ledgerTotal, components, loaded.navSource);
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: loaded.nav,
65
- srcDir: loaded.srcDir,
66
- navSource: loaded.navSource,
67
- assetMounts: loaded.assetDirs,
68
- siteName: loaded.config.title,
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
- /** Absolute path of the config file that was read. */
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
- * Locate `.vitepress/config.*`.
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 `literal.ts` for why. `--eval-config` swaps in
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 { existsSync, readFileSync } from "node:fs";
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 the config file statically (no execution). */
150
- export function readConfig(configPath) {
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
- }
@@ -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 `literal.ts`.
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 { findConfigFile, readConfig, evalConfig, type VitePressConfig, type RawNavItem, type RawSidebar, } from "./config.js";
15
- export { evaluateModule, UNRESOLVED, type EvalResult, type LiteralValue, } from "./literal.js";
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, pathToSlug, globToRegExp, extractRegion, dedent, translateOptionsFor, titleFromSlug, type VitepressLoadResult, type VitepressPageSource, type VitepressLoadOptions, } from "./loaders/load.js";
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 `literal.ts`.
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 { findConfigFile, readConfig, evalConfig, } from "./config.js";
15
- export { evaluateModule, UNRESOLVED, } from "./literal.js";
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, pathToSlug, globToRegExp, extractRegion, dedent, translateOptionsFor, titleFromSlug, } from "./loaders/load.js";
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
+ }