@dogsbay/format-mdx 0.2.0-beta.12 → 0.2.0-beta.121
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/adapter.d.ts +159 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +95 -0
- package/dist/adapter.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +269 -19
- package/dist/cli.js.map +1 -1
- package/dist/extract.d.ts +32 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +152 -0
- package/dist/extract.js.map +1 -0
- package/dist/fumadocs.d.ts +48 -0
- package/dist/fumadocs.d.ts.map +1 -0
- package/dist/fumadocs.js +472 -0
- package/dist/fumadocs.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -1
- package/dist/index.js.map +1 -1
- package/dist/js-scan.d.ts +35 -0
- package/dist/js-scan.d.ts.map +1 -0
- package/dist/js-scan.js +124 -0
- package/dist/js-scan.js.map +1 -0
- package/dist/meta-nav.d.ts +29 -0
- package/dist/meta-nav.d.ts.map +1 -0
- package/dist/meta-nav.js +194 -0
- package/dist/meta-nav.js.map +1 -0
- package/dist/mintlify-nav.d.ts +31 -0
- package/dist/mintlify-nav.d.ts.map +1 -0
- package/dist/mintlify-nav.js +418 -0
- package/dist/mintlify-nav.js.map +1 -0
- package/dist/mintlify-openapi.d.ts +109 -0
- package/dist/mintlify-openapi.d.ts.map +1 -0
- package/dist/mintlify-openapi.js +363 -0
- package/dist/mintlify-openapi.js.map +1 -0
- package/dist/mintlify.d.ts +3 -0
- package/dist/mintlify.d.ts.map +1 -0
- package/dist/mintlify.js +196 -0
- package/dist/mintlify.js.map +1 -0
- package/dist/nav-extract.d.ts +43 -0
- package/dist/nav-extract.d.ts.map +1 -0
- package/dist/nav-extract.js +135 -0
- package/dist/nav-extract.js.map +1 -0
- package/dist/parse.d.ts +100 -4
- package/dist/parse.d.ts.map +1 -1
- package/dist/parse.js +1231 -150
- package/dist/parse.js.map +1 -1
- package/dist/ts-type-block.d.ts +43 -0
- package/dist/ts-type-block.d.ts.map +1 -0
- package/dist/ts-type-block.js +164 -0
- package/dist/ts-type-block.js.map +1 -0
- package/package.json +10 -8
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MDX adapter — extension points for components and conventions a specific
|
|
3
|
+
* site or docs framework layers on top of *generic* MDX.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the proven Docusaurus adapter architecture
|
|
6
|
+
* (format-docusaurus/src/adapter.ts): the generic parser handles only the
|
|
7
|
+
* common Mintlify-style vocabulary (callouts, Tabs, Steps, Cards…); anything
|
|
8
|
+
* a framework or site invented is injected via an adapter and/or per-source
|
|
9
|
+
* declarative component config.
|
|
10
|
+
*
|
|
11
|
+
* Three layers (plans/format-fumadocs.md):
|
|
12
|
+
* A. generic parser + open registry + never-drop `mdx-raw` + fidelity ledger
|
|
13
|
+
* B. framework adapters (e.g. `fumadocs`)
|
|
14
|
+
* C. per-docset declarative config (`as` / `props` / `children` / `handler`)
|
|
15
|
+
*
|
|
16
|
+
* Trust boundary: declarative config and built-in named handlers are safe
|
|
17
|
+
* everywhere (no subscriber-supplied code is ever executed). Arbitrary
|
|
18
|
+
* `handler:` JS modules are NOT supported here — a hosted updater must be
|
|
19
|
+
* able to run any docset config without executing its code.
|
|
20
|
+
*/
|
|
21
|
+
import type { TreeNode } from "@dogsbay/types";
|
|
22
|
+
/** How a component's inner content is treated. */
|
|
23
|
+
export type ChildrenPolicy = "blocks" | "code" | "raw" | "ignore";
|
|
24
|
+
/** Context handed to function-form component handlers. */
|
|
25
|
+
export interface ComponentHandlerContext {
|
|
26
|
+
/** Parse an MDX source fragment into TreeNode[] with the active registry. */
|
|
27
|
+
parseBlocks: (source: string) => TreeNode[];
|
|
28
|
+
/** Append a fidelity-ledger entry. */
|
|
29
|
+
ledger: (entry: Omit<MdxLedgerEntry, "file">) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Same-file `export const` literals (name → raw source text). Lets
|
|
32
|
+
* handlers statically resolve `attr={identifier}` references — the
|
|
33
|
+
* common "data next to the component" MDX pattern. Never executed.
|
|
34
|
+
*/
|
|
35
|
+
moduleData: Record<string, string>;
|
|
36
|
+
}
|
|
37
|
+
export interface ComponentMapping {
|
|
38
|
+
/** TreeNode type to produce (e.g. "callout", "tabs", "endpoint"). */
|
|
39
|
+
type: string;
|
|
40
|
+
/** Extract props from JSX attributes. Bare attributes arrive as "true". */
|
|
41
|
+
propsFromAttrs?: (attrs: Record<string, string>) => Record<string, unknown>;
|
|
42
|
+
/** Whether this component wraps children. */
|
|
43
|
+
container: boolean;
|
|
44
|
+
/** Strip this component entirely (recorded in the ledger). */
|
|
45
|
+
strip?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* How inner content is treated (default "blocks" for containers):
|
|
48
|
+
* - "blocks" — parsed as nested MDX blocks;
|
|
49
|
+
* - "code" — kept as a single code node (a lone inner fence is
|
|
50
|
+
* unwrapped and keeps its language);
|
|
51
|
+
* - "raw" — kept verbatim as an `mdx-raw` child;
|
|
52
|
+
* - "ignore" — dropped (explicit, unlike the old silent swallow).
|
|
53
|
+
*/
|
|
54
|
+
children?: ChildrenPolicy;
|
|
55
|
+
/**
|
|
56
|
+
* Function form — full control over the produced node(s). Return
|
|
57
|
+
* "preserve" to fall back to a byte-exact `mdx-raw` node (+ ledger entry);
|
|
58
|
+
* return null to drop the component (recorded as "stripped").
|
|
59
|
+
* Takes precedence over type/propsFromAttrs/children.
|
|
60
|
+
*/
|
|
61
|
+
toNode?: (attrs: Record<string, string>, innerSource: string, ctx: ComponentHandlerContext) => TreeNode | TreeNode[] | "preserve" | null;
|
|
62
|
+
}
|
|
63
|
+
export interface MdxPreprocessContext {
|
|
64
|
+
/** Absolute path of the source file being parsed. */
|
|
65
|
+
filePath?: string;
|
|
66
|
+
/** Absolute path of the docs root directory. */
|
|
67
|
+
rootDir?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface MdxAdapter {
|
|
70
|
+
/** Adapter name (e.g. "fumadocs"). */
|
|
71
|
+
name: string;
|
|
72
|
+
/** Raw-text rewrites applied BEFORE any parsing. */
|
|
73
|
+
preprocess?: (source: string, ctx: MdxPreprocessContext) => string;
|
|
74
|
+
/** Block-level component mappings, merged over the generic builtins. */
|
|
75
|
+
components?: Record<string, ComponentMapping>;
|
|
76
|
+
/** Tags treated as self-closing when the source omits the `/>`. */
|
|
77
|
+
selfClosingTags?: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Inline component transformations applied to source text before parsing.
|
|
80
|
+
* Each entry: [regex, replacement string or function].
|
|
81
|
+
*/
|
|
82
|
+
inlineTransforms?: [
|
|
83
|
+
RegExp,
|
|
84
|
+
string | ((substring: string, ...args: string[]) => string)
|
|
85
|
+
][];
|
|
86
|
+
}
|
|
87
|
+
export interface MdxLedgerEntry {
|
|
88
|
+
/** Component tag name (e.g. "APIMethod"). */
|
|
89
|
+
component: string;
|
|
90
|
+
/**
|
|
91
|
+
* What happened:
|
|
92
|
+
* - "preserved" — kept byte-exact as `mdx-raw` (unknown or parse-failed);
|
|
93
|
+
* - "stripped" — deliberately dropped by a mapping.
|
|
94
|
+
*/
|
|
95
|
+
action: "preserved" | "stripped";
|
|
96
|
+
/** 1-based line in the (preamble-stripped) source. Best effort. */
|
|
97
|
+
line: number;
|
|
98
|
+
/** Source file, filled in by the importer. */
|
|
99
|
+
file?: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* YAML-friendly component mapping — the per-source `mdx.components:` block.
|
|
103
|
+
* Purely declarative: safe to evaluate for untrusted docset configs.
|
|
104
|
+
*/
|
|
105
|
+
export interface MdxComponentConfig {
|
|
106
|
+
/** TreeNode type to produce (e.g. "table", "endpoint"). */
|
|
107
|
+
as?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Prop mapping: node prop name → JSX attribute name.
|
|
110
|
+
* `{ title: label }` reads attribute `label` into prop `title`.
|
|
111
|
+
*/
|
|
112
|
+
props?: Record<string, string>;
|
|
113
|
+
/** Children policy (see ComponentMapping.children). */
|
|
114
|
+
children?: ChildrenPolicy;
|
|
115
|
+
/**
|
|
116
|
+
* Built-in named handler (e.g. "api-method"). Resolved from the handler
|
|
117
|
+
* registry shipped with this package — never a user-supplied module.
|
|
118
|
+
*/
|
|
119
|
+
handler?: string;
|
|
120
|
+
/**
|
|
121
|
+
* HTML template with `{attr}` placeholders (attribute values are
|
|
122
|
+
* HTML-escaped on interpolation). For simple presentational
|
|
123
|
+
* components: `'<p><a href="https://…/{url}">Open ↗</a></p>'`.
|
|
124
|
+
* Adds no capability MDX content doesn't already have (raw HTML).
|
|
125
|
+
*/
|
|
126
|
+
html?: string;
|
|
127
|
+
/**
|
|
128
|
+
* Map a component whose data lives in a separate TS/TSX file
|
|
129
|
+
* (statically parsed, never executed). `file` resolves relative to
|
|
130
|
+
* the source directory. Requires the CLI import context (file I/O);
|
|
131
|
+
* plain compileComponentConfig callers must pass an extractResolver.
|
|
132
|
+
*/
|
|
133
|
+
extract?: {
|
|
134
|
+
file: string;
|
|
135
|
+
export: string;
|
|
136
|
+
as: "cards" | "table";
|
|
137
|
+
};
|
|
138
|
+
/** Strip the component entirely. */
|
|
139
|
+
strip?: boolean;
|
|
140
|
+
/** Treat as self-closing. */
|
|
141
|
+
selfClosing?: boolean;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Compile declarative per-docset config into ComponentMapping entries.
|
|
145
|
+
*
|
|
146
|
+
* @param handlers registry of built-in named handlers (tag-independent);
|
|
147
|
+
* unknown handler names throw — a config typo must not silently
|
|
148
|
+
* degrade into the default mapping.
|
|
149
|
+
*/
|
|
150
|
+
export declare function compileComponentConfig(config: Record<string, MdxComponentConfig>, handlers?: Record<string, ComponentMapping>,
|
|
151
|
+
/**
|
|
152
|
+
* Resolver for `extract:` entries (needs file I/O — supplied by the
|
|
153
|
+
* CLI import layer). Without one, extract entries throw.
|
|
154
|
+
*/
|
|
155
|
+
extractResolver?: (spec: NonNullable<MdxComponentConfig["extract"]>, tag: string) => ComponentMapping): {
|
|
156
|
+
components: Record<string, ComponentMapping>;
|
|
157
|
+
selfClosingTags: string[];
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,kDAAkD;AAClD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAElE,0DAA0D;AAC1D,MAAM,WAAW,uBAAuB;IACtC,6EAA6E;IAC7E,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;IAC5C,sCAAsC;IACtC,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;IACtD;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5E,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CACP,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC7B,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,uBAAuB,KACzB,QAAQ,GAAG,QAAQ,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC;CAChD;AAED,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,KAAK,MAAM,CAAC;IACnE,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,mEAAmE;IACnE,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE;QACjB,MAAM;QACN,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;KAC5D,EAAE,CAAC;CACL;AAID,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,MAAM,EAAE,WAAW,GAAG,UAAU,CAAC;IACjC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAID;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE,CAAC;IAClE,oCAAoC;IACpC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,6BAA6B;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAC1C,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAM;AAC/C;;;GAGG;AACH,eAAe,CAAC,EAAE,CAChB,IAAI,EAAE,WAAW,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC,EAChD,GAAG,EAAE,MAAM,KACR,gBAAgB,GACpB;IAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAAC,eAAe,EAAE,MAAM,EAAE,CAAA;CAAE,CAwE7E"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MDX adapter — extension points for components and conventions a specific
|
|
3
|
+
* site or docs framework layers on top of *generic* MDX.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the proven Docusaurus adapter architecture
|
|
6
|
+
* (format-docusaurus/src/adapter.ts): the generic parser handles only the
|
|
7
|
+
* common Mintlify-style vocabulary (callouts, Tabs, Steps, Cards…); anything
|
|
8
|
+
* a framework or site invented is injected via an adapter and/or per-source
|
|
9
|
+
* declarative component config.
|
|
10
|
+
*
|
|
11
|
+
* Three layers (plans/format-fumadocs.md):
|
|
12
|
+
* A. generic parser + open registry + never-drop `mdx-raw` + fidelity ledger
|
|
13
|
+
* B. framework adapters (e.g. `fumadocs`)
|
|
14
|
+
* C. per-docset declarative config (`as` / `props` / `children` / `handler`)
|
|
15
|
+
*
|
|
16
|
+
* Trust boundary: declarative config and built-in named handlers are safe
|
|
17
|
+
* everywhere (no subscriber-supplied code is ever executed). Arbitrary
|
|
18
|
+
* `handler:` JS modules are NOT supported here — a hosted updater must be
|
|
19
|
+
* able to run any docset config without executing its code.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Compile declarative per-docset config into ComponentMapping entries.
|
|
23
|
+
*
|
|
24
|
+
* @param handlers registry of built-in named handlers (tag-independent);
|
|
25
|
+
* unknown handler names throw — a config typo must not silently
|
|
26
|
+
* degrade into the default mapping.
|
|
27
|
+
*/
|
|
28
|
+
export function compileComponentConfig(config, handlers = {},
|
|
29
|
+
/**
|
|
30
|
+
* Resolver for `extract:` entries (needs file I/O — supplied by the
|
|
31
|
+
* CLI import layer). Without one, extract entries throw.
|
|
32
|
+
*/
|
|
33
|
+
extractResolver) {
|
|
34
|
+
const components = {};
|
|
35
|
+
const selfClosingTags = [];
|
|
36
|
+
for (const [tag, entry] of Object.entries(config)) {
|
|
37
|
+
if (entry.selfClosing)
|
|
38
|
+
selfClosingTags.push(tag);
|
|
39
|
+
if (entry.extract) {
|
|
40
|
+
if (!extractResolver) {
|
|
41
|
+
throw new Error(`mdx component <${tag}> uses extract:, which requires the CLI ` +
|
|
42
|
+
`import context (file access)`);
|
|
43
|
+
}
|
|
44
|
+
components[tag] = extractResolver(entry.extract, tag);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (entry.html) {
|
|
48
|
+
const template = entry.html;
|
|
49
|
+
components[tag] = {
|
|
50
|
+
type: "html",
|
|
51
|
+
container: true,
|
|
52
|
+
toNode: (attrs) => ({
|
|
53
|
+
type: "html",
|
|
54
|
+
html: template.replace(/\{(\w+)\}/g, (_, name) => String(attrs[name] ?? "")
|
|
55
|
+
.replace(/&/g, "&")
|
|
56
|
+
.replace(/</g, "<")
|
|
57
|
+
.replace(/>/g, ">")
|
|
58
|
+
.replace(/"/g, """)),
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (entry.handler) {
|
|
64
|
+
const handler = handlers[entry.handler];
|
|
65
|
+
if (!handler) {
|
|
66
|
+
throw new Error(`Unknown mdx component handler "${entry.handler}" for <${tag}>. ` +
|
|
67
|
+
`Available: ${Object.keys(handlers).join(", ") || "(none)"}`);
|
|
68
|
+
}
|
|
69
|
+
components[tag] = handler;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (entry.strip) {
|
|
73
|
+
components[tag] = { type: "", container: true, strip: true };
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const propsMap = entry.props;
|
|
77
|
+
components[tag] = {
|
|
78
|
+
type: entry.as ?? "",
|
|
79
|
+
container: true,
|
|
80
|
+
children: entry.children,
|
|
81
|
+
propsFromAttrs: propsMap
|
|
82
|
+
? (attrs) => {
|
|
83
|
+
const props = {};
|
|
84
|
+
for (const [propName, attrName] of Object.entries(propsMap)) {
|
|
85
|
+
if (attrs[attrName] !== undefined)
|
|
86
|
+
props[propName] = attrs[attrName];
|
|
87
|
+
}
|
|
88
|
+
return props;
|
|
89
|
+
}
|
|
90
|
+
: undefined,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { components, selfClosingTags };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAiC,MAAM,gBAAgB,CAAC;AAialF,eAAO,MAAM,MAAM,EAAE,YAapB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -1,37 +1,181 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { join, relative } from "node:path";
|
|
2
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
4
|
import { parseMeta } from "@dogsbay/types";
|
|
5
|
-
import {
|
|
5
|
+
import { parseMdx, extractHeadings } from "./parse.js";
|
|
6
|
+
import { compileComponentConfig, } from "./adapter.js";
|
|
7
|
+
import { MDX_BUILTIN_HANDLERS } from "./fumadocs.js";
|
|
8
|
+
import { buildFumadocsNav, detectFumadocsDir } from "./meta-nav.js";
|
|
9
|
+
import { buildMintlifyNav, detectMintlifyDir } from "./mintlify-nav.js";
|
|
10
|
+
import { mintlifyOpenApiSpecRef, buildMintlifyEndpointMap, readOpenApiSpecSource, collectMintlifyOpenApiSpecRefs, collectMintlifyOpenApiNavPages, openApiPageKey, openApiPageSpecRef, } from "./mintlify-openapi.js";
|
|
11
|
+
import { mintlifyAdapter } from "./mintlify.js";
|
|
12
|
+
import { mintlifyRedirects } from "./mintlify-nav.js";
|
|
13
|
+
import { extractNavFromTsxFile } from "./nav-extract.js";
|
|
14
|
+
import { makeExtractMapping } from "./extract.js";
|
|
6
15
|
/**
|
|
7
|
-
* Detect
|
|
8
|
-
*
|
|
9
|
-
*
|
|
16
|
+
* Detect an MDX docs directory. Two shapes:
|
|
17
|
+
* - a pulled site: nav.json (written by `dogsbay pull`) + .md files;
|
|
18
|
+
* - a Fumadocs content directory: meta.json + .mdx/.md pages.
|
|
10
19
|
*/
|
|
11
20
|
function detectMdxDir(path) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
if (existsSync(join(path, "nav.json")) && hasMdFiles(path))
|
|
22
|
+
return true;
|
|
23
|
+
if (detectMintlifyDir(path))
|
|
24
|
+
return true;
|
|
25
|
+
return detectFumadocsDir(path);
|
|
15
26
|
}
|
|
16
27
|
function hasMdFiles(dir) {
|
|
17
28
|
try {
|
|
18
29
|
const entries = readdirSync(dir);
|
|
19
|
-
return entries.some((e) => e.endsWith(".md") && e !== "llms.txt");
|
|
30
|
+
return entries.some((e) => (e.endsWith(".md") || e.endsWith(".mdx")) && e !== "llms.txt");
|
|
20
31
|
}
|
|
21
32
|
catch {
|
|
22
33
|
return false;
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
|
-
* Import a directory of
|
|
37
|
+
* Import a directory of MDX files into ExportPage[] + NavItem[].
|
|
27
38
|
*/
|
|
28
39
|
async function importMdxDir(source, opts) {
|
|
29
|
-
//
|
|
40
|
+
// Parse options: adapter instance + declarative per-source components.
|
|
41
|
+
const parseOpts = {};
|
|
42
|
+
if (opts.adapterInstance) {
|
|
43
|
+
parseOpts.adapter = opts.adapterInstance;
|
|
44
|
+
}
|
|
45
|
+
else if (detectMintlifyDir(source)) {
|
|
46
|
+
// A Mintlify CONFIG — not merely a file called `docs.json`, which TypeDoc
|
|
47
|
+
// also emits. `detectMintlifyDir` exists precisely to make that
|
|
48
|
+
// distinction (it requires `navigation`, `anchors`, or a mintlify
|
|
49
|
+
// `$schema`), and keying off bare file existence bypassed it: a Fumadocs
|
|
50
|
+
// corpus that happened to ship TypeDoc output got Mintlify's component map
|
|
51
|
+
// applied silently.
|
|
52
|
+
//
|
|
53
|
+
// Default the adapter instead of making the user pass `--adapter mintlify`
|
|
54
|
+
// to get their own components mapped.
|
|
55
|
+
//
|
|
56
|
+
// Without this, importing Mintlify's own docs left every <HeroCard>,
|
|
57
|
+
// <Update>, <ParamField> and <Expandable> unmapped: preserved as mdx-raw
|
|
58
|
+
// and rendered as CODE BLOCKS of their own JSX on the built site. The
|
|
59
|
+
// never-drop policy meant nothing was lost, but the page looked broken.
|
|
60
|
+
// An explicit `--adapter` still wins.
|
|
61
|
+
parseOpts.adapter = mintlifyAdapter;
|
|
62
|
+
}
|
|
63
|
+
// Redirects the source itself declares. A docs site links to its own old
|
|
64
|
+
// paths and relies on the live redirect; after import nothing serves it, so
|
|
65
|
+
// the link 404s unless it is rewritten here.
|
|
66
|
+
const linkRedirects = mintlifyRedirects(source);
|
|
67
|
+
if (Object.keys(linkRedirects).length > 0)
|
|
68
|
+
parseOpts.linkRedirects = linkRedirects;
|
|
69
|
+
if (opts.componentsConfig) {
|
|
70
|
+
const compiled = compileComponentConfig(opts.componentsConfig, MDX_BUILTIN_HANDLERS,
|
|
71
|
+
// extract: data files resolve like sidebar files — absolute paths
|
|
72
|
+
// arrive pre-rebased (site build stages the source), relative ones
|
|
73
|
+
// resolve against the source dir (the convert path).
|
|
74
|
+
(spec) => makeExtractMapping(spec, (file) => isAbsolute(file) ? file : join(source, file)));
|
|
75
|
+
parseOpts.components = compiled.components;
|
|
76
|
+
parseOpts.selfClosingTags = compiled.selfClosingTags;
|
|
77
|
+
}
|
|
78
|
+
// Nav: hardcoded-page-tree extract > nav.json (pulled sites) >
|
|
79
|
+
// Fumadocs meta.json walk.
|
|
80
|
+
const hrefPrefix = typeof opts.hrefPrefix === "string" ? opts.hrefPrefix : "";
|
|
30
81
|
const navPath = join(source, "nav.json");
|
|
31
|
-
|
|
32
|
-
|
|
82
|
+
let nav = null;
|
|
83
|
+
const sidebarExtract = opts.sidebarExtract;
|
|
84
|
+
if (sidebarExtract) {
|
|
85
|
+
// Absolute file paths arrive pre-resolved (the site-build path
|
|
86
|
+
// rebases them onto the ORIGINAL source dir — the staged copy the
|
|
87
|
+
// plugin sees can't reach ../ siblings); relative ones resolve
|
|
88
|
+
// against the source dir (the convert path).
|
|
89
|
+
const sidebarFile = isAbsolute(sidebarExtract.file)
|
|
90
|
+
? sidebarExtract.file
|
|
91
|
+
: join(source, sidebarExtract.file);
|
|
92
|
+
nav = extractNavFromTsxFile(sidebarFile, {
|
|
93
|
+
exportName: sidebarExtract.export,
|
|
94
|
+
stripPrefix: sidebarExtract.stripPrefix,
|
|
95
|
+
hrefPrefix,
|
|
96
|
+
});
|
|
97
|
+
if (!nav) {
|
|
98
|
+
console.warn(`mdx: sidebar extraction from ${sidebarExtract.file} failed — ` +
|
|
99
|
+
`falling back to meta.json nav`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!nav && existsSync(navPath)) {
|
|
103
|
+
nav = JSON.parse(readFileSync(navPath, "utf-8"));
|
|
104
|
+
}
|
|
105
|
+
// Mintlify source-pull: nav is the authoritative `docs.json` / `mint.json`.
|
|
106
|
+
if (!nav) {
|
|
107
|
+
nav = buildMintlifyNav(source, { hrefPrefix });
|
|
108
|
+
}
|
|
109
|
+
if (!nav) {
|
|
110
|
+
nav = buildFumadocsNav(source, { hrefPrefix });
|
|
111
|
+
}
|
|
112
|
+
// Mintlify OpenAPI pages: an `openapi: <method> <path>` frontmatter page has
|
|
113
|
+
// no body — its content is generated from the spec in docs.json (api.openapi).
|
|
114
|
+
// Resolve the spec once into a (method+path → endpoint node) map so those
|
|
115
|
+
// pages import as real endpoint pages instead of blank.
|
|
116
|
+
const specRef = mintlifyOpenApiSpecRef(source);
|
|
117
|
+
// Specs named by INDIVIDUAL PAGES (`openapi: "/openapi.json GET /x"`). A site
|
|
118
|
+
// whose docs.json declares no `api.openapi` still tells us, page by page,
|
|
119
|
+
// exactly which spec to read — Mintlify's own docs do precisely this, and
|
|
120
|
+
// ignoring it cost 100 pages their endpoint content (20 of them entirely
|
|
121
|
+
// blank) with the spec sitting in the repo root. Scanned up front so each
|
|
122
|
+
// distinct spec is parsed once, not per page.
|
|
123
|
+
const pageSpecRefs = new Set();
|
|
124
|
+
const preScan = walkMdFiles(source, source);
|
|
125
|
+
for (const { filePath } of preScan) {
|
|
126
|
+
const fm = extractFrontmatter(readFileSync(filePath, "utf-8"));
|
|
127
|
+
const ref = openApiPageSpecRef(source, fm?.openapi);
|
|
128
|
+
if (ref && ref !== specRef)
|
|
129
|
+
pageSpecRefs.add(ref);
|
|
130
|
+
}
|
|
131
|
+
// Preserve the ORIGINAL spec for exporters that consume one directly. The
|
|
132
|
+
// spec is a canonical source artifact; rebuilding it from endpoint TreeNodes
|
|
133
|
+
// is lossy, so pass the real thing through when we have it.
|
|
134
|
+
const allSpecRefs = collectMintlifyOpenApiSpecRefs(source, [...pageSpecRefs]);
|
|
135
|
+
if (allSpecRefs.length > 0) {
|
|
136
|
+
const preserved = [];
|
|
137
|
+
for (const ref of allSpecRefs) {
|
|
138
|
+
const spec = await readOpenApiSpecSource(ref);
|
|
139
|
+
if (spec) {
|
|
140
|
+
preserved.push({ ...spec, ref });
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// A failed fetch must NOT look like "this source had no spec" — that
|
|
144
|
+
// silently downgrades the export to lossy reconstruction and the
|
|
145
|
+
// ledger then reports the wrong reason.
|
|
146
|
+
console.warn(` warning: could not read the OpenAPI spec at ${ref} — export will ` +
|
|
147
|
+
`reconstruct it from endpoint nodes instead (lossy: $ref components, ` +
|
|
148
|
+
`security schemes and callbacks are lost).`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (preserved.length > 0)
|
|
152
|
+
opts.sourceOpenApiSpecs = preserved;
|
|
153
|
+
}
|
|
154
|
+
const endpointResult = specRef
|
|
155
|
+
? await buildMintlifyEndpointMap(specRef)
|
|
156
|
+
: { map: new Map(), loadError: null };
|
|
157
|
+
const endpointMap = endpointResult.map;
|
|
158
|
+
// One endpoint map per page-declared spec, keyed by resolved spec path.
|
|
159
|
+
const pageSpecMaps = new Map();
|
|
160
|
+
for (const ref of pageSpecRefs) {
|
|
161
|
+
const result = await buildMintlifyEndpointMap(ref);
|
|
162
|
+
if (result.loadError) {
|
|
163
|
+
console.warn(` warning: failed to load OpenAPI spec at ${ref} (named by page ` +
|
|
164
|
+
`frontmatter): ${result.loadError} — those pages import without ` +
|
|
165
|
+
`endpoint content.`);
|
|
166
|
+
}
|
|
167
|
+
pageSpecMaps.set(ref, result.map);
|
|
168
|
+
}
|
|
169
|
+
if (endpointResult.loadError) {
|
|
170
|
+
console.warn(` warning: failed to load OpenAPI spec from ${specRef}: ` +
|
|
171
|
+
`${endpointResult.loadError} — API pages import without endpoint content.`);
|
|
172
|
+
}
|
|
173
|
+
// Walk all .md/.mdx files
|
|
33
174
|
const mdFiles = walkMdFiles(source, source);
|
|
34
175
|
const pages = [];
|
|
176
|
+
const ledger = [];
|
|
177
|
+
let openApiResolved = 0;
|
|
178
|
+
let openApiMissed = 0;
|
|
35
179
|
// Custom taxonomy names from dogsbay.config.yml's `taxonomies:` block.
|
|
36
180
|
const taxonomyNames = Array.isArray(opts.taxonomyNames)
|
|
37
181
|
? opts.taxonomyNames
|
|
@@ -39,7 +183,34 @@ async function importMdxDir(source, opts) {
|
|
|
39
183
|
for (const { filePath, slug } of mdFiles) {
|
|
40
184
|
const content = readFileSync(filePath, "utf-8");
|
|
41
185
|
const frontmatter = extractFrontmatter(content);
|
|
42
|
-
const
|
|
186
|
+
const result = parseMdx(content, {
|
|
187
|
+
...parseOpts,
|
|
188
|
+
file: relative(source, filePath),
|
|
189
|
+
// Needed to resolve `import X from "/snippets/foo.mdx"` — Mintlify
|
|
190
|
+
// resolves a leading `/` against the DOCS ROOT, not the filesystem root.
|
|
191
|
+
rootDir: source,
|
|
192
|
+
});
|
|
193
|
+
let tree = result.tree;
|
|
194
|
+
ledger.push(...result.ledger);
|
|
195
|
+
// Mintlify OpenAPI page: append the spec-generated endpoint node. The MDX
|
|
196
|
+
// body (usually empty, sometimes an intro paragraph) renders before it.
|
|
197
|
+
const oaKey = openApiPageKey(frontmatter?.openapi);
|
|
198
|
+
if (oaKey) {
|
|
199
|
+
// The page's OWN spec wins when it names one — a site can reference
|
|
200
|
+
// several specs (mintlify-docs ships openapi.json, admin-openapi.json,
|
|
201
|
+
// analytics.openapi.json and discovery-openapi.json), so the config-level
|
|
202
|
+
// map is the fallback, not the authority.
|
|
203
|
+
const ownRef = openApiPageSpecRef(source, frontmatter?.openapi);
|
|
204
|
+
const endpoint = (ownRef ? pageSpecMaps.get(ownRef)?.get(oaKey) : undefined) ??
|
|
205
|
+
endpointMap.get(oaKey);
|
|
206
|
+
if (endpoint) {
|
|
207
|
+
tree = [...tree, ...endpoint];
|
|
208
|
+
openApiResolved++;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
openApiMissed++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
43
214
|
const headings = extractHeadings(tree);
|
|
44
215
|
// Extract title: prefer frontmatter, fall back to first h1
|
|
45
216
|
const titleNode = tree.find((n) => n.type === "heading" && n.props?.level === 1);
|
|
@@ -49,6 +220,44 @@ async function importMdxDir(source, opts) {
|
|
|
49
220
|
const meta = parseMeta(frontmatter, { slug, taxonomyNames });
|
|
50
221
|
pages.push({ slug, title, tree, headings, frontmatter, meta });
|
|
51
222
|
}
|
|
223
|
+
// Nav-referenced OpenAPI pages: `METHOD /path` nav strings under a container
|
|
224
|
+
// with an `openapi` spec exist in no .mdx file — generate them so the nav
|
|
225
|
+
// links resolve (ollama's "Endpoints" group). Their slugs match the nav hrefs
|
|
226
|
+
// (both via openApiRefSlug); skip any that collide with a real .mdx page.
|
|
227
|
+
const existingSlugs = new Set(pages.map((p) => p.slug));
|
|
228
|
+
const navOa = await collectMintlifyOpenApiNavPages(source);
|
|
229
|
+
let navOaAdded = 0;
|
|
230
|
+
for (const page of navOa.pages) {
|
|
231
|
+
if (existingSlugs.has(page.slug))
|
|
232
|
+
continue;
|
|
233
|
+
existingSlugs.add(page.slug);
|
|
234
|
+
pages.push(page);
|
|
235
|
+
navOaAdded++;
|
|
236
|
+
}
|
|
237
|
+
if (navOa.loadError && navOaAdded === 0) {
|
|
238
|
+
console.warn(` warning: failed to load nav-referenced OpenAPI spec: ${navOa.loadError}`);
|
|
239
|
+
}
|
|
240
|
+
// Report OpenAPI page resolution — never silently leave API pages blank.
|
|
241
|
+
const totalOa = openApiResolved + navOaAdded;
|
|
242
|
+
if (totalOa > 0 || openApiMissed > 0) {
|
|
243
|
+
console.log(` Mintlify OpenAPI: resolved ${totalOa} endpoint page(s)` +
|
|
244
|
+
(specRef ? ` from ${specRef}` : ""));
|
|
245
|
+
}
|
|
246
|
+
if (openApiMissed > 0) {
|
|
247
|
+
const cause = !specRef && pageSpecRefs.size === 0
|
|
248
|
+
? " (no api.openapi spec found in docs.json/mint.json, and no page named its own spec)"
|
|
249
|
+
: endpointResult.loadError
|
|
250
|
+
? " (the spec failed to load — see the warning above)"
|
|
251
|
+
: "";
|
|
252
|
+
console.warn(` warning: ${openApiMissed} page(s) with \`openapi:\` frontmatter had no ` +
|
|
253
|
+
`matching operation in the spec${cause}` +
|
|
254
|
+
" — those pages import without endpoint content.");
|
|
255
|
+
}
|
|
256
|
+
// Fidelity ledger: expose programmatically; print with --report.
|
|
257
|
+
opts.importLedger = ledger;
|
|
258
|
+
if (opts.report && ledger.length > 0) {
|
|
259
|
+
printLedgerReport(ledger);
|
|
260
|
+
}
|
|
52
261
|
// Attach metadata for exporters to consume
|
|
53
262
|
const llmsTxtPath = join(source, "llms.txt");
|
|
54
263
|
if (existsSync(llmsTxtPath)) {
|
|
@@ -60,6 +269,34 @@ async function importMdxDir(source, opts) {
|
|
|
60
269
|
}
|
|
61
270
|
return { pages, nav };
|
|
62
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* Print the fidelity ledger grouped by component: what was preserved
|
|
274
|
+
* as raw MDX (candidates for a component mapping) and what a mapping
|
|
275
|
+
* deliberately stripped. Mirrors the import-ditamap --report pattern.
|
|
276
|
+
*/
|
|
277
|
+
function printLedgerReport(ledger) {
|
|
278
|
+
const byComponent = new Map();
|
|
279
|
+
for (const entry of ledger) {
|
|
280
|
+
const key = `${entry.component} ${entry.action}`;
|
|
281
|
+
const agg = byComponent.get(key) ?? {
|
|
282
|
+
action: entry.action,
|
|
283
|
+
count: 0,
|
|
284
|
+
files: new Set(),
|
|
285
|
+
};
|
|
286
|
+
agg.count++;
|
|
287
|
+
if (entry.file)
|
|
288
|
+
agg.files.add(entry.file);
|
|
289
|
+
byComponent.set(key, agg);
|
|
290
|
+
}
|
|
291
|
+
console.log("\nMDX fidelity ledger:");
|
|
292
|
+
const sorted = [...byComponent.entries()].sort((a, b) => b[1].count - a[1].count);
|
|
293
|
+
for (const [key, agg] of sorted) {
|
|
294
|
+
const component = key.split(" ")[0];
|
|
295
|
+
console.log(` <${component}> ${agg.action} ×${agg.count} (${agg.files.size} file${agg.files.size === 1 ? "" : "s"})`);
|
|
296
|
+
}
|
|
297
|
+
console.log(" Preserved components survive byte-exact as raw MDX. Map them via an\n" +
|
|
298
|
+
" adapter or the per-source mdx.components config to convert them.");
|
|
299
|
+
}
|
|
63
300
|
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
64
301
|
/**
|
|
65
302
|
* Lift YAML frontmatter from the raw markdown source. Returns
|
|
@@ -85,8 +322,8 @@ function extractFrontmatter(source) {
|
|
|
85
322
|
}
|
|
86
323
|
}
|
|
87
324
|
/**
|
|
88
|
-
* Recursively find all .md files in a directory.
|
|
89
|
-
* Returns slug (relative path without
|
|
325
|
+
* Recursively find all .md/.mdx files in a directory.
|
|
326
|
+
* Returns slug (relative path without extension) and absolute file path.
|
|
90
327
|
*/
|
|
91
328
|
function walkMdFiles(dir, root) {
|
|
92
329
|
const results = [];
|
|
@@ -95,11 +332,19 @@ function walkMdFiles(dir, root) {
|
|
|
95
332
|
const fullPath = join(dir, entry);
|
|
96
333
|
const stat = statSync(fullPath);
|
|
97
334
|
if (stat.isDirectory()) {
|
|
335
|
+
// Dot-directories are TOOLING, not documentation. Mintlify's own docs
|
|
336
|
+
// carry `.claude/skills/**/SKILL.md`, `.mintlify/AGENTS.md` and
|
|
337
|
+
// `.github/pull_request_template.md`; all 20 were imported as pages and
|
|
338
|
+
// landed in the exported vault as `CLAUDE.md`, `AGENTS.md` and
|
|
339
|
+
// `SKILL.md` beside the real docs. `node_modules` likewise.
|
|
340
|
+
if (entry.startsWith(".") || entry === "node_modules")
|
|
341
|
+
continue;
|
|
98
342
|
results.push(...walkMdFiles(fullPath, root));
|
|
99
343
|
}
|
|
100
|
-
else if (entry.endsWith(".md")
|
|
344
|
+
else if ((entry.endsWith(".md") || entry.endsWith(".mdx")) &&
|
|
345
|
+
entry !== "llms.txt") {
|
|
101
346
|
const rel = relative(root, fullPath);
|
|
102
|
-
const slug = rel.replace(/\.md$/, "").replace(/\\/g, "/");
|
|
347
|
+
const slug = rel.replace(/\.(md|mdx)$/, "").replace(/\\/g, "/");
|
|
103
348
|
results.push({ filePath: fullPath, slug });
|
|
104
349
|
}
|
|
105
350
|
}
|
|
@@ -110,7 +355,12 @@ export const plugin = {
|
|
|
110
355
|
canImport: true,
|
|
111
356
|
canExport: false,
|
|
112
357
|
detectSource: detectMdxDir,
|
|
113
|
-
importOptions: [
|
|
358
|
+
importOptions: [
|
|
359
|
+
{ flags: "--adapter <name>", description: "MDX framework adapter (e.g. fumadocs)" },
|
|
360
|
+
{ flags: "--components <path>", description: "YAML/JSON file with declarative component mappings" },
|
|
361
|
+
{ flags: "--sidebar <path[#export]>", description: "Statically extract a hand-written page tree" },
|
|
362
|
+
{ flags: "--report", description: "Print the fidelity ledger (preserved/stripped components)" },
|
|
363
|
+
],
|
|
114
364
|
exportOptions: [],
|
|
115
365
|
import: importMdxDir,
|
|
116
366
|
};
|