@dogsbay/format-mkdocs 0.2.0-beta.11 → 0.2.0-beta.111
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/cli.d.ts.map +1 -1
- package/dist/cli.js +111 -4
- package/dist/cli.js.map +1 -1
- package/dist/export/to-mkdocs-project.d.ts +3 -0
- package/dist/export/to-mkdocs-project.d.ts.map +1 -1
- package/dist/export/to-mkdocs-project.js +267 -63
- package/dist/export/to-mkdocs-project.js.map +1 -1
- package/dist/export/to-mkdocs.d.ts +53 -3
- package/dist/export/to-mkdocs.d.ts.map +1 -1
- package/dist/export/to-mkdocs.js +842 -100
- package/dist/export/to-mkdocs.js.map +1 -1
- package/dist/index.d.ts +20 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -3
- package/dist/index.js.map +1 -1
- package/dist/loader.js +1 -2
- package/dist/loader.js.map +1 -1
- package/dist/rules/autodoc.d.ts +1 -1
- package/dist/rules/autodoc.d.ts.map +1 -1
- package/dist/rules/autodoc.js +1 -2
- package/dist/rules/autodoc.js.map +1 -1
- package/package.json +13 -12
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAqC,MAAM,gBAAgB,CAAC;AAKtF,eAAO,MAAM,MAAM,EAAE,YA6GpB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
1
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, writeFileSync, copyFileSync } from "node:fs";
|
|
2
|
+
import { dirname, extname, join } from "node:path";
|
|
3
3
|
import { importMkdocsProject } from "./importer.js";
|
|
4
|
+
import { exportMkdocsProject } from "./export/to-mkdocs-project.js";
|
|
5
|
+
import { formatLedgerReport } from "@dogsbay/serialize-core";
|
|
4
6
|
export const plugin = {
|
|
5
7
|
name: "mkdocs",
|
|
6
8
|
canImport: true,
|
|
@@ -12,7 +14,15 @@ export const plugin = {
|
|
|
12
14
|
{ flags: "--custom-css <files>", description: "Custom CSS files to copy (comma-separated)" },
|
|
13
15
|
{ flags: "--docusaurus-admonitions", description: "Enable Docusaurus-style ::: admonitions" },
|
|
14
16
|
],
|
|
15
|
-
|
|
17
|
+
// NOT `--theme`: `dogsbay convert` already owns that flag for the Astro
|
|
18
|
+
// preset, with a default of "default" — which reached this exporter as
|
|
19
|
+
// `theme: name: default`, a theme MkDocs does not have, and every
|
|
20
|
+
// `convert --to mkdocs` project failed its first `mkdocs build`.
|
|
21
|
+
exportOptions: [
|
|
22
|
+
{ flags: "--mkdocs-theme <name>", description: "MkDocs theme for the emitted mkdocs.yml (material | mkdocs | readthedocs)", default: "material" },
|
|
23
|
+
{ flags: "--site-url <url>", description: "site_url for the emitted mkdocs.yml" },
|
|
24
|
+
{ flags: "--repo-url <url>", description: "repo_url for the emitted mkdocs.yml" },
|
|
25
|
+
],
|
|
16
26
|
async import(source, opts) {
|
|
17
27
|
const { pages, nav, config } = await importMkdocsProject(source, opts);
|
|
18
28
|
// Attach config metadata for the exporter to consume
|
|
@@ -26,7 +36,104 @@ export const plugin = {
|
|
|
26
36
|
if (extraCss && Array.isArray(extraCss)) {
|
|
27
37
|
opts.extraCss = extraCss;
|
|
28
38
|
}
|
|
29
|
-
|
|
39
|
+
// Slugs are relative to `docs_dir`, not to the project root — so an
|
|
40
|
+
// `{% include %}` in a page means a file under THERE.
|
|
41
|
+
return { pages, nav, contentRoot: join(source, config.docsDir) };
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* Export: TreeNode pages + nav → a complete MkDocs project.
|
|
45
|
+
*
|
|
46
|
+
* `exportMkdocsProject` is I/O-free and returns a path → content map, so
|
|
47
|
+
* everything this method does is create directories and write bytes. It was
|
|
48
|
+
* written and unit-tested long before it was reachable: `cli.ts` declared
|
|
49
|
+
* `canExport: true` with no `export` method, and `resolveExporter()` filters
|
|
50
|
+
* on `canExport && f.export`, so `--to mkdocs` reported an unknown target
|
|
51
|
+
* while the serializer sat in the same package. That predicate exists
|
|
52
|
+
* BECAUSE of this plugin.
|
|
53
|
+
*/
|
|
54
|
+
async export(pages, nav, output, opts) {
|
|
55
|
+
const result = exportMkdocsProject(pages, nav, {
|
|
56
|
+
siteName: opts.siteName ?? "Documentation",
|
|
57
|
+
siteUrl: opts.siteUrl,
|
|
58
|
+
repoUrl: opts.repoUrl,
|
|
59
|
+
theme: mkdocsTheme(opts),
|
|
60
|
+
});
|
|
61
|
+
for (const [filePath, content] of result.files) {
|
|
62
|
+
const fullPath = join(output, filePath);
|
|
63
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
64
|
+
writeFileSync(fullPath, content);
|
|
65
|
+
}
|
|
66
|
+
console.log(`Generated ${result.files.size} MkDocs files`);
|
|
67
|
+
// Mounted asset dirs, copied under docs/ at the ROOT their refs were
|
|
68
|
+
// rewritten to (`_assets` for an imported corpus, `images`/`assets` for
|
|
69
|
+
// hand-authored content). The serializer emits those refs relative to
|
|
70
|
+
// each page, so the file has to land exactly there or every image 404s
|
|
71
|
+
// with a clean exit code.
|
|
72
|
+
const mounts = opts.assetMounts;
|
|
73
|
+
for (const mount of mounts ?? []) {
|
|
74
|
+
if (!existsSync(mount.dir))
|
|
75
|
+
continue;
|
|
76
|
+
const root = mount.root ?? "_assets";
|
|
77
|
+
const dest = mount.into ? join(output, "docs", root, mount.into) : join(output, "docs", root);
|
|
78
|
+
mkdirSync(dest, { recursive: true });
|
|
79
|
+
if (mount.mediaOnly) {
|
|
80
|
+
copyMediaTree(mount.dir, dest);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
// Skip symlinks rather than copy them: relative links are rewritten to
|
|
84
|
+
// ABSOLUTE paths into the source checkout, so the output only works on
|
|
85
|
+
// the machine that produced it. AsciiBinder corpora are full of them.
|
|
86
|
+
cpSync(mount.dir, dest, {
|
|
87
|
+
recursive: true,
|
|
88
|
+
dereference: false,
|
|
89
|
+
filter: (src) => !lstatSync(src).isSymbolicLink(),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Co-located media next to the markdown (the mkdocs shape itself).
|
|
93
|
+
const sourceDir = opts.sourceDir;
|
|
94
|
+
if (sourceDir && existsSync(sourceDir))
|
|
95
|
+
copyMediaTree(sourceDir, join(output, "docs"));
|
|
96
|
+
if (!result.ledger.isEmpty) {
|
|
97
|
+
console.log(formatLedgerReport(result.ledger, "mkdocs"));
|
|
98
|
+
}
|
|
30
99
|
},
|
|
31
100
|
};
|
|
101
|
+
const MEDIA_EXT = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp", ".avif", ".mp4", ".webm", ".pdf", ".ico"]);
|
|
102
|
+
/**
|
|
103
|
+
* Copy media files only, preserving the directory shape. Never follows a
|
|
104
|
+
* symlink: AsciiBinder corpora symlink every section back to one images
|
|
105
|
+
* tree, and following them copied a 64 MB tree once per link.
|
|
106
|
+
*/
|
|
107
|
+
function copyMediaTree(from, to) {
|
|
108
|
+
for (const entry of readdirSync(from, { withFileTypes: true })) {
|
|
109
|
+
if (entry.isSymbolicLink())
|
|
110
|
+
continue;
|
|
111
|
+
const src = join(from, entry.name);
|
|
112
|
+
if (entry.isDirectory()) {
|
|
113
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
114
|
+
continue;
|
|
115
|
+
copyMediaTree(src, join(to, entry.name));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!MEDIA_EXT.has(extname(entry.name).toLowerCase()))
|
|
119
|
+
continue;
|
|
120
|
+
// Always copy. A same-size skip saved nothing measurable and silently
|
|
121
|
+
// kept a stale file when a one-character SVG edit left the size unchanged.
|
|
122
|
+
mkdirSync(to, { recursive: true });
|
|
123
|
+
copyFileSync(src, join(to, entry.name));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const MKDOCS_THEMES = new Set(["material", "mkdocs", "readthedocs"]);
|
|
127
|
+
/**
|
|
128
|
+
* The MkDocs theme to write. `--mkdocs-theme` wins; a bare `theme` is honoured
|
|
129
|
+
* only when it names a theme MkDocs actually has, so the Astro preset default
|
|
130
|
+
* ("default") that `convert` passes through can never reach mkdocs.yml.
|
|
131
|
+
*/
|
|
132
|
+
function mkdocsTheme(opts) {
|
|
133
|
+
for (const candidate of [opts.mkdocsTheme, opts.theme]) {
|
|
134
|
+
if (typeof candidate === "string" && MKDOCS_THEMES.has(candidate))
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
137
|
+
return "material";
|
|
138
|
+
}
|
|
32
139
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExportPage, NavItem } from "@dogsbay/types";
|
|
2
|
+
import { ExportLedger } from "@dogsbay/serialize-core";
|
|
2
3
|
/**
|
|
3
4
|
* Options for project export.
|
|
4
5
|
*/
|
|
@@ -18,6 +19,8 @@ export interface MkdocsProjectOptions {
|
|
|
18
19
|
export interface MkdocsProjectExport {
|
|
19
20
|
files: Map<string, string>;
|
|
20
21
|
mkdocsYml: string;
|
|
22
|
+
/** What could not be represented, per page. Empty is the goal, not the assumption. */
|
|
23
|
+
ledger: ExportLedger;
|
|
21
24
|
}
|
|
22
25
|
/**
|
|
23
26
|
* Export a set of pages + navigation to a complete MkDocs project.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"to-mkdocs-project.d.ts","sourceRoot":"","sources":["../../src/export/to-mkdocs-project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"to-mkdocs-project.d.ts","sourceRoot":"","sources":["../../src/export/to-mkdocs-project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAwB,UAAU,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAChF,OAAO,EAAE,YAAY,EAAgC,MAAM,yBAAyB,CAAC;AAGrF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,MAAM,EAAE,YAAY,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EAAE,EACnB,GAAG,EAAE,OAAO,EAAE,EACd,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CAqErB"}
|
|
@@ -1,26 +1,116 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExportLedger, frontmatterBlock, yamlScalar } from "@dogsbay/serialize-core";
|
|
2
|
+
import { treeToMkdocs, hrefToSlugKey } from "./to-mkdocs.js";
|
|
2
3
|
/**
|
|
3
4
|
* Export a set of pages + navigation to a complete MkDocs project.
|
|
4
5
|
* Returns file map (path → content) including mkdocs.yml and all .md files.
|
|
5
6
|
*/
|
|
6
7
|
export function exportMkdocsProject(pages, nav, options) {
|
|
7
8
|
const files = new Map();
|
|
9
|
+
const ledger = new ExportLedger();
|
|
10
|
+
const slugs = new Set(pages.map((p) => p.slug));
|
|
8
11
|
// Generate markdown files
|
|
9
12
|
const allTrees = [];
|
|
10
13
|
for (const page of pages) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
ledger.setPage(page.slug);
|
|
15
|
+
// A slug is a path under docs/. One that climbs out of it (`../x`),
|
|
16
|
+
// starts at the root, or carries a backslash is not a page name, it is a
|
|
17
|
+
// write outside the output tree. Refuse it, on the record.
|
|
18
|
+
if (!isSafeSlug(page.slug)) {
|
|
19
|
+
ledger.record({ feature: "page", action: "dropped", detail: `unsafe slug ${JSON.stringify(page.slug)} — not written` });
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const path = `docs/${page.slug}.md`;
|
|
23
|
+
// Two pages on one slug (the importer strips `/index`, so `a.md` and
|
|
24
|
+
// `a/index.md` both arrive as `a`) used to overwrite silently — last one
|
|
25
|
+
// won, the ledger stayed empty, and the page count said 4 while 3 files
|
|
26
|
+
// existed. Keep the first, record the second.
|
|
27
|
+
if (files.has(path)) {
|
|
28
|
+
ledger.record({ feature: "page", action: "dropped", detail: `duplicate slug ${page.slug} — a second page with this slug was not written` });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const body = treeToMkdocs(page.tree, { pageSlug: page.slug, slugs, ledger });
|
|
32
|
+
// MkDocs reads `title:` from page meta; without it the nav label falls back
|
|
33
|
+
// to the first heading (fine) or the filename (not fine).
|
|
34
|
+
const fm = pageFrontmatter(page, ledger);
|
|
35
|
+
files.set(path, fm ? `${fm}\n\n${body}\n` : `${body}\n`);
|
|
14
36
|
allTrees.push(page.tree);
|
|
15
37
|
}
|
|
38
|
+
ledger.setPage(undefined);
|
|
16
39
|
// Auto-detect required extensions from tree content
|
|
17
40
|
const extensions = detectExtensions(allTrees, options.extraExtensions);
|
|
41
|
+
// Resolve nav against what was WRITTEN, not what was submitted: a page
|
|
42
|
+
// refused above (unsafe slug, duplicate) is still in `slugs`, and a nav
|
|
43
|
+
// entry or the home redirect pointing at it would be a link to a 404.
|
|
44
|
+
const written = new Set([...files.keys()].filter((k) => k.startsWith("docs/") && k.endsWith(".md")).map((k) => k.slice(5, -3)));
|
|
18
45
|
// Convert nav to mkdocs.yml nav format
|
|
19
|
-
const navYaml = navToYaml(nav,
|
|
46
|
+
const navYaml = navToYaml(nav, written, ledger);
|
|
47
|
+
// Nothing at `/`. MkDocs serves `docs/index.md` as the site root and
|
|
48
|
+
// emits NOTHING there otherwise — no 404, no redirect, just no
|
|
49
|
+
// `site/index.html` — so a corpus whose nav starts at `welcome.md` (the
|
|
50
|
+
// OpenShift shape) published a site whose front door was a 404. The
|
|
51
|
+
// Docusaurus exporter closes the same hole with `src/pages/index.js`; this
|
|
52
|
+
// is the MkDocs equivalent: a meta-refresh to the first nav page. Kept out
|
|
53
|
+
// of `nav:` (MkDocs builds unlisted pages without listing them) and out of
|
|
54
|
+
// search.
|
|
55
|
+
if (!files.has("docs/index.md")) {
|
|
56
|
+
const first = firstNavSlug(nav, written);
|
|
57
|
+
if (first) {
|
|
58
|
+
files.set("docs/index.md", homeRedirect(options.siteName, first));
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
ledger.record({ feature: "home-page", action: "dropped", detail: "no index page and no nav entry to redirect to — the site root will 404" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
20
64
|
// Generate mkdocs.yml
|
|
21
65
|
const yml = buildMkdocsYml(options, extensions, navYaml);
|
|
22
66
|
files.set("mkdocs.yml", yml);
|
|
23
|
-
return { files, mkdocsYml: yml };
|
|
67
|
+
return { files, mkdocsYml: yml, ledger };
|
|
68
|
+
}
|
|
69
|
+
function isSafeSlug(slug) {
|
|
70
|
+
if (!slug || slug.startsWith("/") || slug.includes("\\"))
|
|
71
|
+
return false;
|
|
72
|
+
return !slug.split("/").some((seg) => seg === ".." || seg === "." || seg === "");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Frontmatter keys MkDocs Material reads from page meta. Anything else the
|
|
76
|
+
* source carried is recorded as dropped rather than silently discarded — a
|
|
77
|
+
* mkdocs → mkdocs round trip that loses `tags:` looks like success otherwise.
|
|
78
|
+
*/
|
|
79
|
+
const MATERIAL_META = new Set(["tags", "hide", "status", "template", "icon", "search", "subtitle"]);
|
|
80
|
+
function pageFrontmatter(page, ledger) {
|
|
81
|
+
const fm = (page.frontmatter ?? {});
|
|
82
|
+
const entries = [];
|
|
83
|
+
const rawTitle = fm.title ?? page.title;
|
|
84
|
+
const title = typeof rawTitle === "string" ? rawTitle : page.title;
|
|
85
|
+
if (title)
|
|
86
|
+
entries.push(["title", title]);
|
|
87
|
+
if (typeof fm.description === "string")
|
|
88
|
+
entries.push(["description", fm.description]);
|
|
89
|
+
const extra = [];
|
|
90
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
91
|
+
if (key === "title" || key === "description")
|
|
92
|
+
continue;
|
|
93
|
+
if (MATERIAL_META.has(key)) {
|
|
94
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) {
|
|
95
|
+
extra.push(`${key}:\n${value.map((v) => ` - ${yamlScalar(String(v))}`).join("\n")}`);
|
|
96
|
+
}
|
|
97
|
+
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
98
|
+
extra.push(`${key}: ${typeof value === "string" ? yamlScalar(value) : String(value)}`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
ledger.record({ feature: `frontmatter:${key}`, action: "dropped", detail: "unsupported value shape" });
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
ledger.record({ feature: `frontmatter:${key}`, action: "dropped", detail: "no MkDocs equivalent" });
|
|
106
|
+
}
|
|
107
|
+
if (entries.length === 0 && extra.length === 0)
|
|
108
|
+
return "";
|
|
109
|
+
const base = entries.length ? frontmatterBlock(entries) : "";
|
|
110
|
+
if (!extra.length)
|
|
111
|
+
return base;
|
|
112
|
+
const inner = base ? base.replace(/\n---$/, "") : "---";
|
|
113
|
+
return `${inner}\n${extra.join("\n")}\n---`;
|
|
24
114
|
}
|
|
25
115
|
// ── Extension Detection ─────────────────────────────────────────
|
|
26
116
|
/**
|
|
@@ -29,8 +119,12 @@ export function exportMkdocsProject(pages, nav, options) {
|
|
|
29
119
|
*/
|
|
30
120
|
function detectExtensions(allTrees, extra) {
|
|
31
121
|
const found = new Set();
|
|
32
|
-
// Always
|
|
122
|
+
// Always-on. `attr_list` because every heading carries `{ #id }`; the rest
|
|
123
|
+
// are cheap and what the serializer emits assumes them.
|
|
33
124
|
found.add("toc:\n permalink: true");
|
|
125
|
+
found.add("attr_list");
|
|
126
|
+
found.add("md_in_html");
|
|
127
|
+
found.add("tables");
|
|
34
128
|
for (const tree of allTrees) {
|
|
35
129
|
walkForExtensions(tree, found);
|
|
36
130
|
}
|
|
@@ -53,40 +147,92 @@ function walkForExtensions(nodes, found) {
|
|
|
53
147
|
case "tabs":
|
|
54
148
|
case "tab":
|
|
55
149
|
found.add("pymdownx.tabbed:\n alternate_style: true");
|
|
150
|
+
found.add("pymdownx.superfences");
|
|
56
151
|
break;
|
|
57
|
-
case "code":
|
|
152
|
+
case "code":
|
|
58
153
|
found.add("pymdownx.highlight:\n anchor_linenums: true");
|
|
59
154
|
found.add("pymdownx.superfences");
|
|
60
|
-
if (node.props?.hasAnnotations) {
|
|
61
|
-
found.add("pymdownx.superfences");
|
|
62
|
-
}
|
|
63
155
|
break;
|
|
64
|
-
|
|
156
|
+
case "diagram":
|
|
157
|
+
found.add("pymdownx.superfences:\n custom_fences:\n - name: mermaid\n class: mermaid\n format: !!python/name:pymdownx.superfences.fence_code_format");
|
|
158
|
+
break;
|
|
65
159
|
case "deflist":
|
|
66
160
|
case "dt":
|
|
67
161
|
case "dd":
|
|
68
162
|
found.add("def_list");
|
|
69
163
|
break;
|
|
70
|
-
case "html-container":
|
|
71
|
-
found.add("md_in_html");
|
|
72
|
-
break;
|
|
73
164
|
case "task-list":
|
|
74
165
|
case "task-item":
|
|
75
166
|
found.add("pymdownx.tasklist:\n custom_checkbox: true");
|
|
76
167
|
break;
|
|
168
|
+
// Checkboxes are `list-item` nodes with `props.checked` — the shape the
|
|
169
|
+
// parser emits and the serializer reads; there is no `task-item` node.
|
|
170
|
+
case "list-item":
|
|
171
|
+
if (node.props?.checked !== undefined)
|
|
172
|
+
found.add("pymdownx.tasklist:\n custom_checkbox: true");
|
|
173
|
+
break;
|
|
77
174
|
case "math":
|
|
175
|
+
case "math-block":
|
|
78
176
|
case "math-inline":
|
|
79
177
|
found.add("pymdownx.arithmatex:\n generic: true");
|
|
80
178
|
break;
|
|
81
179
|
case "footnote":
|
|
82
|
-
case "footnote-
|
|
180
|
+
case "footnote-list":
|
|
181
|
+
case "footnote-def":
|
|
83
182
|
found.add("footnotes");
|
|
84
183
|
break;
|
|
184
|
+
// The serializer emits `:octicons-arrow-right-24:` on a card link.
|
|
185
|
+
case "card":
|
|
186
|
+
case "cards":
|
|
187
|
+
case "link-card":
|
|
188
|
+
found.add(EMOJI_EXT);
|
|
189
|
+
break;
|
|
190
|
+
// An unrealized include is emitted as `--8<-- "path"`.
|
|
191
|
+
case "include":
|
|
192
|
+
found.add("pymdownx.snippets");
|
|
193
|
+
break;
|
|
85
194
|
}
|
|
195
|
+
if (node.inline)
|
|
196
|
+
walkInlineForExtensions(node.inline, found);
|
|
86
197
|
if (node.children)
|
|
87
198
|
walkForExtensions(node.children, found);
|
|
88
199
|
}
|
|
89
200
|
}
|
|
201
|
+
const EMOJI_EXT = "pymdownx.emoji:\n emoji_index: !!python/name:material.extensions.emoji.twemoji\n emoji_generator: !!python/name:material.extensions.emoji.to_svg";
|
|
202
|
+
/**
|
|
203
|
+
* Inline nodes NEST — a highlight inside a link, a struck span inside bold.
|
|
204
|
+
* Walking one level detected `==x==` at top level and missed the same mark
|
|
205
|
+
* one link deeper, so the extension was absent and the marks rendered as
|
|
206
|
+
* literal `==`.
|
|
207
|
+
*/
|
|
208
|
+
function walkInlineForExtensions(nodes, found) {
|
|
209
|
+
for (const inline of nodes) {
|
|
210
|
+
switch (inline.type) {
|
|
211
|
+
case "footnote-ref":
|
|
212
|
+
found.add("footnotes");
|
|
213
|
+
break;
|
|
214
|
+
case "highlight":
|
|
215
|
+
found.add("pymdownx.mark");
|
|
216
|
+
break;
|
|
217
|
+
case "kbd":
|
|
218
|
+
found.add("pymdownx.keys");
|
|
219
|
+
break;
|
|
220
|
+
case "math":
|
|
221
|
+
found.add("pymdownx.arithmatex:\n generic: true");
|
|
222
|
+
break;
|
|
223
|
+
case "icon":
|
|
224
|
+
found.add(EMOJI_EXT);
|
|
225
|
+
break;
|
|
226
|
+
case "text":
|
|
227
|
+
if (inline.strikethrough)
|
|
228
|
+
found.add("pymdownx.tilde");
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
const kids = inline.children;
|
|
232
|
+
if (kids)
|
|
233
|
+
walkInlineForExtensions(kids, found);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
90
236
|
// ── Nav Conversion ──────────────────────────────────────────────
|
|
91
237
|
/**
|
|
92
238
|
* Convert NavItem[] to mkdocs.yml nav YAML string.
|
|
@@ -95,82 +241,140 @@ function walkForExtensions(nodes, found) {
|
|
|
95
241
|
* - Home: index.md
|
|
96
242
|
* - Section:
|
|
97
243
|
* - Page: path.md
|
|
244
|
+
*
|
|
245
|
+
* An href is matched EXACTLY against the exported slugs (after stripping the
|
|
246
|
+
* origin, query, fragment and slashes). The previous last-segment fuzzy match
|
|
247
|
+
* would map `/installing/index/` to whichever page's slug ended in `index`
|
|
248
|
+
* first — wrong for any corpus with more than one index page.
|
|
98
249
|
*/
|
|
99
|
-
function navToYaml(items,
|
|
250
|
+
function navToYaml(items, slugs, ledger, indent = 2) {
|
|
100
251
|
const lines = [];
|
|
101
252
|
const prefix = " ".repeat(indent);
|
|
102
253
|
for (const item of items) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
lines.push(navToYaml(item.children, pages, indent + 4));
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
// Group without own page
|
|
116
|
-
lines.push(`${prefix}- ${item.label}:`);
|
|
117
|
-
lines.push(navToYaml(item.children, pages, indent + 4));
|
|
118
|
-
}
|
|
254
|
+
const label = yamlScalar(item.label ?? "");
|
|
255
|
+
const target = item.href ? navTarget(item.href, slugs) : null;
|
|
256
|
+
// Children FIRST, so a group whose entries all failed to resolve is
|
|
257
|
+
// skipped rather than emitted as `- Group:` — a null section, which is a
|
|
258
|
+
// hard `mkdocs build` configuration abort, not a warning.
|
|
259
|
+
const inner = item.children?.length ? navToYaml(item.children, slugs, ledger, indent + 4) : "";
|
|
260
|
+
if (item.href && !target) {
|
|
261
|
+
ledger.record({ feature: "nav-entry", action: "dropped", detail: `${item.label ?? ""} → ${item.href}: not an exported page` });
|
|
119
262
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
263
|
+
if (inner) {
|
|
264
|
+
lines.push(`${prefix}- ${label}:`);
|
|
265
|
+
if (target)
|
|
266
|
+
lines.push(`${prefix} - ${label}: ${target}`);
|
|
267
|
+
lines.push(inner);
|
|
268
|
+
}
|
|
269
|
+
else if (target) {
|
|
270
|
+
lines.push(`${prefix}- ${label}: ${target}`);
|
|
271
|
+
}
|
|
272
|
+
else if (item.children?.length) {
|
|
273
|
+
ledger.record({ feature: "nav-entry", action: "dropped", detail: `${item.label ?? ""}: section with no exported pages` });
|
|
126
274
|
}
|
|
127
275
|
}
|
|
128
276
|
return lines.filter(Boolean).join("\n");
|
|
129
277
|
}
|
|
130
278
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
279
|
+
* What a nav href becomes in mkdocs.yml: a `slug.md` for an exported page,
|
|
280
|
+
* the URL itself for an external link (MkDocs supports `- Label: https://…`
|
|
281
|
+
* natively), or null when it names nothing we emit.
|
|
133
282
|
*/
|
|
134
|
-
function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
283
|
+
function navTarget(href, slugs) {
|
|
284
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith("//"))
|
|
285
|
+
return href;
|
|
286
|
+
const slug = hrefToSlug(href, slugs);
|
|
287
|
+
return slug ? `${slug}.md` : null;
|
|
288
|
+
}
|
|
289
|
+
function hrefToSlug(href, slugs) {
|
|
290
|
+
const key = hrefToSlugKey(href);
|
|
291
|
+
if (slugs.has(key))
|
|
292
|
+
return key;
|
|
293
|
+
if (slugs.has(`${key}/index`))
|
|
294
|
+
return `${key}/index`;
|
|
295
|
+
if (key === "" && slugs.has("index"))
|
|
296
|
+
return "index";
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
/** First nav entry that resolves to an exported page, depth-first. */
|
|
300
|
+
function firstNavSlug(nav, slugs) {
|
|
301
|
+
for (const item of nav) {
|
|
302
|
+
if (item.href && !/^[a-z][a-z0-9+.-]*:/i.test(item.href) && !item.href.startsWith("//")) {
|
|
303
|
+
const slug = hrefToSlug(item.href, slugs);
|
|
304
|
+
if (slug)
|
|
305
|
+
return slug;
|
|
142
306
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
307
|
+
if (item.children?.length) {
|
|
308
|
+
const inner = firstNavSlug(item.children, slugs);
|
|
309
|
+
if (inner)
|
|
310
|
+
return inner;
|
|
146
311
|
}
|
|
147
312
|
}
|
|
148
|
-
|
|
149
|
-
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* A root page that forwards to `slug`. The URL is RELATIVE (`welcome/`, or
|
|
317
|
+
* `a/b/` — a trailing `index` stripped, since `use_directory_urls: true`,
|
|
318
|
+
* which the emitted config sets, serves `a/index.md` at `a/`), so it is
|
|
319
|
+
* right under any `site_url` path prefix. The markdown link is the
|
|
320
|
+
* no-refresh fallback and lets `mkdocs build` validate the target — it is
|
|
321
|
+
* also the one MkDocs rewrites if someone later flips `use_directory_urls`;
|
|
322
|
+
* the meta URL is not. Not in `nav:`, so MkDocs still lists it in
|
|
323
|
+
* sitemap.xml (no per-page exclusion exists) and titles it `Site - Site`.
|
|
324
|
+
*/
|
|
325
|
+
function homeRedirect(siteName, slug) {
|
|
326
|
+
const url = slug.replace(/(^|\/)index$/, "$1").replace(/\/?$/, "/");
|
|
327
|
+
// A slug is a path, not an attribute value: encode per segment for the
|
|
328
|
+
// meta URL, and use the `<…>` destination form so a space survives the
|
|
329
|
+
// markdown link.
|
|
330
|
+
const href = url.split("/").map(encodeURIComponent).join("/");
|
|
331
|
+
return [
|
|
332
|
+
"---",
|
|
333
|
+
`title: ${yamlScalar(siteName)}`,
|
|
334
|
+
"search:",
|
|
335
|
+
" exclude: true",
|
|
336
|
+
"---",
|
|
337
|
+
"",
|
|
338
|
+
`<meta http-equiv="refresh" content="0; url=${href}">`,
|
|
339
|
+
"",
|
|
340
|
+
`Redirecting to [${url}](<${slug}.md>)…`,
|
|
341
|
+
"",
|
|
342
|
+
].join("\n");
|
|
150
343
|
}
|
|
151
344
|
// ── YAML Generation ─────────────────────────────────────────────
|
|
152
345
|
function buildMkdocsYml(options, extensions, navYaml) {
|
|
153
346
|
const lines = [];
|
|
154
|
-
lines.push(`site_name: ${options.siteName}`);
|
|
347
|
+
lines.push(`site_name: ${yamlScalar(options.siteName)}`);
|
|
155
348
|
if (options.siteUrl)
|
|
156
|
-
lines.push(`site_url: ${options.siteUrl}`);
|
|
349
|
+
lines.push(`site_url: ${yamlScalar(options.siteUrl)}`);
|
|
157
350
|
if (options.repoUrl)
|
|
158
|
-
lines.push(`repo_url: ${options.repoUrl}`);
|
|
351
|
+
lines.push(`repo_url: ${yamlScalar(options.repoUrl)}`);
|
|
352
|
+
lines.push("use_directory_urls: true");
|
|
159
353
|
lines.push("");
|
|
160
354
|
// Theme
|
|
161
355
|
lines.push("theme:");
|
|
162
356
|
lines.push(` name: ${options.theme ?? "material"}`);
|
|
163
357
|
lines.push(" features:");
|
|
164
358
|
lines.push(" - content.code.copy");
|
|
165
|
-
lines.push(" -
|
|
359
|
+
lines.push(" - content.tabs.link");
|
|
166
360
|
lines.push(" - navigation.tracking");
|
|
361
|
+
// Material renders the WHOLE nav into every page unless told to prune.
|
|
362
|
+
// Measured on OpenShift 4.22 (1,800 pages, 2,100 nav entries): 1 MB per
|
|
363
|
+
// page, 1.9 GB total — over GitHub Pages' 1 GB site limit — with the nav
|
|
364
|
+
// as 95% of every file. `navigation.prune` keeps only the active path;
|
|
365
|
+
// Material's own docs recommend it for sites with hundreds of pages.
|
|
366
|
+
lines.push(" - navigation.prune");
|
|
167
367
|
lines.push(" - navigation.sections");
|
|
168
|
-
lines.push(" -
|
|
368
|
+
lines.push(" - navigation.indexes");
|
|
369
|
+
lines.push(" - navigation.top");
|
|
370
|
+
lines.push(" - toc.follow");
|
|
169
371
|
lines.push("");
|
|
170
372
|
// Nav
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
373
|
+
if (navYaml) {
|
|
374
|
+
lines.push("nav:");
|
|
375
|
+
lines.push(navYaml);
|
|
376
|
+
lines.push("");
|
|
377
|
+
}
|
|
174
378
|
// Extensions
|
|
175
379
|
lines.push("markdown_extensions:");
|
|
176
380
|
for (const ext of extensions) {
|
|
@@ -179,7 +383,7 @@ function buildMkdocsYml(options, extensions, navYaml) {
|
|
|
179
383
|
const extLines = ext.split("\n");
|
|
180
384
|
lines.push(` - ${extLines[0]}`);
|
|
181
385
|
for (let i = 1; i < extLines.length; i++) {
|
|
182
|
-
lines.push(`
|
|
386
|
+
lines.push(` ${extLines[i]}`);
|
|
183
387
|
}
|
|
184
388
|
}
|
|
185
389
|
else {
|