ardo 3.1.0 → 3.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -19
- package/dist/DocPage-CIBiCAxZ.js +1010 -0
- package/dist/DocPage-CIBiCAxZ.js.map +1 -0
- package/dist/assets/src/ui/Breadcrumb.css.ts.vanilla-Dpgq-C_p.css +20 -0
- package/dist/assets/src/ui/DocPage.css.ts.vanilla-CXKuz4U-.css +34 -0
- package/dist/assets/src/ui/Footer.css.ts.vanilla-BSzPIPt4.css +100 -0
- package/dist/assets/src/ui/Header.css.ts.vanilla-8QL0Jzgk.css +156 -0
- package/dist/assets/src/ui/Layout.css.ts.vanilla-Bpx_-gJt.css +67 -0
- package/dist/assets/src/ui/Nav.css.ts.vanilla-CsAQjogy.css +51 -0
- package/dist/assets/src/ui/Sidebar.css.ts.vanilla-D70qXTEr.css +115 -0
- package/dist/assets/src/ui/Toc.css.ts.vanilla-CYqcWgvD.css +52 -0
- package/dist/assets/src/ui/components/ApiItem.css.ts.vanilla-B_DW-1iJ.css +218 -0
- package/dist/assets/src/ui/components/CodeBlock.css.ts.vanilla-lNKqskjQ.css +182 -0
- package/dist/assets/src/ui/components/Container.css.ts.vanilla-CUhRUA9t.css +80 -0
- package/dist/assets/src/ui/components/CopyButton.css.ts.vanilla-DZZ5jgTM.css +24 -0
- package/dist/assets/src/ui/components/Features.css.ts.vanilla-D-pNXM9Q.css +129 -0
- package/dist/assets/src/ui/components/Hero.css.ts.vanilla-DHJVZ6GX.css +134 -0
- package/dist/assets/src/ui/components/Search.css.ts.vanilla-BYpWHzky.css +135 -0
- package/dist/assets/src/ui/components/Steps.css.ts.vanilla-CisaxeNj.css +59 -0
- package/dist/assets/src/ui/components/Tabs.css.ts.vanilla-C4-vJSnf.css +30 -0
- package/dist/assets/src/ui/components/ThemeToggle.css.ts.vanilla---sSUELC.css +22 -0
- package/dist/assets/src/ui/content.css.ts.vanilla-O_RaSPXm.css +106 -0
- package/dist/assets/src/ui/theme/animations.css.ts.vanilla-D6ImVUKy.css +10 -0
- package/dist/assets/src/ui/theme/dark.css.ts.vanilla-2iJgcpbU.css +87 -0
- package/dist/assets/src/ui/theme/light.css.ts.vanilla-CwinfWSf.css +87 -0
- package/dist/assets/src/ui/theme/reset.css.ts.vanilla-0Q3pLjfC.css +34 -0
- package/dist/brand-icons-DLJKqTun.js +59 -0
- package/dist/brand-icons-DLJKqTun.js.map +1 -0
- package/dist/config/index.d.ts +5 -5
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +54 -11
- package/dist/config/index.js.map +1 -1
- package/dist/contract.css-DYvFVCFE.d.ts +105 -0
- package/dist/contract.css-DYvFVCFE.d.ts.map +1 -0
- package/dist/generator-DPtRXxM_.js +1194 -0
- package/dist/generator-DPtRXxM_.js.map +1 -0
- package/dist/icons/index.d.ts +22 -1
- package/dist/icons/index.d.ts.map +1 -0
- package/dist/icons/index.js +2 -2
- package/dist/index-BTeHvysI.d.ts +807 -0
- package/dist/index-BTeHvysI.d.ts.map +1 -0
- package/dist/index-DySzkJlC.d.ts +78 -0
- package/dist/index-DySzkJlC.d.ts.map +1 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +6 -100
- package/dist/mdx/provider.d.ts +61 -4
- package/dist/mdx/provider.d.ts.map +1 -0
- package/dist/mdx/provider.js +89 -117
- package/dist/mdx/provider.js.map +1 -1
- package/dist/runtime/index.d.ts +2 -41
- package/dist/runtime/index.js +2 -28
- package/dist/sidebar-utils-1Skqle1Q.js +109 -0
- package/dist/sidebar-utils-1Skqle1Q.js.map +1 -0
- package/dist/theme/index.d.ts +201 -182
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +288 -128
- package/dist/theme/index.js.map +1 -1
- package/dist/typedoc/components/index.d.ts +55 -0
- package/dist/typedoc/components/index.d.ts.map +1 -0
- package/dist/typedoc/components/index.js +339 -0
- package/dist/typedoc/components/index.js.map +1 -0
- package/dist/typedoc/index.d.ts +36 -305
- package/dist/typedoc/index.d.ts.map +1 -0
- package/dist/typedoc/index.js +97 -268
- package/dist/typedoc/index.js.map +1 -1
- package/dist/types-BCuJBsJu.d.ts +182 -0
- package/dist/types-BCuJBsJu.d.ts.map +1 -0
- package/dist/types-CTd_mkrv.d.ts +175 -0
- package/dist/types-CTd_mkrv.d.ts.map +1 -0
- package/dist/ui/index.d.ts +2 -178
- package/dist/ui/index.js +3 -95
- package/dist/ui/styles.css +1401 -1335
- package/dist/ui/styles.d.ts +1 -2
- package/dist/ui/styles.js +23 -4
- package/dist/ui-3grzJSsq.js +1314 -0
- package/dist/ui-3grzJSsq.js.map +1 -0
- package/dist/vite/index.d.ts +78 -86
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +2931 -1282
- package/dist/vite/index.js.map +1 -1
- package/package.json +30 -19
- package/dist/Features-D_Pt7zpA.d.ts +0 -615
- package/dist/Search-DOJMNI2T.css +0 -193
- package/dist/Search-DOJMNI2T.css.map +0 -1
- package/dist/Search-VYYG3D43.js +0 -10
- package/dist/Search-VYYG3D43.js.map +0 -1
- package/dist/chunk-4YQE3TNM.js +0 -1
- package/dist/chunk-4YQE3TNM.js.map +0 -1
- package/dist/chunk-AXLJDGQL.js +0 -1
- package/dist/chunk-AXLJDGQL.js.map +0 -1
- package/dist/chunk-CZM5NX27.js +0 -909
- package/dist/chunk-CZM5NX27.js.map +0 -1
- package/dist/chunk-FZP2AVJL.js +0 -43
- package/dist/chunk-FZP2AVJL.js.map +0 -1
- package/dist/chunk-IEPSORG5.js +0 -444
- package/dist/chunk-IEPSORG5.js.map +0 -1
- package/dist/chunk-KUWEUO37.js +0 -1
- package/dist/chunk-KUWEUO37.js.map +0 -1
- package/dist/chunk-NBRHGTR2.js +0 -79
- package/dist/chunk-NBRHGTR2.js.map +0 -1
- package/dist/chunk-PGHUPTGL.js +0 -1035
- package/dist/chunk-PGHUPTGL.js.map +0 -1
- package/dist/chunk-PMS3P4MA.js +0 -43
- package/dist/chunk-PMS3P4MA.js.map +0 -1
- package/dist/chunk-QELSOHIY.js +0 -46
- package/dist/chunk-QELSOHIY.js.map +0 -1
- package/dist/chunk-R2QKY6G3.js +0 -1
- package/dist/chunk-R2QKY6G3.js.map +0 -1
- package/dist/chunk-ZPYQQZ7J.js +0 -210
- package/dist/chunk-ZPYQQZ7J.js.map +0 -1
- package/dist/icons/index.js.map +0 -1
- package/dist/index.css +0 -1290
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mdx/provider.css +0 -403
- package/dist/mdx/provider.css.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/types-CLkHwCch.d.ts +0 -248
- package/dist/ui/index.css +0 -1290
- package/dist/ui/index.css.map +0 -1
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/styles.css.map +0 -1
- package/dist/ui/styles.js.map +0 -1
package/dist/vite/index.js
CHANGED
|
@@ -1,1335 +1,2984 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
generateApiDocs
|
|
7
|
-
} from "../chunk-PGHUPTGL.js";
|
|
8
|
-
|
|
9
|
-
// src/vite/plugin.ts
|
|
10
|
-
import { reactRouter } from "@react-router/dev/vite";
|
|
11
|
-
import mdx from "@mdx-js/rollup";
|
|
1
|
+
import { defaultMarkdownConfig, resolveConfig } from "../config/index.js";
|
|
2
|
+
import { n as generateApiDocs } from "../generator-DPtRXxM_.js";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import matter from "gray-matter";
|
|
5
|
+
import rehypeStringify from "rehype-stringify";
|
|
12
6
|
import remarkFrontmatter from "remark-frontmatter";
|
|
13
|
-
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
14
7
|
import remarkGfm from "remark-gfm";
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
createHighlighter
|
|
20
|
-
} from "shiki";
|
|
8
|
+
import remarkParse from "remark-parse";
|
|
9
|
+
import remarkRehype from "remark-rehype";
|
|
10
|
+
import { unified } from "unified";
|
|
21
11
|
import { visit } from "unist-util-visit";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
12
|
+
import { createHighlighter } from "shiki";
|
|
13
|
+
import fs from "node:fs/promises";
|
|
14
|
+
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
|
15
|
+
import fsSync from "node:fs";
|
|
16
|
+
import { execSync } from "node:child_process";
|
|
17
|
+
import mdx from "@mdx-js/rollup";
|
|
18
|
+
import { reactRouter } from "@react-router/dev/vite";
|
|
19
|
+
import rehypeShiki from "@shikijs/rehype";
|
|
20
|
+
import remarkMdxFrontmatter from "remark-mdx-frontmatter";
|
|
21
|
+
//#region src/markdown/links.ts
|
|
22
|
+
/**
|
|
23
|
+
* Rehype plugin that rewrites internal links to include the basePath.
|
|
24
|
+
* This is needed for static sites deployed to subpaths (e.g., GitHub Pages).
|
|
25
|
+
*/
|
|
26
|
+
function rehypeLinks(options) {
|
|
27
|
+
const { basePath } = options;
|
|
28
|
+
const normalizedBase = basePath === "/" ? "" : basePath.replace(/\/$/, "");
|
|
29
|
+
return (tree) => {
|
|
30
|
+
if (!normalizedBase) return;
|
|
31
|
+
visit(tree, "element", (node) => {
|
|
32
|
+
if (node.tagName !== "a") return;
|
|
33
|
+
const href = node.properties.href;
|
|
34
|
+
if (typeof href === "string" && href.startsWith("/") && !href.startsWith("//") && !href.startsWith(normalizedBase)) node.properties.href = normalizedBase + href;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/markdown/shiki-theme.ts
|
|
40
|
+
/** Default Ardo themes used when no config is provided */
|
|
41
|
+
const DEFAULT_THEMES = {
|
|
42
|
+
light: "github-light-default",
|
|
43
|
+
dark: "github-dark-default"
|
|
25
44
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const themeConfig = options?.theme ?? DEFAULT_THEMES;
|
|
29
|
-
if (!cachedHighlighter) {
|
|
30
|
-
cachedHighlighter = await createShikiHighlighter({
|
|
31
|
-
theme: themeConfig,
|
|
32
|
-
lineNumbers: false,
|
|
33
|
-
anchor: false,
|
|
34
|
-
toc: { level: [2, 3] }
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
if (typeof themeConfig === "string") {
|
|
38
|
-
return cachedHighlighter.codeToHtml(code, { lang: language, theme: themeConfig });
|
|
39
|
-
}
|
|
40
|
-
return cachedHighlighter.codeToHtml(code, {
|
|
41
|
-
lang: language,
|
|
42
|
-
themes: { light: themeConfig.light, dark: themeConfig.dark },
|
|
43
|
-
defaultColor: false
|
|
44
|
-
});
|
|
45
|
+
function resolveThemeConfig(theme) {
|
|
46
|
+
return theme ?? DEFAULT_THEMES;
|
|
45
47
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const highlighter = await createHighlighter({
|
|
50
|
-
themes,
|
|
51
|
-
langs: [
|
|
52
|
-
// Web fundamentals
|
|
53
|
-
"javascript",
|
|
54
|
-
"typescript",
|
|
55
|
-
"jsx",
|
|
56
|
-
"tsx",
|
|
57
|
-
"html",
|
|
58
|
-
"css",
|
|
59
|
-
"scss",
|
|
60
|
-
// Data & config formats
|
|
61
|
-
"json",
|
|
62
|
-
"jsonc",
|
|
63
|
-
"yaml",
|
|
64
|
-
"toml",
|
|
65
|
-
"xml",
|
|
66
|
-
"graphql",
|
|
67
|
-
// Markdown & docs
|
|
68
|
-
"markdown",
|
|
69
|
-
"mdx",
|
|
70
|
-
// Shell & DevOps
|
|
71
|
-
"bash",
|
|
72
|
-
"shell",
|
|
73
|
-
"dockerfile",
|
|
74
|
-
// General purpose
|
|
75
|
-
"python",
|
|
76
|
-
"rust",
|
|
77
|
-
"go",
|
|
78
|
-
"sql",
|
|
79
|
-
"diff"
|
|
80
|
-
]
|
|
81
|
-
});
|
|
82
|
-
return highlighter;
|
|
48
|
+
function getBundledThemes(themeConfig) {
|
|
49
|
+
if (themeConfig == null) return [DEFAULT_THEMES.light, DEFAULT_THEMES.dark];
|
|
50
|
+
return typeof themeConfig === "string" ? [themeConfig] : [themeConfig.light, themeConfig.dark];
|
|
83
51
|
}
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
let html;
|
|
102
|
-
if (typeof themeConfig === "string") {
|
|
103
|
-
html = highlighter.codeToHtml(codeContent, {
|
|
104
|
-
lang,
|
|
105
|
-
theme: themeConfig
|
|
106
|
-
});
|
|
107
|
-
} else {
|
|
108
|
-
html = highlighter.codeToHtml(codeContent, {
|
|
109
|
-
lang,
|
|
110
|
-
themes: {
|
|
111
|
-
light: themeConfig.light,
|
|
112
|
-
dark: themeConfig.dark
|
|
113
|
-
},
|
|
114
|
-
defaultColor: false
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
const metaString = codeNode.properties?.metastring || "";
|
|
118
|
-
const lineNumbers = config.lineNumbers || metaString.includes("showLineNumbers");
|
|
119
|
-
const highlightLines = parseHighlightLines(metaString);
|
|
120
|
-
const title = parseTitle(metaString);
|
|
121
|
-
const wrapperHtml = buildCodeBlockHtml(html, {
|
|
122
|
-
lang,
|
|
123
|
-
lineNumbers,
|
|
124
|
-
highlightLines,
|
|
125
|
-
title
|
|
126
|
-
});
|
|
127
|
-
if (parent && typeof index === "number") {
|
|
128
|
-
const newNode = {
|
|
129
|
-
type: "element",
|
|
130
|
-
tagName: "div",
|
|
131
|
-
properties: {
|
|
132
|
-
className: ["ardo-code-block"],
|
|
133
|
-
"data-lang": lang
|
|
134
|
-
},
|
|
135
|
-
children: [
|
|
136
|
-
{
|
|
137
|
-
type: "raw",
|
|
138
|
-
value: wrapperHtml
|
|
139
|
-
}
|
|
140
|
-
]
|
|
141
|
-
};
|
|
142
|
-
parent.children[index] = newNode;
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
};
|
|
52
|
+
function highlightWithTheme(params) {
|
|
53
|
+
const { code, highlighter, language, themeConfig } = params;
|
|
54
|
+
const resolved = themeConfig ?? DEFAULT_THEMES;
|
|
55
|
+
if (typeof resolved === "string") return highlighter.codeToHtml(code, {
|
|
56
|
+
lang: language,
|
|
57
|
+
theme: resolved
|
|
58
|
+
});
|
|
59
|
+
return highlighter.codeToHtml(code, {
|
|
60
|
+
defaultColor: false,
|
|
61
|
+
lang: language,
|
|
62
|
+
themes: {
|
|
63
|
+
dark: resolved.dark,
|
|
64
|
+
light: resolved.light
|
|
65
|
+
}
|
|
66
|
+
});
|
|
148
67
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/ui/components/code-block-classes.ts
|
|
70
|
+
const shikiContainerClassName = "ardo-shiki";
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/markdown/shiki-html.ts
|
|
73
|
+
function buildCodeBlockHtml(shikiHtml, options) {
|
|
74
|
+
const titleHtml = renderTitle(options.title);
|
|
75
|
+
const codeHtml = renderCodeLines({
|
|
76
|
+
highlightLines: options.highlightLines,
|
|
77
|
+
lineNumbers: options.lineNumbers,
|
|
78
|
+
shikiHtml
|
|
79
|
+
});
|
|
80
|
+
const copyButton = renderCopyButton(shikiHtml);
|
|
81
|
+
return `${titleHtml}<div data-lang="${options.lang}">${codeHtml}${copyButton}</div>`;
|
|
157
82
|
}
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const ranges = match[1].split(",");
|
|
162
|
-
const lines = [];
|
|
163
|
-
for (const range of ranges) {
|
|
164
|
-
if (range.includes("-")) {
|
|
165
|
-
const [start, end] = range.split("-").map(Number);
|
|
166
|
-
for (let i = start; i <= end; i++) {
|
|
167
|
-
lines.push(i);
|
|
168
|
-
}
|
|
169
|
-
} else {
|
|
170
|
-
lines.push(Number(range));
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return lines;
|
|
83
|
+
function renderTitle(title) {
|
|
84
|
+
if (title == null || title.length === 0) return "";
|
|
85
|
+
return `<div data-title>${escapeHtml(title)}</div>`;
|
|
174
86
|
}
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
87
|
+
function renderCodeLines(params) {
|
|
88
|
+
const { highlightLines, lineNumbers, shikiHtml } = params;
|
|
89
|
+
if (!lineNumbers && highlightLines.length === 0) return shikiHtml;
|
|
90
|
+
return shikiHtml.split("\n").map((lineHtml, index) => renderSingleCodeLine({
|
|
91
|
+
highlightLines,
|
|
92
|
+
lineHtml,
|
|
93
|
+
lineNumber: index + 1,
|
|
94
|
+
lineNumbers
|
|
95
|
+
})).join("\n");
|
|
178
96
|
}
|
|
179
|
-
function
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const lines = shikiHtml.split("\n");
|
|
188
|
-
const processedHtml = lines.map((line, i) => {
|
|
189
|
-
const lineNum = i + 1;
|
|
190
|
-
const isHighlighted = highlightLines.includes(lineNum);
|
|
191
|
-
const classes = ["ardo-code-line"];
|
|
192
|
-
if (isHighlighted) classes.push("highlighted");
|
|
193
|
-
let prefix = "";
|
|
194
|
-
if (lineNumbers) {
|
|
195
|
-
prefix = `<span class="ardo-line-number">${lineNum}</span>`;
|
|
196
|
-
}
|
|
197
|
-
return `<span class="${classes.join(" ")}">${prefix}${line}</span>`;
|
|
198
|
-
}).join("\n");
|
|
199
|
-
html += processedHtml;
|
|
200
|
-
} else {
|
|
201
|
-
html += shikiHtml;
|
|
202
|
-
}
|
|
203
|
-
html += `<button class="ardo-copy-button" data-code="${encodeURIComponent(extractCodeFromHtml(shikiHtml))}">
|
|
204
|
-
<span class="ardo-copy-icon">Copy</span>
|
|
205
|
-
<span class="ardo-copied-icon" style="display:none">Copied!</span>
|
|
97
|
+
function renderSingleCodeLine(params) {
|
|
98
|
+
const { highlightLines, lineHtml, lineNumber, lineNumbers } = params;
|
|
99
|
+
return `<span class="${highlightLines.includes(lineNumber) ? "line highlighted" : "line"}"${lineNumbers ? ` data-ln="${lineNumber}"` : ""}>${lineHtml}</span>`;
|
|
100
|
+
}
|
|
101
|
+
function renderCopyButton(shikiHtml) {
|
|
102
|
+
return `<button data-code="${encodeURIComponent(extractCodeFromHtml(shikiHtml))}">
|
|
103
|
+
<span>Copy</span>
|
|
104
|
+
<span style="display:none">Copied!</span>
|
|
206
105
|
</button>`;
|
|
207
|
-
html += "</div>";
|
|
208
|
-
return html;
|
|
209
106
|
}
|
|
210
107
|
function extractCodeFromHtml(html) {
|
|
211
|
-
|
|
108
|
+
return decodeCommonEntities(stripTags(html));
|
|
109
|
+
}
|
|
110
|
+
function stripTags(html) {
|
|
111
|
+
let result = "";
|
|
112
|
+
let inTag = false;
|
|
113
|
+
for (const char of html) {
|
|
114
|
+
if (char === "<") {
|
|
115
|
+
inTag = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (inTag && char === ">") {
|
|
119
|
+
inTag = false;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!inTag) result += char;
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
function decodeCommonEntities(text) {
|
|
127
|
+
return text.replaceAll("<", "<").replaceAll(">", ">").replaceAll("&", "&").replaceAll(""", "\"").replaceAll("'", "'");
|
|
212
128
|
}
|
|
213
129
|
function escapeHtml(text) {
|
|
214
|
-
|
|
130
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """).replaceAll("'", "'");
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/markdown/shiki-meta.ts
|
|
134
|
+
function parseHighlightLines(meta) {
|
|
135
|
+
const match = /\{([\d,-]+)\}/u.exec(meta);
|
|
136
|
+
if (match?.[1] == null) return [];
|
|
137
|
+
const lines = [];
|
|
138
|
+
for (const range of match[1].split(",")) appendRangeLines(lines, range);
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
function appendRangeLines(lines, range) {
|
|
142
|
+
if (range.includes("-")) {
|
|
143
|
+
appendRangeSet(lines, range);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const lineNumber = Number(range);
|
|
147
|
+
if (Number.isFinite(lineNumber)) lines.push(lineNumber);
|
|
148
|
+
}
|
|
149
|
+
function appendRangeSet(lines, range) {
|
|
150
|
+
const [start, end] = range.split("-").map(Number);
|
|
151
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return;
|
|
152
|
+
for (let line = start; line <= end; line++) lines.push(line);
|
|
153
|
+
}
|
|
154
|
+
function parseTitle(meta) {
|
|
155
|
+
return /title="([^"]+)"/u.exec(meta)?.[1];
|
|
156
|
+
}
|
|
157
|
+
function parseLabel(meta) {
|
|
158
|
+
const start = meta.indexOf("[");
|
|
159
|
+
if (start === -1) return;
|
|
160
|
+
const end = meta.indexOf("]", start + 1);
|
|
161
|
+
if (end === -1) return;
|
|
162
|
+
const label = meta.slice(start + 1, end).trim();
|
|
163
|
+
return label.length > 0 ? label : void 0;
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/markdown/shiki-rehype.ts
|
|
167
|
+
function rehypeShikiFromHighlighter(options) {
|
|
168
|
+
const themeConfig = resolveThemeConfig(options.config.theme);
|
|
169
|
+
return function(tree) {
|
|
170
|
+
visit(tree, "element", (node, index, parent) => {
|
|
171
|
+
transformCodeNode({
|
|
172
|
+
config: options.config,
|
|
173
|
+
highlighter: options.highlighter,
|
|
174
|
+
index,
|
|
175
|
+
node,
|
|
176
|
+
parent,
|
|
177
|
+
themeConfig
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function transformCodeNode(context) {
|
|
183
|
+
const codeNode = getCodeNode(context.node);
|
|
184
|
+
if (codeNode == null) return;
|
|
185
|
+
const codeContent = getTextContent(codeNode);
|
|
186
|
+
if (codeContent.trim().length === 0) return;
|
|
187
|
+
const metaString = getMetaString(codeNode);
|
|
188
|
+
const innerHtml = tryRenderHighlightedHtml({
|
|
189
|
+
codeContent,
|
|
190
|
+
context,
|
|
191
|
+
language: getLanguage(codeNode),
|
|
192
|
+
metaString
|
|
193
|
+
});
|
|
194
|
+
if (innerHtml == null) return;
|
|
195
|
+
replaceNodeWithShikiContainer(context.parent, context.index, innerHtml);
|
|
196
|
+
}
|
|
197
|
+
function tryRenderHighlightedHtml(params) {
|
|
198
|
+
const { codeContent, context, language, metaString } = params;
|
|
199
|
+
try {
|
|
200
|
+
return buildCodeBlockHtml(highlightWithTheme({
|
|
201
|
+
code: codeContent,
|
|
202
|
+
highlighter: context.highlighter,
|
|
203
|
+
language,
|
|
204
|
+
themeConfig: context.themeConfig
|
|
205
|
+
}), {
|
|
206
|
+
highlightLines: parseHighlightLines(metaString),
|
|
207
|
+
lang: language,
|
|
208
|
+
lineNumbers: (context.config.lineNumbers ?? false) || metaString.includes("showLineNumbers"),
|
|
209
|
+
title: parseTitle(metaString)
|
|
210
|
+
});
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function getCodeNode(node) {
|
|
216
|
+
if (node.tagName !== "pre") return null;
|
|
217
|
+
const firstChild = node.children.at(0);
|
|
218
|
+
if (!isElementNode(firstChild) || firstChild.tagName !== "code") return null;
|
|
219
|
+
return firstChild;
|
|
220
|
+
}
|
|
221
|
+
function isElementNode(node) {
|
|
222
|
+
return isRecord$5(node) && node.type === "element";
|
|
223
|
+
}
|
|
224
|
+
function getMetaString(codeNode) {
|
|
225
|
+
const properties = toRecord(codeNode.properties);
|
|
226
|
+
return typeof properties.metastring === "string" ? properties.metastring : "";
|
|
227
|
+
}
|
|
228
|
+
function getLanguage(codeNode) {
|
|
229
|
+
const languageClass = toClassNameList(toRecord(codeNode.properties).className).find((className) => className.startsWith("language-"));
|
|
230
|
+
return languageClass == null ? "text" : languageClass.replace("language-", "");
|
|
231
|
+
}
|
|
232
|
+
function toClassNameList(className) {
|
|
233
|
+
if (Array.isArray(className)) return className.filter((entry) => typeof entry === "string");
|
|
234
|
+
if (typeof className === "string") return [className];
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
function getTextContent(node) {
|
|
238
|
+
if (node.type === "text") return node.value;
|
|
239
|
+
const parts = [];
|
|
240
|
+
for (const child of node.children) if (child.type === "text" || child.type === "element") parts.push(getTextContent(child));
|
|
241
|
+
return parts.join("");
|
|
242
|
+
}
|
|
243
|
+
function replaceNodeWithShikiContainer(parent, index, innerHtml) {
|
|
244
|
+
if (index == null || !hasChildrenArray(parent)) return;
|
|
245
|
+
parent.children[index] = {
|
|
246
|
+
type: "element",
|
|
247
|
+
tagName: "div",
|
|
248
|
+
properties: { className: [shikiContainerClassName] },
|
|
249
|
+
children: [{
|
|
250
|
+
type: "raw",
|
|
251
|
+
value: innerHtml
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function hasChildrenArray(value) {
|
|
256
|
+
return isRecord$5(value) && Array.isArray(value.children);
|
|
257
|
+
}
|
|
258
|
+
function toRecord(value) {
|
|
259
|
+
return isRecord$5(value) ? value : {};
|
|
260
|
+
}
|
|
261
|
+
function isRecord$5(value) {
|
|
262
|
+
return value != null && typeof value === "object";
|
|
215
263
|
}
|
|
264
|
+
//#endregion
|
|
265
|
+
//#region src/markdown/shiki-transformer.ts
|
|
266
|
+
/**
|
|
267
|
+
* Remark plugin that extracts code fence meta info and stores it as HAST
|
|
268
|
+
* data attributes before MDX compilation can corrupt it.
|
|
269
|
+
*/
|
|
216
270
|
function remarkCodeMeta() {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
271
|
+
return function(tree) {
|
|
272
|
+
visit(tree, "code", (node) => {
|
|
273
|
+
const meta = node.meta;
|
|
274
|
+
if (meta == null || meta.length === 0) return;
|
|
275
|
+
const data = ensureNodeData(node);
|
|
276
|
+
const hProperties = ensureRecord(data.hProperties);
|
|
277
|
+
hProperties.metastring = meta;
|
|
278
|
+
data.hProperties = hProperties;
|
|
279
|
+
node.meta = null;
|
|
280
|
+
});
|
|
281
|
+
};
|
|
228
282
|
}
|
|
283
|
+
function ensureNodeData(node) {
|
|
284
|
+
node.data ??= {};
|
|
285
|
+
return node.data;
|
|
286
|
+
}
|
|
287
|
+
function ensureRecord(value) {
|
|
288
|
+
return isRecord$4(value) ? value : {};
|
|
289
|
+
}
|
|
290
|
+
function isRecord$4(value) {
|
|
291
|
+
return value != null && typeof value === "object";
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Shiki transformer that adds line highlighting, line numbers, and title
|
|
295
|
+
* attributes to code blocks in the MDX pipeline.
|
|
296
|
+
*/
|
|
229
297
|
function ardoLineTransformer(options = {}) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
const currentClass = node.properties?.class || "";
|
|
255
|
-
const classes = currentClass ? currentClass.split(" ") : [];
|
|
256
|
-
classes.push("ardo-code-line");
|
|
257
|
-
if (highlightLines.includes(line)) {
|
|
258
|
-
classes.push("highlighted");
|
|
259
|
-
}
|
|
260
|
-
node.properties = node.properties || {};
|
|
261
|
-
node.properties.class = classes.join(" ");
|
|
262
|
-
if (showLineNumbers) {
|
|
263
|
-
node.children.unshift({
|
|
264
|
-
type: "element",
|
|
265
|
-
tagName: "span",
|
|
266
|
-
properties: { class: "ardo-line-number" },
|
|
267
|
-
children: [{ type: "text", value: String(line) }]
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
};
|
|
298
|
+
const state = {
|
|
299
|
+
highlightLines: [],
|
|
300
|
+
metaRaw: "",
|
|
301
|
+
showLineNumbers: false
|
|
302
|
+
};
|
|
303
|
+
return {
|
|
304
|
+
name: "ardo:lines",
|
|
305
|
+
preprocess(_code, shikiOptions) {
|
|
306
|
+
const metaRaw = getMetaRaw(shikiOptions.meta);
|
|
307
|
+
state.metaRaw = metaRaw;
|
|
308
|
+
state.highlightLines = parseHighlightLines(metaRaw);
|
|
309
|
+
state.showLineNumbers = (options.globalLineNumbers ?? false) || metaRaw.includes("showLineNumbers");
|
|
310
|
+
},
|
|
311
|
+
pre(node) {
|
|
312
|
+
const properties = ensureNodeProperties(node);
|
|
313
|
+
applyTitleProperty(properties, state.metaRaw);
|
|
314
|
+
applyLabelProperty(properties, state.metaRaw);
|
|
315
|
+
},
|
|
316
|
+
line(node, line) {
|
|
317
|
+
const properties = ensureNodeProperties(node);
|
|
318
|
+
applyHighlightedLineClass(properties, state.highlightLines, line);
|
|
319
|
+
if (state.showLineNumbers) properties["data-ln"] = String(line);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
272
322
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
import path2 from "path";
|
|
278
|
-
import { execSync } from "child_process";
|
|
279
|
-
import matter from "gray-matter";
|
|
280
|
-
|
|
281
|
-
// src/vite/routes-plugin.ts
|
|
282
|
-
import fs from "fs/promises";
|
|
283
|
-
import fsSync from "fs";
|
|
284
|
-
import path from "path";
|
|
285
|
-
function ardoRoutesPlugin(options = {}) {
|
|
286
|
-
let routesDir;
|
|
287
|
-
let appDir;
|
|
288
|
-
let routesFilePath;
|
|
289
|
-
function scanRoutesSync(dir, rootDir) {
|
|
290
|
-
const routes = [];
|
|
291
|
-
try {
|
|
292
|
-
const entries = fsSync.readdirSync(dir, { withFileTypes: true });
|
|
293
|
-
for (const entry of entries) {
|
|
294
|
-
const fullPath = path.join(dir, entry.name);
|
|
295
|
-
if (entry.isDirectory()) {
|
|
296
|
-
const children = scanRoutesSync(fullPath, rootDir);
|
|
297
|
-
routes.push(...children);
|
|
298
|
-
} else if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md") || entry.name.endsWith(".tsx")) {
|
|
299
|
-
if (entry.name === "root.tsx" || entry.name.startsWith("_")) {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
const relativePath = path.relative(rootDir, fullPath);
|
|
303
|
-
const ext = entry.name.endsWith(".mdx") ? ".mdx" : entry.name.endsWith(".md") ? ".md" : ".tsx";
|
|
304
|
-
const baseName = entry.name.replace(ext, "");
|
|
305
|
-
let urlPath;
|
|
306
|
-
if (baseName === "index" || baseName === "home") {
|
|
307
|
-
const parentDir = path.dirname(relativePath);
|
|
308
|
-
urlPath = parentDir === "." ? "/" : "/" + parentDir.replace(/\\/g, "/");
|
|
309
|
-
} else {
|
|
310
|
-
urlPath = "/" + relativePath.replace(ext, "").replace(/\\/g, "/");
|
|
311
|
-
}
|
|
312
|
-
urlPath = urlPath.replace(/\$(\w+)/g, ":$1");
|
|
313
|
-
routes.push({
|
|
314
|
-
path: urlPath,
|
|
315
|
-
file: "routes/" + relativePath.replace(/\\/g, "/"),
|
|
316
|
-
isIndex: baseName === "index" || baseName === "home"
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} catch {
|
|
321
|
-
}
|
|
322
|
-
return routes;
|
|
323
|
-
}
|
|
324
|
-
function generateRoutesFile(routes) {
|
|
325
|
-
const sortedRoutes = [...routes].sort((a, b) => {
|
|
326
|
-
if (a.path === "/" && b.path !== "/") return -1;
|
|
327
|
-
if (b.path === "/" && a.path !== "/") return 1;
|
|
328
|
-
if (a.isIndex && !b.isIndex) return -1;
|
|
329
|
-
if (b.isIndex && !a.isIndex) return 1;
|
|
330
|
-
return a.path.localeCompare(b.path);
|
|
331
|
-
});
|
|
332
|
-
const entries = sortedRoutes.map((r) => {
|
|
333
|
-
if (r.path === "/") {
|
|
334
|
-
return ` index("${r.file}"),`;
|
|
335
|
-
}
|
|
336
|
-
const routePath = r.path.substring(1);
|
|
337
|
-
return ` route("${routePath}", "${r.file}"),`;
|
|
338
|
-
});
|
|
339
|
-
return `// AUTO-GENERATED by Ardo - Do not edit manually
|
|
340
|
-
|
|
341
|
-
import { type RouteConfig, route, index } from "@react-router/dev/routes"
|
|
342
|
-
|
|
343
|
-
export default [
|
|
344
|
-
${entries.join("\n")}
|
|
345
|
-
] satisfies RouteConfig
|
|
346
|
-
`;
|
|
347
|
-
}
|
|
348
|
-
function writeRoutesFileSync() {
|
|
349
|
-
const routes = scanRoutesSync(routesDir, routesDir);
|
|
350
|
-
if (routes.length === 0) {
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const content = generateRoutesFile(routes);
|
|
354
|
-
try {
|
|
355
|
-
const existing = fsSync.readFileSync(routesFilePath, "utf-8");
|
|
356
|
-
if (existing === content) {
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
} catch {
|
|
360
|
-
}
|
|
361
|
-
fsSync.mkdirSync(appDir, { recursive: true });
|
|
362
|
-
fsSync.writeFileSync(routesFilePath, content, "utf-8");
|
|
363
|
-
console.log(`[ardo] Generated routes.ts with ${routes.length} routes`);
|
|
364
|
-
}
|
|
365
|
-
async function writeRoutesFile() {
|
|
366
|
-
const routes = scanRoutesSync(routesDir, routesDir);
|
|
367
|
-
if (routes.length === 0) {
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const content = generateRoutesFile(routes);
|
|
371
|
-
try {
|
|
372
|
-
const existing = await fs.readFile(routesFilePath, "utf-8");
|
|
373
|
-
if (existing === content) {
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
} catch {
|
|
377
|
-
}
|
|
378
|
-
await fs.mkdir(appDir, { recursive: true });
|
|
379
|
-
await fs.writeFile(routesFilePath, content, "utf-8");
|
|
380
|
-
}
|
|
381
|
-
return {
|
|
382
|
-
name: "ardo:routes",
|
|
383
|
-
enforce: "pre",
|
|
384
|
-
config(userConfig) {
|
|
385
|
-
const root = userConfig.root || process.cwd();
|
|
386
|
-
appDir = path.join(root, "app");
|
|
387
|
-
routesDir = options.routesDir || path.join(appDir, "routes");
|
|
388
|
-
routesFilePath = path.join(appDir, "routes.ts");
|
|
389
|
-
try {
|
|
390
|
-
writeRoutesFileSync();
|
|
391
|
-
} catch (err) {
|
|
392
|
-
console.warn("[ardo] Could not generate routes.ts in config phase:", err);
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
configResolved(config) {
|
|
396
|
-
if (!appDir) {
|
|
397
|
-
appDir = path.join(config.root, "app");
|
|
398
|
-
routesDir = options.routesDir || path.join(appDir, "routes");
|
|
399
|
-
routesFilePath = path.join(appDir, "routes.ts");
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
async buildStart() {
|
|
403
|
-
await writeRoutesFile();
|
|
404
|
-
},
|
|
405
|
-
configureServer(server) {
|
|
406
|
-
server.watcher.add(routesDir);
|
|
407
|
-
const handleChange = async (changedPath) => {
|
|
408
|
-
if (changedPath.startsWith(routesDir) && (changedPath.endsWith(".mdx") || changedPath.endsWith(".md") || changedPath.endsWith(".tsx"))) {
|
|
409
|
-
await writeRoutesFile();
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
server.watcher.on("add", handleChange);
|
|
413
|
-
server.watcher.on("unlink", handleChange);
|
|
414
|
-
}
|
|
415
|
-
};
|
|
323
|
+
function getMetaRaw(meta) {
|
|
324
|
+
if (!isRecord$4(meta)) return "";
|
|
325
|
+
const raw = meta.__raw;
|
|
326
|
+
return typeof raw === "string" ? raw : "";
|
|
416
327
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
328
|
+
function ensureNodeProperties(node) {
|
|
329
|
+
node.properties ??= {};
|
|
330
|
+
return node.properties;
|
|
331
|
+
}
|
|
332
|
+
function applyTitleProperty(properties, metaRaw) {
|
|
333
|
+
const title = parseTitle(metaRaw);
|
|
334
|
+
if (title != null && title.length > 0) properties["data-title"] = title;
|
|
335
|
+
}
|
|
336
|
+
function applyLabelProperty(properties, metaRaw) {
|
|
337
|
+
const label = parseLabel(metaRaw);
|
|
338
|
+
if (label != null && label.length > 0) properties["data-label"] = label;
|
|
339
|
+
}
|
|
340
|
+
function applyHighlightedLineClass(properties, highlightLines, line) {
|
|
341
|
+
if (!highlightLines.includes(line)) return;
|
|
342
|
+
const currentClass = typeof properties.class === "string" ? properties.class : "";
|
|
343
|
+
properties.class = currentClass.length > 0 ? `${currentClass} highlighted` : "highlighted";
|
|
344
|
+
}
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region src/markdown/shiki.ts
|
|
347
|
+
let cachedHighlighterPromise;
|
|
348
|
+
/**
|
|
349
|
+
* Highlights code using Shiki with Ardo's default themes.
|
|
350
|
+
* Creates and caches a highlighter instance for reuse.
|
|
351
|
+
*/
|
|
352
|
+
async function highlightCode(code, language, options) {
|
|
353
|
+
const themeConfig = resolveThemeConfig(options?.theme);
|
|
354
|
+
return highlightWithTheme({
|
|
355
|
+
code,
|
|
356
|
+
highlighter: await getCachedHighlighter(themeConfig),
|
|
357
|
+
language,
|
|
358
|
+
themeConfig
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
async function getCachedHighlighter(themeConfig) {
|
|
362
|
+
cachedHighlighterPromise ??= createShikiHighlighter({
|
|
363
|
+
anchor: false,
|
|
364
|
+
lineNumbers: false,
|
|
365
|
+
theme: themeConfig,
|
|
366
|
+
toc: { level: [2, 3] }
|
|
367
|
+
});
|
|
368
|
+
return cachedHighlighterPromise;
|
|
369
|
+
}
|
|
370
|
+
async function createShikiHighlighter(config) {
|
|
371
|
+
return createHighlighter({
|
|
372
|
+
themes: getBundledThemes(resolveThemeConfig(config.theme)),
|
|
373
|
+
langs: [
|
|
374
|
+
"javascript",
|
|
375
|
+
"typescript",
|
|
376
|
+
"jsx",
|
|
377
|
+
"tsx",
|
|
378
|
+
"html",
|
|
379
|
+
"css",
|
|
380
|
+
"scss",
|
|
381
|
+
"json",
|
|
382
|
+
"jsonc",
|
|
383
|
+
"yaml",
|
|
384
|
+
"toml",
|
|
385
|
+
"xml",
|
|
386
|
+
"graphql",
|
|
387
|
+
"markdown",
|
|
388
|
+
"mdx",
|
|
389
|
+
"bash",
|
|
390
|
+
"shell",
|
|
391
|
+
"dockerfile",
|
|
392
|
+
"python",
|
|
393
|
+
"rust",
|
|
394
|
+
"go",
|
|
395
|
+
"sql",
|
|
396
|
+
"diff"
|
|
397
|
+
]
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/markdown/toc.ts
|
|
402
|
+
function remarkExtractToc(options) {
|
|
403
|
+
const { tocExtraction, levels } = options;
|
|
404
|
+
const [minLevel, maxLevel] = levels;
|
|
405
|
+
return function(tree) {
|
|
406
|
+
const headings = [];
|
|
407
|
+
let headingIndex = 0;
|
|
408
|
+
visit(tree, "heading", (node) => {
|
|
409
|
+
if (node.depth < minLevel || node.depth > maxLevel) return;
|
|
410
|
+
const text = getHeadingText$1(node);
|
|
411
|
+
const slug = slugify$1(text);
|
|
412
|
+
const id = slug === "" ? `heading-${headingIndex}` : slug;
|
|
413
|
+
headingIndex++;
|
|
414
|
+
headings.push({
|
|
415
|
+
text,
|
|
416
|
+
level: node.depth,
|
|
417
|
+
id
|
|
418
|
+
});
|
|
419
|
+
const hProperties = ensureHProperties$1(node);
|
|
420
|
+
hProperties.id = id;
|
|
421
|
+
});
|
|
422
|
+
tocExtraction.toc = buildTocTree(headings);
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function getHeadingText$1(node) {
|
|
426
|
+
const textParts = [];
|
|
427
|
+
function extractText(child) {
|
|
428
|
+
if (!isRecord$3(child)) return;
|
|
429
|
+
if (child.type === "text") textParts.push(typeof child.value === "string" ? child.value : "");
|
|
430
|
+
else if (child.type === "inlineCode") textParts.push(typeof child.value === "string" ? child.value : "");
|
|
431
|
+
else if (Array.isArray(child.children)) child.children.forEach((nestedChild) => {
|
|
432
|
+
extractText(nestedChild);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
node.children.forEach((child) => {
|
|
436
|
+
extractText(child);
|
|
437
|
+
});
|
|
438
|
+
return textParts.join("");
|
|
439
|
+
}
|
|
440
|
+
function slugify$1(text) {
|
|
441
|
+
let slug = text.toLowerCase().trim().replaceAll(/[^\s\w-]/g, "").replaceAll(/[\s_]/g, "-");
|
|
442
|
+
while (slug.includes("--")) slug = slug.replaceAll("--", "-");
|
|
443
|
+
if (slug.startsWith("-")) slug = slug.slice(1);
|
|
444
|
+
if (slug.endsWith("-")) slug = slug.slice(0, -1);
|
|
445
|
+
return slug;
|
|
446
|
+
}
|
|
447
|
+
function popStackUntilParent(stack, level) {
|
|
448
|
+
while (stack.length > 0) {
|
|
449
|
+
const last = stack.at(-1);
|
|
450
|
+
if (last === void 0 || last.level < level) break;
|
|
451
|
+
stack.pop();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function insertIntoTree(result, stack, item) {
|
|
455
|
+
const parent = stack.at(-1)?.item;
|
|
456
|
+
if (parent === void 0) result.push(item);
|
|
457
|
+
else {
|
|
458
|
+
parent.children ??= [];
|
|
459
|
+
parent.children.push(item);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function buildTocTree(headings) {
|
|
463
|
+
const result = [];
|
|
464
|
+
const stack = [];
|
|
465
|
+
for (const heading of headings) {
|
|
466
|
+
const item = {
|
|
467
|
+
id: heading.id,
|
|
468
|
+
text: heading.text,
|
|
469
|
+
level: heading.level
|
|
470
|
+
};
|
|
471
|
+
popStackUntilParent(stack, heading.level);
|
|
472
|
+
insertIntoTree(result, stack, item);
|
|
473
|
+
stack.push({
|
|
474
|
+
item,
|
|
475
|
+
level: heading.level
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return result;
|
|
479
|
+
}
|
|
480
|
+
function ensureHProperties$1(node) {
|
|
481
|
+
const data = node.data ?? {};
|
|
482
|
+
node.data = data;
|
|
483
|
+
if (!isRecord$3(data.hProperties)) data.hProperties = {};
|
|
484
|
+
return data.hProperties;
|
|
485
|
+
}
|
|
486
|
+
function isRecord$3(value) {
|
|
487
|
+
return value != null && typeof value === "object";
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/markdown/pipeline.ts
|
|
491
|
+
async function transformMarkdown(content, config, options = {}) {
|
|
492
|
+
const { data, content: markdownContent } = matter(content);
|
|
493
|
+
const frontmatterData = data;
|
|
494
|
+
const { basePath = "/", highlighter: providedHighlighter } = options;
|
|
495
|
+
const tocExtraction = { toc: [] };
|
|
496
|
+
const highlighter = providedHighlighter ?? await createShikiHighlighter(config);
|
|
497
|
+
const processor = unified().use(remarkParse).use(remarkFrontmatter, ["yaml"]).use(remarkGfm).use(remarkExtractToc, {
|
|
498
|
+
tocExtraction,
|
|
499
|
+
levels: config.toc?.level ?? [2, 3]
|
|
500
|
+
}).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShikiFromHighlighter, {
|
|
501
|
+
highlighter,
|
|
502
|
+
config
|
|
503
|
+
}).use(rehypeLinks, { basePath }).use(rehypeStringify, { allowDangerousHtml: true });
|
|
504
|
+
if (config.remarkPlugins) processor.use(config.remarkPlugins);
|
|
505
|
+
if (config.rehypePlugins) processor.use(config.rehypePlugins);
|
|
506
|
+
const result = await processor.process(markdownContent);
|
|
507
|
+
return {
|
|
508
|
+
html: String(result),
|
|
509
|
+
frontmatter: readPageFrontmatter(frontmatterData),
|
|
510
|
+
toc: tocExtraction.toc
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
async function transformMarkdownToReact(content, config) {
|
|
514
|
+
return transformMarkdown(content, config);
|
|
515
|
+
}
|
|
516
|
+
function readPageFrontmatter(data) {
|
|
517
|
+
return isRecord$2(data) ? data : {};
|
|
518
|
+
}
|
|
519
|
+
function isRecord$2(value) {
|
|
520
|
+
return value != null && typeof value === "object";
|
|
521
|
+
}
|
|
522
|
+
//#endregion
|
|
523
|
+
//#region src/runtime/loader.ts
|
|
524
|
+
async function findFile(contentDir, slug) {
|
|
525
|
+
const possiblePaths = [path.join(contentDir, `${slug}.md`), path.join(contentDir, slug, "index.md")];
|
|
526
|
+
for (const tryPath of possiblePaths) try {
|
|
527
|
+
return {
|
|
528
|
+
filePath: tryPath,
|
|
529
|
+
fileContent: await fs.readFile(tryPath, "utf8")
|
|
530
|
+
};
|
|
531
|
+
} catch {
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
async function getLastUpdated(filePath) {
|
|
537
|
+
try {
|
|
538
|
+
return (await fs.stat(filePath)).mtimeMs;
|
|
539
|
+
} catch {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
async function loadDoc(options) {
|
|
544
|
+
const { slug, contentDir, config } = options;
|
|
545
|
+
const found = await findFile(contentDir, slug);
|
|
546
|
+
if (found === null) return null;
|
|
547
|
+
const result = await transformMarkdown(found.fileContent, config.markdown);
|
|
548
|
+
return {
|
|
549
|
+
content: result.html,
|
|
550
|
+
frontmatter: result.frontmatter,
|
|
551
|
+
toc: result.toc,
|
|
552
|
+
filePath: found.filePath,
|
|
553
|
+
relativePath: path.relative(contentDir, found.filePath),
|
|
554
|
+
lastUpdated: await getLastUpdated(found.filePath)
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
async function loadAllDocs(contentDir, config) {
|
|
558
|
+
const docs = [];
|
|
559
|
+
async function scanDir(dir) {
|
|
560
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
561
|
+
for (const entry of entries) {
|
|
562
|
+
const fullPath = path.join(dir, entry.name);
|
|
563
|
+
if (entry.isDirectory()) await scanDir(fullPath);
|
|
564
|
+
else if (entry.name.endsWith(".md")) {
|
|
565
|
+
const result = await transformMarkdown(await fs.readFile(fullPath, "utf8"), config.markdown);
|
|
566
|
+
const relativePath = path.relative(contentDir, fullPath);
|
|
567
|
+
let lastUpdated;
|
|
568
|
+
try {
|
|
569
|
+
lastUpdated = (await fs.stat(fullPath)).mtimeMs;
|
|
570
|
+
} catch {}
|
|
571
|
+
docs.push({
|
|
572
|
+
title: result.frontmatter.title ?? formatTitle$3(entry.name.replace(/\.md$/, "")),
|
|
573
|
+
description: result.frontmatter.description,
|
|
574
|
+
frontmatter: result.frontmatter,
|
|
575
|
+
content: result.html,
|
|
576
|
+
toc: result.toc,
|
|
577
|
+
filePath: fullPath,
|
|
578
|
+
relativePath,
|
|
579
|
+
lastUpdated
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
await scanDir(contentDir);
|
|
585
|
+
return docs;
|
|
586
|
+
}
|
|
587
|
+
function formatTitle$3(name) {
|
|
588
|
+
return name.replaceAll(/[_-]/g, " ").replaceAll(/\b\w/g, (c) => c.toUpperCase());
|
|
589
|
+
}
|
|
590
|
+
function getSlugFromPath(relativePath) {
|
|
591
|
+
return relativePath.replace(/\.md$/, "").replace(/\/index$/, "").replaceAll("\\", "/");
|
|
592
|
+
}
|
|
593
|
+
function getPageDataForRoute(docs, slug) {
|
|
594
|
+
return docs.find((doc) => {
|
|
595
|
+
const docSlug = getSlugFromPath(doc.relativePath);
|
|
596
|
+
return docSlug === slug || docSlug === `${slug}/index`;
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/runtime/sidebar.ts
|
|
601
|
+
async function generateSidebar(options) {
|
|
602
|
+
const { contentDir } = options;
|
|
603
|
+
return scanDirectoryForSidebar(contentDir, contentDir);
|
|
604
|
+
}
|
|
605
|
+
async function scanDirectoryForSidebar(dir, rootDir) {
|
|
606
|
+
const entries = await readDirectoryEntries$1(dir);
|
|
607
|
+
const items = [];
|
|
608
|
+
for (const entry of entries) {
|
|
609
|
+
const sidebarItem = await createSidebarItemFromEntry({
|
|
610
|
+
dir,
|
|
611
|
+
entry,
|
|
612
|
+
rootDir
|
|
613
|
+
});
|
|
614
|
+
if (sidebarItem != null) items.push(sidebarItem);
|
|
615
|
+
}
|
|
616
|
+
sortSidebarItems(items);
|
|
617
|
+
return items.map(({ order: _order, ...item }) => item);
|
|
618
|
+
}
|
|
619
|
+
async function readDirectoryEntries$1(dir) {
|
|
620
|
+
try {
|
|
621
|
+
return await fs.readdir(dir, { withFileTypes: true });
|
|
622
|
+
} catch {
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
async function createSidebarItemFromEntry(params) {
|
|
627
|
+
const { dir, entry, rootDir } = params;
|
|
628
|
+
if (isIgnoredEntry(entry.name)) return null;
|
|
629
|
+
const fullPath = path.join(dir, entry.name);
|
|
630
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
631
|
+
if (entry.isDirectory()) return createDirectorySidebarItem(fullPath, relativePath, rootDir);
|
|
632
|
+
if (isMarkdownPage(entry.name)) return createMarkdownSidebarItem(fullPath, relativePath, entry.name);
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
function isIgnoredEntry(entryName) {
|
|
636
|
+
return entryName.startsWith(".") || entryName.startsWith("_");
|
|
637
|
+
}
|
|
638
|
+
function isMarkdownPage(entryName) {
|
|
639
|
+
return entryName.endsWith(".md") && entryName !== "index.md";
|
|
640
|
+
}
|
|
641
|
+
async function createDirectorySidebarItem(fullPath, relativePath, rootDir) {
|
|
642
|
+
const children = await scanDirectoryForSidebar(fullPath, rootDir);
|
|
643
|
+
if (children.length === 0) return null;
|
|
644
|
+
const metadata = await readDirectoryIndexMetadata(fullPath, relativePath);
|
|
645
|
+
const title = metadata.title ?? formatTitle$2(path.basename(fullPath));
|
|
646
|
+
return {
|
|
647
|
+
collapsed: false,
|
|
648
|
+
items: children,
|
|
649
|
+
link: metadata.link,
|
|
650
|
+
order: metadata.order,
|
|
651
|
+
text: title
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
async function readDirectoryIndexMetadata(fullPath, relativePath) {
|
|
655
|
+
const frontmatter = await readFrontmatter$1(path.join(fullPath, "index.md"));
|
|
656
|
+
return {
|
|
657
|
+
link: frontmatter == null ? void 0 : normalizePath(relativePath),
|
|
658
|
+
order: frontmatter?.order,
|
|
659
|
+
title: frontmatter?.title
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
async function createMarkdownSidebarItem(fullPath, relativePath, fileName) {
|
|
663
|
+
const frontmatter = await readFrontmatter$1(fullPath);
|
|
664
|
+
if (frontmatter?.sidebar === false) return null;
|
|
665
|
+
const fallbackTitle = formatTitle$2(fileName.replace(/\.md$/u, ""));
|
|
666
|
+
const title = frontmatter?.title ?? fallbackTitle;
|
|
667
|
+
return {
|
|
668
|
+
link: normalizePath(relativePath.replace(/\.md$/u, "")),
|
|
669
|
+
order: frontmatter?.order,
|
|
670
|
+
text: title
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
async function readFrontmatter$1(filePath) {
|
|
674
|
+
try {
|
|
675
|
+
return toSidebarFrontmatter(matter(await fs.readFile(filePath, "utf8")).data);
|
|
676
|
+
} catch {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function toSidebarFrontmatter(data) {
|
|
681
|
+
if (!isRecord$1(data)) return {};
|
|
682
|
+
return {
|
|
683
|
+
order: typeof data.order === "number" ? data.order : void 0,
|
|
684
|
+
sidebar: typeof data.sidebar === "boolean" ? data.sidebar : void 0,
|
|
685
|
+
title: typeof data.title === "string" ? data.title : void 0
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function isRecord$1(value) {
|
|
689
|
+
return value != null && typeof value === "object";
|
|
690
|
+
}
|
|
691
|
+
function sortSidebarItems(items) {
|
|
692
|
+
items.sort((left, right) => {
|
|
693
|
+
if (left.order != null && right.order != null) return left.order - right.order;
|
|
694
|
+
if (left.order != null) return -1;
|
|
695
|
+
if (right.order != null) return 1;
|
|
696
|
+
return left.text.localeCompare(right.text);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
function formatTitle$2(name) {
|
|
700
|
+
return name.replace(/^\d+-/u, "").replaceAll(/[_-]/gu, " ").replaceAll(/\b\w/gu, (char) => char.toUpperCase());
|
|
701
|
+
}
|
|
702
|
+
function normalizePath(p) {
|
|
703
|
+
return `/${p.replaceAll("\\", "/").replace(/^\/+/u, "")}`;
|
|
704
|
+
}
|
|
705
|
+
//#endregion
|
|
706
|
+
//#region src/vite/codeblock-scan.ts
|
|
707
|
+
const OPENING_TAG = "<ArdoCodeBlock";
|
|
708
|
+
const CLOSING_TAG = "</ArdoCodeBlock>";
|
|
709
|
+
/**
|
|
710
|
+
* Strips leading/trailing blank lines and removes common leading whitespace.
|
|
711
|
+
* Same logic as the runtime outdent in CodeBlock.tsx.
|
|
712
|
+
*/
|
|
422
713
|
function outdent(text) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
714
|
+
const trimmedLines = trimBlankLines(text.split("\n"));
|
|
715
|
+
if (trimmedLines.length === 0) return "";
|
|
716
|
+
const commonIndent = getCommonIndent(trimmedLines);
|
|
717
|
+
if (commonIndent === 0) return trimmedLines.join("\n");
|
|
718
|
+
return trimmedLines.map((line) => stripIndent(line, commonIndent)).join("\n");
|
|
719
|
+
}
|
|
720
|
+
function trimBlankLines(lines) {
|
|
721
|
+
const result = [...lines];
|
|
722
|
+
while (result.length > 0 && result[0].trim().length === 0) result.shift();
|
|
723
|
+
while (result.length > 0 && result.at(-1)?.trim().length === 0) result.pop();
|
|
724
|
+
return result;
|
|
725
|
+
}
|
|
726
|
+
function getCommonIndent(lines) {
|
|
727
|
+
let minIndent = Number.POSITIVE_INFINITY;
|
|
728
|
+
for (const line of lines) {
|
|
729
|
+
if (line.trim().length === 0) continue;
|
|
730
|
+
const indent = getLeadingWhitespaceLength(line);
|
|
731
|
+
if (indent < minIndent) minIndent = indent;
|
|
732
|
+
}
|
|
733
|
+
return Number.isFinite(minIndent) ? minIndent : 0;
|
|
734
|
+
}
|
|
735
|
+
function stripIndent(line, commonIndent) {
|
|
736
|
+
const availableIndent = getLeadingWhitespaceLength(line);
|
|
737
|
+
const removeCount = Math.min(commonIndent, availableIndent);
|
|
738
|
+
return line.slice(removeCount);
|
|
739
|
+
}
|
|
740
|
+
function getLeadingWhitespaceLength(line) {
|
|
741
|
+
let index = 0;
|
|
742
|
+
while (index < line.length) {
|
|
743
|
+
const char = line[index];
|
|
744
|
+
if (char !== " " && char !== " ") break;
|
|
745
|
+
index++;
|
|
746
|
+
}
|
|
747
|
+
return index;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Finds all `<ArdoCodeBlock ... />` and `<ArdoCodeBlock ...>...</ArdoCodeBlock>` tags
|
|
751
|
+
* by scanning for balanced quotes and braces.
|
|
752
|
+
*/
|
|
753
|
+
function scanArdoCodeBlocks(source) {
|
|
754
|
+
const blocks = [];
|
|
755
|
+
let cursor = 0;
|
|
756
|
+
while (cursor < source.length) {
|
|
757
|
+
const scanResult = scanNextCodeBlock(source, cursor);
|
|
758
|
+
if (scanResult == null) break;
|
|
759
|
+
const { block, nextCursor } = scanResult;
|
|
760
|
+
if (block != null) blocks.push(block);
|
|
761
|
+
cursor = nextCursor;
|
|
762
|
+
}
|
|
763
|
+
return blocks;
|
|
764
|
+
}
|
|
765
|
+
function scanNextCodeBlock(source, cursor) {
|
|
766
|
+
const start = source.indexOf(OPENING_TAG, cursor);
|
|
767
|
+
if (start === -1) return null;
|
|
768
|
+
const opening = scanOpeningTag(source, start);
|
|
769
|
+
if (opening == null) return {
|
|
770
|
+
block: null,
|
|
771
|
+
nextCursor: start + 14
|
|
772
|
+
};
|
|
773
|
+
const block = createScannedBlock(source, start, opening);
|
|
774
|
+
if (block == null) return null;
|
|
775
|
+
return {
|
|
776
|
+
block,
|
|
777
|
+
nextCursor: block.end
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function scanOpeningTag(source, start) {
|
|
781
|
+
const afterTag = start + 14;
|
|
782
|
+
if (!isWhitespaceChar(source[afterTag])) return null;
|
|
783
|
+
let index = afterTag;
|
|
784
|
+
let braceDepth = 0;
|
|
785
|
+
let quote = null;
|
|
786
|
+
while (index < source.length) {
|
|
787
|
+
const step = advanceOpeningTagScan(source, {
|
|
788
|
+
braceDepth,
|
|
789
|
+
index,
|
|
790
|
+
quote
|
|
791
|
+
});
|
|
792
|
+
if (step.tagEndIndex != null) return finalizeOpeningTag(source, afterTag, step.tagEndIndex);
|
|
793
|
+
index = step.nextIndex;
|
|
794
|
+
braceDepth = step.nextBraceDepth;
|
|
795
|
+
quote = step.nextQuote;
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
function finalizeOpeningTag(source, afterTag, endBracketIndex) {
|
|
800
|
+
const isSelfClosing = source[endBracketIndex - 1] === "/";
|
|
801
|
+
const propsEnd = isSelfClosing ? endBracketIndex - 1 : endBracketIndex;
|
|
802
|
+
const props = source.slice(afterTag, propsEnd).trim();
|
|
803
|
+
return {
|
|
804
|
+
end: endBracketIndex + 1,
|
|
805
|
+
isSelfClosing,
|
|
806
|
+
props
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function advanceOpeningTagScan(source, state) {
|
|
810
|
+
const { braceDepth, index, quote } = state;
|
|
811
|
+
if (quote != null) return scanQuotedStep(source, {
|
|
812
|
+
braceDepth,
|
|
813
|
+
index,
|
|
814
|
+
quote
|
|
815
|
+
});
|
|
816
|
+
return scanUnquotedStep(source, index, braceDepth);
|
|
817
|
+
}
|
|
818
|
+
function scanQuotedStep(source, state) {
|
|
819
|
+
const { braceDepth, index, quote } = state;
|
|
820
|
+
if (source[index] === "\\") return {
|
|
821
|
+
nextBraceDepth: braceDepth,
|
|
822
|
+
nextIndex: Math.min(source.length, index + 2),
|
|
823
|
+
nextQuote: quote,
|
|
824
|
+
tagEndIndex: null
|
|
825
|
+
};
|
|
826
|
+
const closesQuote = source[index] === quote;
|
|
827
|
+
return {
|
|
828
|
+
nextBraceDepth: braceDepth,
|
|
829
|
+
nextIndex: index + 1,
|
|
830
|
+
nextQuote: closesQuote ? null : quote,
|
|
831
|
+
tagEndIndex: null
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function scanUnquotedStep(source, index, braceDepth) {
|
|
835
|
+
const char = source[index];
|
|
836
|
+
if (isQuote(char)) return makeScanStep(index + 1, braceDepth, char);
|
|
837
|
+
if (char === "{") return makeScanStep(index + 1, braceDepth + 1, null);
|
|
838
|
+
if (char === "}") return makeScanStep(index + 1, Math.max(0, braceDepth - 1), null);
|
|
839
|
+
if (braceDepth === 0 && char === ">") return {
|
|
840
|
+
nextBraceDepth: braceDepth,
|
|
841
|
+
nextIndex: index + 1,
|
|
842
|
+
nextQuote: null,
|
|
843
|
+
tagEndIndex: index
|
|
844
|
+
};
|
|
845
|
+
return makeScanStep(index + 1, braceDepth, null);
|
|
846
|
+
}
|
|
847
|
+
function makeScanStep(nextIndex, nextBraceDepth, nextQuote) {
|
|
848
|
+
return {
|
|
849
|
+
nextBraceDepth,
|
|
850
|
+
nextIndex,
|
|
851
|
+
nextQuote,
|
|
852
|
+
tagEndIndex: null
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function createScannedBlock(source, start, opening) {
|
|
856
|
+
if (opening.isSelfClosing) return {
|
|
857
|
+
children: null,
|
|
858
|
+
end: opening.end,
|
|
859
|
+
fullMatch: source.slice(start, opening.end),
|
|
860
|
+
props: opening.props,
|
|
861
|
+
start
|
|
862
|
+
};
|
|
863
|
+
const closeStart = source.indexOf(CLOSING_TAG, opening.end);
|
|
864
|
+
if (closeStart === -1) return null;
|
|
865
|
+
const end = closeStart + 16;
|
|
866
|
+
return {
|
|
867
|
+
children: source.slice(opening.end, closeStart),
|
|
868
|
+
end,
|
|
869
|
+
fullMatch: source.slice(start, end),
|
|
870
|
+
props: opening.props,
|
|
871
|
+
start
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
function isQuote(char) {
|
|
875
|
+
return char === "'" || char === "\"" || char === "`";
|
|
876
|
+
}
|
|
877
|
+
function isWhitespaceChar(char) {
|
|
878
|
+
return char === " " || char === "\n" || char === "\r" || char === " ";
|
|
879
|
+
}
|
|
880
|
+
//#endregion
|
|
881
|
+
//#region src/vite/codeblock-transform.ts
|
|
882
|
+
async function transformArdoCodeBlocks(source, markdownConfig) {
|
|
883
|
+
let result = source;
|
|
884
|
+
let offset = 0;
|
|
885
|
+
const blocks = scanArdoCodeBlocks(source);
|
|
886
|
+
for (const block of blocks) {
|
|
887
|
+
const replacement = await createReplacement(block, markdownConfig);
|
|
888
|
+
if (replacement == null) continue;
|
|
889
|
+
const adjustedStart = block.start + offset;
|
|
890
|
+
const adjustedEnd = block.end + offset;
|
|
891
|
+
result = result.slice(0, adjustedStart) + replacement + result.slice(adjustedEnd);
|
|
892
|
+
offset += replacement.length - block.fullMatch.length;
|
|
893
|
+
}
|
|
894
|
+
return result;
|
|
895
|
+
}
|
|
896
|
+
async function createReplacement(block, markdownConfig) {
|
|
897
|
+
if (block.props.includes("__html")) return null;
|
|
898
|
+
if (block.children == null) return createSelfClosingReplacement(block, markdownConfig);
|
|
899
|
+
return createChildrenReplacement(block, markdownConfig);
|
|
900
|
+
}
|
|
901
|
+
async function createSelfClosingReplacement(block, markdownConfig) {
|
|
902
|
+
const codeValue = extractCodeValue(block.props);
|
|
903
|
+
const language = extractPropValue(block.props, "language");
|
|
904
|
+
if (codeValue == null || language == null) return null;
|
|
905
|
+
const html = await safeHighlightCode(codeValue, language, markdownConfig);
|
|
906
|
+
if (html == null) return null;
|
|
907
|
+
const newProps = `__html={${JSON.stringify(html)}} ${block.props}`;
|
|
908
|
+
return block.fullMatch.replace(block.props, newProps);
|
|
515
909
|
}
|
|
910
|
+
async function createChildrenReplacement(block, markdownConfig) {
|
|
911
|
+
const language = extractPropValue(block.props, "language");
|
|
912
|
+
if (language == null || block.children == null) return null;
|
|
913
|
+
const codeContent = outdent(unwrapTemplateChildren(block.children));
|
|
914
|
+
const html = await safeHighlightCode(codeContent, language, markdownConfig);
|
|
915
|
+
if (html == null) return null;
|
|
916
|
+
return `<ArdoCodeBlock __html={${JSON.stringify(html)}} code={${JSON.stringify(codeContent)}} ${block.props} />`;
|
|
917
|
+
}
|
|
918
|
+
function unwrapTemplateChildren(rawChildren) {
|
|
919
|
+
const trimmed = rawChildren.trim();
|
|
920
|
+
if (!trimmed.startsWith("{`") || !trimmed.endsWith("`}")) return rawChildren;
|
|
921
|
+
return trimmed.slice(2, -2);
|
|
922
|
+
}
|
|
923
|
+
function extractCodeValue(props) {
|
|
924
|
+
const code = extractPropValue(props, "code");
|
|
925
|
+
if (code == null) return null;
|
|
926
|
+
return decodeEscapedString(code);
|
|
927
|
+
}
|
|
928
|
+
function decodeEscapedString(value) {
|
|
929
|
+
return value.replaceAll("\\n", "\n").replaceAll("\\\"", "\"").replaceAll("\\\\", "\\");
|
|
930
|
+
}
|
|
931
|
+
function extractPropValue(props, propName) {
|
|
932
|
+
const patterns = getPropPatterns(propName);
|
|
933
|
+
for (const pattern of patterns) {
|
|
934
|
+
const match = pattern.exec(props);
|
|
935
|
+
if (match?.[1] != null) return match[1];
|
|
936
|
+
}
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
function getPropPatterns(propName) {
|
|
940
|
+
return [
|
|
941
|
+
new RegExp(`\\b${propName}="((?:[^"\\\\]|\\\\.)*)"`, "su"),
|
|
942
|
+
new RegExp(`\\b${propName}=\\{\\s*"((?:[^"\\\\]|\\\\.)*)"\\s*\\}`, "su"),
|
|
943
|
+
new RegExp(`\\b${propName}=\\{\\s*'((?:[^'\\\\]|\\\\.)*)'\\s*\\}`, "su")
|
|
944
|
+
];
|
|
945
|
+
}
|
|
946
|
+
async function safeHighlightCode(codeContent, language, markdownConfig) {
|
|
947
|
+
try {
|
|
948
|
+
return await highlightCode(codeContent, language, { theme: markdownConfig?.theme });
|
|
949
|
+
} catch {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/vite/codeblock-plugin.ts
|
|
955
|
+
/**
|
|
956
|
+
* Vite plugin that pre-highlights ArdoCodeBlock components at build time.
|
|
957
|
+
*
|
|
958
|
+
* Runs before the JSX parser, so children can contain arbitrary code
|
|
959
|
+
* (including `<`, `{`, etc.) without causing syntax errors.
|
|
960
|
+
*/
|
|
516
961
|
function ardoCodeBlockPlugin(markdownConfig) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
if (!codeMatch) continue;
|
|
531
|
-
const langMatch = propsStr.match(/\blanguage="([^"]*)"/) || propsStr.match(/\blanguage=\{"([^"]*)"\}/) || propsStr.match(/\blanguage=\{'([^']*)'\}/);
|
|
532
|
-
if (!langMatch) continue;
|
|
533
|
-
if (propsStr.includes("__html")) continue;
|
|
534
|
-
const codeContent = codeMatch[1].replace(/\\n/g, "\n").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
535
|
-
const language = langMatch[1];
|
|
536
|
-
try {
|
|
537
|
-
const html = await highlightCode(codeContent, language, {
|
|
538
|
-
theme: markdownConfig?.theme
|
|
539
|
-
});
|
|
540
|
-
const escapedHtml = JSON.stringify(html);
|
|
541
|
-
const newPropsStr = `__html={${escapedHtml}} ` + propsStr;
|
|
542
|
-
const newFullMatch = fullMatch.replace(propsStr, newPropsStr);
|
|
543
|
-
result = result.slice(0, match.index + offset) + newFullMatch + result.slice(match.index + offset + fullMatch.length);
|
|
544
|
-
offset += newFullMatch.length - fullMatch.length;
|
|
545
|
-
} catch {
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
const childrenRegex = /<CodeBlock\s+([^>]*?)>([\s\S]*?)<\/CodeBlock>/g;
|
|
549
|
-
offset = result.length - code.length;
|
|
550
|
-
let regexMatch;
|
|
551
|
-
while ((regexMatch = childrenRegex.exec(code)) !== null) {
|
|
552
|
-
const fullMatch = regexMatch[0];
|
|
553
|
-
const propsStr = regexMatch[1];
|
|
554
|
-
let rawChildren = regexMatch[2];
|
|
555
|
-
const langMatch = propsStr.match(/\blanguage="([^"]*)"/) || propsStr.match(/\blanguage=\{"([^"]*)"\}/) || propsStr.match(/\blanguage=\{'([^']*)'\}/);
|
|
556
|
-
if (!langMatch) continue;
|
|
557
|
-
if (propsStr.includes("__html")) continue;
|
|
558
|
-
const templateMatch = rawChildren.match(/^\s*\{`([\s\S]*)`\}\s*$/);
|
|
559
|
-
if (templateMatch) {
|
|
560
|
-
rawChildren = templateMatch[1];
|
|
561
|
-
}
|
|
562
|
-
const codeContent = outdent(rawChildren);
|
|
563
|
-
const language = langMatch[1];
|
|
564
|
-
try {
|
|
565
|
-
const html = await highlightCode(codeContent, language, {
|
|
566
|
-
theme: markdownConfig?.theme
|
|
567
|
-
});
|
|
568
|
-
const escapedHtml = JSON.stringify(html);
|
|
569
|
-
const escapedCode = JSON.stringify(codeContent);
|
|
570
|
-
const newTag = `<CodeBlock __html={${escapedHtml}} code={${escapedCode}} ${propsStr} />`;
|
|
571
|
-
result = result.slice(0, regexMatch.index + offset) + newTag + result.slice(regexMatch.index + offset + fullMatch.length);
|
|
572
|
-
offset += newTag.length - fullMatch.length;
|
|
573
|
-
} catch {
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
if (result !== code) {
|
|
577
|
-
return { code: result, map: null };
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
};
|
|
962
|
+
return {
|
|
963
|
+
enforce: "pre",
|
|
964
|
+
name: "ardo:codeblock-highlight",
|
|
965
|
+
async transform(code, id) {
|
|
966
|
+
if (!shouldProcessFile(code, id)) return;
|
|
967
|
+
const transformed = await transformArdoCodeBlocks(code, markdownConfig);
|
|
968
|
+
if (transformed === code) return;
|
|
969
|
+
return {
|
|
970
|
+
code: transformed,
|
|
971
|
+
map: null
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
};
|
|
581
975
|
}
|
|
582
|
-
|
|
583
|
-
|
|
976
|
+
function shouldProcessFile(code, id) {
|
|
977
|
+
if (!/\.[jt]sx$/u.test(id)) return false;
|
|
978
|
+
if (id.includes("node_modules")) return false;
|
|
979
|
+
return code.includes("ArdoCodeBlock");
|
|
980
|
+
}
|
|
981
|
+
//#endregion
|
|
982
|
+
//#region src/vite/git-utils.ts
|
|
983
|
+
/**
|
|
984
|
+
* Finds the package root by looking for package.json in parent directories.
|
|
985
|
+
* Returns the path relative to cwd, or undefined if not found.
|
|
986
|
+
*/
|
|
584
987
|
function findPackageRoot(cwd) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
988
|
+
let currentDir = path.resolve(cwd);
|
|
989
|
+
const filesystemRoot = path.parse(currentDir).root;
|
|
990
|
+
while (currentDir !== filesystemRoot) {
|
|
991
|
+
const parentDir = path.dirname(currentDir);
|
|
992
|
+
const packageJsonPath = path.join(parentDir, "package.json");
|
|
993
|
+
if (fsSync.existsSync(packageJsonPath)) {
|
|
994
|
+
const relativePath = path.relative(cwd, parentDir);
|
|
995
|
+
return relativePath === "" ? "." : relativePath;
|
|
996
|
+
}
|
|
997
|
+
currentDir = parentDir;
|
|
998
|
+
}
|
|
596
999
|
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Detects the GitHub repository name from git remote URL.
|
|
1002
|
+
* Returns the repo name (e.g., "ardo" from "github.com/sebastian-software/ardo")
|
|
1003
|
+
* or undefined if not a GitHub repo.
|
|
1004
|
+
*/
|
|
597
1005
|
function detectGitHubRepoName(cwd) {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
encoding: "utf-8",
|
|
602
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
603
|
-
}).trim();
|
|
604
|
-
const match = remoteUrl.match(/github\.com[/:][\w-]+\/([\w.-]+?)(?:\.git)?$/);
|
|
605
|
-
return match?.[1];
|
|
606
|
-
} catch {
|
|
607
|
-
return void 0;
|
|
608
|
-
}
|
|
1006
|
+
const remoteUrl = runGitCommand(cwd, "git remote get-url origin");
|
|
1007
|
+
if (remoteUrl == null) return;
|
|
1008
|
+
return parseGitHubRepoName(remoteUrl);
|
|
609
1009
|
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Detects the current short git commit hash.
|
|
1012
|
+
*/
|
|
610
1013
|
function detectGitHash(cwd) {
|
|
611
|
-
|
|
612
|
-
return execSync("git rev-parse --short HEAD", {
|
|
613
|
-
cwd,
|
|
614
|
-
encoding: "utf-8",
|
|
615
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
616
|
-
}).trim();
|
|
617
|
-
} catch {
|
|
618
|
-
return void 0;
|
|
619
|
-
}
|
|
1014
|
+
return runGitCommand(cwd, "git rev-parse --short HEAD");
|
|
620
1015
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
} else if (pkg.repository?.url) {
|
|
630
|
-
repository = pkg.repository.url.replace(/^git\+/, "").replace(/^git:\/\//, "https://").replace(/\.git$/, "");
|
|
631
|
-
}
|
|
632
|
-
let author;
|
|
633
|
-
if (typeof pkg.author === "string") {
|
|
634
|
-
author = pkg.author;
|
|
635
|
-
} else if (pkg.author?.name) {
|
|
636
|
-
author = pkg.author.name;
|
|
637
|
-
}
|
|
638
|
-
return {
|
|
639
|
-
name: pkg.name,
|
|
640
|
-
homepage: pkg.homepage,
|
|
641
|
-
repository,
|
|
642
|
-
version: pkg.version,
|
|
643
|
-
author,
|
|
644
|
-
license: pkg.license
|
|
645
|
-
};
|
|
646
|
-
} catch {
|
|
647
|
-
return {};
|
|
648
|
-
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Detects the GitHub Pages basename from the git remote URL.
|
|
1018
|
+
* Returns "/" in dev mode or when no GitHub repo is detected.
|
|
1019
|
+
*/
|
|
1020
|
+
function detectGitHubBasename(cwd) {
|
|
1021
|
+
if (process.env.NODE_ENV !== "production") return "/";
|
|
1022
|
+
const repoName = detectGitHubRepoName(cwd ?? process.cwd());
|
|
1023
|
+
return repoName != null ? `/${repoName}/` : "/";
|
|
649
1024
|
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Recursively copies files from src to dest, overwriting existing files.
|
|
1027
|
+
*/
|
|
650
1028
|
function copyRecursive(src, dest) {
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
copyRecursive(path2.join(src, item), path2.join(dest, item));
|
|
658
|
-
}
|
|
659
|
-
} else {
|
|
660
|
-
fsSync2.copyFileSync(src, dest);
|
|
661
|
-
}
|
|
1029
|
+
if (!fsSync.statSync(src).isDirectory()) {
|
|
1030
|
+
fsSync.copyFileSync(src, dest);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (!fsSync.existsSync(dest)) fsSync.mkdirSync(dest, { recursive: true });
|
|
1034
|
+
for (const item of fsSync.readdirSync(src)) copyRecursive(path.join(src, item), path.join(dest, item));
|
|
662
1035
|
}
|
|
663
|
-
function
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
function ardoPlugin(options = {}) {
|
|
679
|
-
let resolvedConfig;
|
|
680
|
-
let routesDir;
|
|
681
|
-
const {
|
|
682
|
-
routes,
|
|
683
|
-
typedoc,
|
|
684
|
-
githubPages = true,
|
|
685
|
-
routesDir: routesDirOption,
|
|
686
|
-
...pressConfig
|
|
687
|
-
} = options;
|
|
688
|
-
const mainPlugin = {
|
|
689
|
-
name: "ardo",
|
|
690
|
-
enforce: "pre",
|
|
691
|
-
config(userConfig, env) {
|
|
692
|
-
const root = userConfig.root || process.cwd();
|
|
693
|
-
routesDir = routesDirOption || path2.join(root, "app", "routes");
|
|
694
|
-
const result = {
|
|
695
|
-
define: {
|
|
696
|
-
__BUILD_TIME__: JSON.stringify((/* @__PURE__ */ new Date()).toISOString())
|
|
697
|
-
},
|
|
698
|
-
optimizeDeps: {
|
|
699
|
-
exclude: ["ardo/ui/styles.css"]
|
|
700
|
-
},
|
|
701
|
-
ssr: {
|
|
702
|
-
noExternal: ["ardo"]
|
|
703
|
-
}
|
|
704
|
-
};
|
|
705
|
-
if (githubPages && env.command === "build" && !userConfig.base) {
|
|
706
|
-
const repoName = detectGitHubRepoName(root);
|
|
707
|
-
if (repoName) {
|
|
708
|
-
result.base = `/${repoName}/`;
|
|
709
|
-
console.log(`[ardo] GitHub Pages detected, using base: ${result.base}`);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
return result;
|
|
713
|
-
},
|
|
714
|
-
async configResolved(config) {
|
|
715
|
-
const root = config.root;
|
|
716
|
-
routesDir = routesDirOption || path2.join(root, "app", "routes");
|
|
717
|
-
const detectedProject = readProjectMeta(root);
|
|
718
|
-
const project = { ...detectedProject, ...pressConfig.project };
|
|
719
|
-
const defaultConfig = {
|
|
720
|
-
title: pressConfig.title ?? "Ardo",
|
|
721
|
-
description: pressConfig.description ?? "Documentation powered by Ardo"
|
|
722
|
-
};
|
|
723
|
-
const configWithRoutes = {
|
|
724
|
-
...defaultConfig,
|
|
725
|
-
...pressConfig,
|
|
726
|
-
project,
|
|
727
|
-
srcDir: routesDir
|
|
728
|
-
};
|
|
729
|
-
resolvedConfig = resolveConfig(configWithRoutes, root);
|
|
730
|
-
},
|
|
731
|
-
resolveId(id) {
|
|
732
|
-
if (id === VIRTUAL_MODULE_ID) {
|
|
733
|
-
return RESOLVED_VIRTUAL_MODULE_ID;
|
|
734
|
-
}
|
|
735
|
-
if (id === VIRTUAL_SIDEBAR_ID) {
|
|
736
|
-
return RESOLVED_VIRTUAL_SIDEBAR_ID;
|
|
737
|
-
}
|
|
738
|
-
if (id === VIRTUAL_SEARCH_ID) {
|
|
739
|
-
return RESOLVED_VIRTUAL_SEARCH_ID;
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
|
-
async load(id) {
|
|
743
|
-
if (id === RESOLVED_VIRTUAL_MODULE_ID) {
|
|
744
|
-
const clientConfig = {
|
|
745
|
-
title: resolvedConfig.title,
|
|
746
|
-
description: resolvedConfig.description,
|
|
747
|
-
base: resolvedConfig.base,
|
|
748
|
-
lang: resolvedConfig.lang,
|
|
749
|
-
themeConfig: resolvedConfig.themeConfig,
|
|
750
|
-
project: resolvedConfig.project,
|
|
751
|
-
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
752
|
-
buildHash: detectGitHash(resolvedConfig.root)
|
|
753
|
-
};
|
|
754
|
-
return `export default ${JSON.stringify(clientConfig)}`;
|
|
755
|
-
}
|
|
756
|
-
if (id === RESOLVED_VIRTUAL_SIDEBAR_ID) {
|
|
757
|
-
const sidebar = await generateSidebar(resolvedConfig, routesDir);
|
|
758
|
-
return `export default ${JSON.stringify(sidebar)}`;
|
|
759
|
-
}
|
|
760
|
-
if (id === RESOLVED_VIRTUAL_SEARCH_ID) {
|
|
761
|
-
const searchIndex = await generateSearchIndex(routesDir);
|
|
762
|
-
return `export default ${JSON.stringify(searchIndex)}`;
|
|
763
|
-
}
|
|
764
|
-
},
|
|
765
|
-
transform(code, id) {
|
|
766
|
-
if (!/\.(mdx|md)$/.test(id)) return;
|
|
767
|
-
if (!id.startsWith(routesDir)) return;
|
|
768
|
-
if (/export\s+(const|function)\s+meta\b/.test(code)) return;
|
|
769
|
-
const titleMatch = code.match(
|
|
770
|
-
/export\s+const\s+frontmatter\s*=\s*\{[^}]*title\s*:\s*"([^"]*)"/
|
|
771
|
-
);
|
|
772
|
-
const descMatch = code.match(
|
|
773
|
-
/export\s+const\s+frontmatter\s*=\s*\{[^}]*description\s*:\s*"([^"]*)"/
|
|
774
|
-
);
|
|
775
|
-
const pageTitle = titleMatch?.[1];
|
|
776
|
-
if (!pageTitle) return;
|
|
777
|
-
const siteTitle = resolvedConfig.title;
|
|
778
|
-
const separator = resolvedConfig.titleSeparator;
|
|
779
|
-
const fullTitle = `${pageTitle}${separator}${siteTitle}`;
|
|
780
|
-
const description = descMatch?.[1];
|
|
781
|
-
const metaEntries = [`{ title: ${JSON.stringify(fullTitle)} }`];
|
|
782
|
-
if (description) {
|
|
783
|
-
metaEntries.push(`{ name: "description", content: ${JSON.stringify(description)} }`);
|
|
784
|
-
}
|
|
785
|
-
return {
|
|
786
|
-
code: `${code}
|
|
787
|
-
export const meta = () => [${metaEntries.join(", ")}];
|
|
788
|
-
`,
|
|
789
|
-
map: null
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
};
|
|
793
|
-
const plugins = [mainPlugin];
|
|
794
|
-
if (routes !== false) {
|
|
795
|
-
plugins.push(
|
|
796
|
-
ardoRoutesPlugin({
|
|
797
|
-
routesDir: routesDirOption,
|
|
798
|
-
...routes
|
|
799
|
-
})
|
|
800
|
-
);
|
|
801
|
-
}
|
|
802
|
-
if (typedoc) {
|
|
803
|
-
const packageRoot = findPackageRoot(process.cwd());
|
|
804
|
-
const defaultEntryPoint = packageRoot ? `${packageRoot}/src/index.ts` : "./src/index.ts";
|
|
805
|
-
const defaultTsconfig = packageRoot ? `${packageRoot}/tsconfig.json` : "./tsconfig.json";
|
|
806
|
-
const defaultTypedocConfig = {
|
|
807
|
-
enabled: true,
|
|
808
|
-
entryPoints: [defaultEntryPoint],
|
|
809
|
-
tsconfig: defaultTsconfig,
|
|
810
|
-
out: "api-reference",
|
|
811
|
-
excludePrivate: true,
|
|
812
|
-
excludeInternal: true
|
|
813
|
-
};
|
|
814
|
-
const typedocConfig = typedoc === true ? defaultTypedocConfig : { ...defaultTypedocConfig, ...typedoc };
|
|
815
|
-
const typedocPlugin = {
|
|
816
|
-
name: "ardo:typedoc",
|
|
817
|
-
async buildStart() {
|
|
818
|
-
if (typedocGenerated || !typedocConfig.enabled) {
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
console.log("[ardo] Generating API documentation with TypeDoc...");
|
|
822
|
-
const startTime = Date.now();
|
|
823
|
-
try {
|
|
824
|
-
const outputDir = routesDirOption || "./app/routes";
|
|
825
|
-
const docs = await generateApiDocs(typedocConfig, outputDir);
|
|
826
|
-
const duration = Date.now() - startTime;
|
|
827
|
-
console.log(`[ardo] Generated ${docs.length} API documentation pages in ${duration}ms`);
|
|
828
|
-
} catch (error) {
|
|
829
|
-
console.warn("[ardo] TypeDoc generation failed. API documentation will not be available.");
|
|
830
|
-
console.warn("[ardo] Check your typedoc.entryPoints configuration.");
|
|
831
|
-
if (error instanceof Error) {
|
|
832
|
-
console.warn(`[ardo] Error: ${error.message}`);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
typedocGenerated = true;
|
|
836
|
-
}
|
|
837
|
-
};
|
|
838
|
-
plugins.unshift(typedocPlugin);
|
|
839
|
-
}
|
|
840
|
-
plugins.push(ardoCodeBlockPlugin(pressConfig.markdown));
|
|
841
|
-
const themeConfig = pressConfig.markdown?.theme ?? defaultMarkdownConfig.theme;
|
|
842
|
-
const hasThemeObject = themeConfig && typeof themeConfig === "object" && "light" in themeConfig;
|
|
843
|
-
const lineNumbers = pressConfig.markdown?.lineNumbers || false;
|
|
844
|
-
const shikiOptions = hasThemeObject ? {
|
|
845
|
-
themes: {
|
|
846
|
-
light: themeConfig.light,
|
|
847
|
-
dark: themeConfig.dark
|
|
848
|
-
},
|
|
849
|
-
defaultColor: false,
|
|
850
|
-
transformers: [ardoLineTransformer({ globalLineNumbers: lineNumbers })]
|
|
851
|
-
} : {
|
|
852
|
-
theme: themeConfig,
|
|
853
|
-
transformers: [ardoLineTransformer({ globalLineNumbers: lineNumbers })]
|
|
854
|
-
};
|
|
855
|
-
const mdxPlugin = mdx({
|
|
856
|
-
include: /\.(md|mdx)$/,
|
|
857
|
-
remarkPlugins: [
|
|
858
|
-
remarkFrontmatter,
|
|
859
|
-
[remarkMdxFrontmatter, { name: "frontmatter" }],
|
|
860
|
-
remarkGfm,
|
|
861
|
-
remarkCodeMeta
|
|
862
|
-
],
|
|
863
|
-
rehypePlugins: [[rehypeShiki, shikiOptions]],
|
|
864
|
-
providerImportSource: "ardo/mdx-provider"
|
|
865
|
-
});
|
|
866
|
-
plugins.push(mdxPlugin);
|
|
867
|
-
plugins.push(...vanillaExtractPlugin());
|
|
868
|
-
const reactRouterPlugin = reactRouter();
|
|
869
|
-
const reactRouterPlugins = (Array.isArray(reactRouterPlugin) ? reactRouterPlugin : [reactRouterPlugin]).filter((p) => p != null);
|
|
870
|
-
plugins.push(...reactRouterPlugins);
|
|
871
|
-
if (githubPages) {
|
|
872
|
-
let detectedBase;
|
|
873
|
-
const flattenPlugin = {
|
|
874
|
-
name: "ardo:flatten-github-pages",
|
|
875
|
-
enforce: "post",
|
|
876
|
-
configResolved(config) {
|
|
877
|
-
if (config.base && config.base !== "/") {
|
|
878
|
-
detectedBase = config.base;
|
|
879
|
-
}
|
|
880
|
-
},
|
|
881
|
-
closeBundle() {
|
|
882
|
-
if (flattenExecuted || !detectedBase) {
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
const baseName = detectedBase.replace(/^\/|\/$/g, "");
|
|
886
|
-
if (!baseName) return;
|
|
887
|
-
const buildDir = path2.join(process.cwd(), "build", "client");
|
|
888
|
-
const nestedDir = path2.join(buildDir, baseName);
|
|
889
|
-
if (!fsSync2.existsSync(nestedDir)) {
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
console.log(`[ardo] Flattening build/client/${baseName}/ to build/client/ for GitHub Pages`);
|
|
893
|
-
copyRecursive(nestedDir, buildDir);
|
|
894
|
-
fsSync2.rmSync(nestedDir, { recursive: true, force: true });
|
|
895
|
-
console.log("[ardo] Build output flattened successfully.");
|
|
896
|
-
flattenExecuted = true;
|
|
897
|
-
}
|
|
898
|
-
};
|
|
899
|
-
plugins.push(flattenPlugin);
|
|
900
|
-
}
|
|
901
|
-
return plugins;
|
|
902
|
-
}
|
|
903
|
-
async function generateSidebar(config, routesDir) {
|
|
904
|
-
const { themeConfig } = config;
|
|
905
|
-
if (themeConfig.sidebar && !Array.isArray(themeConfig.sidebar)) {
|
|
906
|
-
return themeConfig.sidebar;
|
|
907
|
-
}
|
|
908
|
-
if (themeConfig.sidebar && Array.isArray(themeConfig.sidebar) && themeConfig.sidebar.length > 0) {
|
|
909
|
-
return themeConfig.sidebar;
|
|
910
|
-
}
|
|
911
|
-
try {
|
|
912
|
-
const sidebar = await scanDirectory(routesDir, routesDir);
|
|
913
|
-
return sidebar;
|
|
914
|
-
} catch {
|
|
915
|
-
return [];
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
async function scanDirectory(dir, rootDir) {
|
|
919
|
-
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
920
|
-
const items = [];
|
|
921
|
-
for (const entry of entries) {
|
|
922
|
-
const fullPath = path2.join(dir, entry.name);
|
|
923
|
-
const relativePath = path2.relative(rootDir, fullPath);
|
|
924
|
-
if (entry.isDirectory()) {
|
|
925
|
-
const children = await scanDirectory(fullPath, rootDir);
|
|
926
|
-
if (children.length > 0) {
|
|
927
|
-
const indexPath = path2.join(fullPath, "index.mdx");
|
|
928
|
-
let link;
|
|
929
|
-
try {
|
|
930
|
-
await fs2.access(indexPath);
|
|
931
|
-
link = "/" + relativePath.replace(/\\/g, "/");
|
|
932
|
-
} catch {
|
|
933
|
-
}
|
|
934
|
-
items.push({
|
|
935
|
-
text: formatTitle(entry.name),
|
|
936
|
-
link,
|
|
937
|
-
items: children
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
} else if ((entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) && entry.name !== "index.mdx" && entry.name !== "index.md") {
|
|
941
|
-
const fileContent = await fs2.readFile(fullPath, "utf-8");
|
|
942
|
-
const { data: frontmatter } = matter(fileContent);
|
|
943
|
-
const ext = entry.name.endsWith(".mdx") ? ".mdx" : ".md";
|
|
944
|
-
const title = frontmatter.title || formatTitle(entry.name.replace(ext, ""));
|
|
945
|
-
const order = typeof frontmatter.order === "number" ? frontmatter.order : void 0;
|
|
946
|
-
const link = "/" + relativePath.replace(ext, "").replace(/\\/g, "/");
|
|
947
|
-
items.push({
|
|
948
|
-
text: title,
|
|
949
|
-
link,
|
|
950
|
-
order
|
|
951
|
-
});
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
items.sort((a, b) => {
|
|
955
|
-
if (a.order !== void 0 && b.order !== void 0) {
|
|
956
|
-
return a.order - b.order;
|
|
957
|
-
}
|
|
958
|
-
if (a.order !== void 0) return -1;
|
|
959
|
-
if (b.order !== void 0) return 1;
|
|
960
|
-
return a.text.localeCompare(b.text);
|
|
961
|
-
});
|
|
962
|
-
return items.map(({ order: _order, ...item }) => item);
|
|
1036
|
+
function runGitCommand(cwd, command) {
|
|
1037
|
+
try {
|
|
1038
|
+
const commandResult = execSync(command, {
|
|
1039
|
+
cwd,
|
|
1040
|
+
encoding: "utf8",
|
|
1041
|
+
stdio: [
|
|
1042
|
+
"pipe",
|
|
1043
|
+
"pipe",
|
|
1044
|
+
"pipe"
|
|
1045
|
+
]
|
|
1046
|
+
}).trim();
|
|
1047
|
+
return commandResult === "" ? void 0 : commandResult;
|
|
1048
|
+
} catch {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
963
1051
|
}
|
|
964
|
-
function
|
|
965
|
-
|
|
1052
|
+
function parseGitHubRepoName(remoteUrl) {
|
|
1053
|
+
const normalizedUrl = remoteUrl.trim();
|
|
1054
|
+
if (!normalizedUrl.includes("github.com")) return;
|
|
1055
|
+
const urlSegments = (normalizedUrl.endsWith(".git") ? normalizedUrl.slice(0, -4) : normalizedUrl).replace(":", "/").split("/");
|
|
1056
|
+
const repoName = urlSegments.at(-1);
|
|
1057
|
+
const ownerName = urlSegments.at(-2);
|
|
1058
|
+
if (repoName == null || repoName === "" || ownerName == null || ownerName === "") return;
|
|
1059
|
+
return repoName;
|
|
966
1060
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
}
|
|
996
|
-
} catch (error) {
|
|
997
|
-
console.warn(
|
|
998
|
-
"[ardo] Failed to scan for search index:",
|
|
999
|
-
error instanceof Error ? error.message : error
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
await scanForSearch(routesDir);
|
|
1004
|
-
return docs;
|
|
1061
|
+
//#endregion
|
|
1062
|
+
//#region src/vite/flatten-plugin.ts
|
|
1063
|
+
let flattenExecuted = false;
|
|
1064
|
+
function createFlattenPlugin() {
|
|
1065
|
+
let detectedBase;
|
|
1066
|
+
return {
|
|
1067
|
+
name: "ardo:flatten-github-pages",
|
|
1068
|
+
enforce: "post",
|
|
1069
|
+
configResolved(config) {
|
|
1070
|
+
detectedBase = config.base === "/" ? void 0 : config.base;
|
|
1071
|
+
},
|
|
1072
|
+
closeBundle() {
|
|
1073
|
+
if (flattenExecuted || detectedBase == null) return;
|
|
1074
|
+
const baseName = trimSlashes(detectedBase);
|
|
1075
|
+
if (baseName === "") return;
|
|
1076
|
+
const buildDir = path.join(process.cwd(), "build", "client");
|
|
1077
|
+
const nestedDir = path.join(buildDir, baseName);
|
|
1078
|
+
if (!fsSync.existsSync(nestedDir)) return;
|
|
1079
|
+
console.log(`[ardo] Flattening build/client/${baseName}/ to build/client/ for GitHub Pages`);
|
|
1080
|
+
copyRecursive(nestedDir, buildDir);
|
|
1081
|
+
fsSync.rmSync(nestedDir, {
|
|
1082
|
+
recursive: true,
|
|
1083
|
+
force: true
|
|
1084
|
+
});
|
|
1085
|
+
console.log("[ardo] Build output flattened successfully.");
|
|
1086
|
+
flattenExecuted = true;
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1005
1089
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
function
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1090
|
+
function trimSlashes(value) {
|
|
1091
|
+
let trimmed = value;
|
|
1092
|
+
while (trimmed.startsWith("/")) trimmed = trimmed.slice(1);
|
|
1093
|
+
while (trimmed.endsWith("/")) trimmed = trimmed.slice(0, -1);
|
|
1094
|
+
return trimmed;
|
|
1095
|
+
}
|
|
1096
|
+
//#endregion
|
|
1097
|
+
//#region ../../node_modules/.pnpm/estree-util-value-to-estree@3.5.0/node_modules/estree-util-value-to-estree/dist/estree-util-value-to-estree.js
|
|
1098
|
+
/**
|
|
1099
|
+
* Create an ESTree identifier node for a given name.
|
|
1100
|
+
*
|
|
1101
|
+
* @param name
|
|
1102
|
+
* The name of the identifier.
|
|
1103
|
+
* @returns
|
|
1104
|
+
* The identifier node.
|
|
1105
|
+
*/
|
|
1106
|
+
function identifier(name) {
|
|
1107
|
+
return {
|
|
1108
|
+
type: "Identifier",
|
|
1109
|
+
name
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
/**
|
|
1113
|
+
* Create an ESTree literal node for a given value.
|
|
1114
|
+
*
|
|
1115
|
+
* @param value
|
|
1116
|
+
* The value for which to create a literal.
|
|
1117
|
+
* @returns
|
|
1118
|
+
* The literal node.
|
|
1119
|
+
*/
|
|
1120
|
+
function literal(value) {
|
|
1121
|
+
return {
|
|
1122
|
+
type: "Literal",
|
|
1123
|
+
value
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Create an ESTree call expression on an object member.
|
|
1128
|
+
*
|
|
1129
|
+
* @param object
|
|
1130
|
+
* The object to call the method on.
|
|
1131
|
+
* @param name
|
|
1132
|
+
* The name of the method to call.
|
|
1133
|
+
* @param args
|
|
1134
|
+
* Arguments to pass to the function call
|
|
1135
|
+
* @returns
|
|
1136
|
+
* The call expression node.
|
|
1137
|
+
*/
|
|
1138
|
+
function methodCall(object, name, args) {
|
|
1139
|
+
return {
|
|
1140
|
+
type: "CallExpression",
|
|
1141
|
+
optional: false,
|
|
1142
|
+
callee: {
|
|
1143
|
+
type: "MemberExpression",
|
|
1144
|
+
computed: false,
|
|
1145
|
+
optional: false,
|
|
1146
|
+
object,
|
|
1147
|
+
property: identifier(name)
|
|
1148
|
+
},
|
|
1149
|
+
arguments: args
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
/**
|
|
1153
|
+
* Turn a number or bigint into an ESTree expression. This handles positive and negative numbers and
|
|
1154
|
+
* bigints as well as special numbers.
|
|
1155
|
+
*
|
|
1156
|
+
* @param number
|
|
1157
|
+
* The value to turn into an ESTree expression.
|
|
1158
|
+
* @returns
|
|
1159
|
+
* An expression that represents the given value.
|
|
1160
|
+
*/
|
|
1161
|
+
function processNumber(number) {
|
|
1162
|
+
if (number < 0 || Object.is(number, -0)) return {
|
|
1163
|
+
type: "UnaryExpression",
|
|
1164
|
+
operator: "-",
|
|
1165
|
+
prefix: true,
|
|
1166
|
+
argument: processNumber(-number)
|
|
1167
|
+
};
|
|
1168
|
+
if (typeof number === "bigint") return {
|
|
1169
|
+
type: "Literal",
|
|
1170
|
+
bigint: String(number)
|
|
1171
|
+
};
|
|
1172
|
+
if (number === Number.POSITIVE_INFINITY || Number.isNaN(number)) return identifier(String(number));
|
|
1173
|
+
return literal(number);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Process an array of numbers. This is a shortcut for iterables whose constructor takes an array of
|
|
1177
|
+
* numbers as input.
|
|
1178
|
+
*
|
|
1179
|
+
* @param numbers
|
|
1180
|
+
* The numbers to add to the array expression.
|
|
1181
|
+
* @returns
|
|
1182
|
+
* An ESTree array expression whose elements match the input numbers.
|
|
1183
|
+
*/
|
|
1184
|
+
function processNumberArray(numbers) {
|
|
1185
|
+
return {
|
|
1186
|
+
type: "ArrayExpression",
|
|
1187
|
+
elements: Array.from(numbers, processNumber)
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Check whether a value can be constructed from its string representation.
|
|
1192
|
+
*
|
|
1193
|
+
* @param value
|
|
1194
|
+
* The value to check
|
|
1195
|
+
* @returns
|
|
1196
|
+
* Whether or not the value can be constructed from its string representation.
|
|
1197
|
+
*/
|
|
1198
|
+
function isStringReconstructable(value) {
|
|
1199
|
+
return value instanceof URL || value instanceof URLSearchParams;
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Check whether a value can be constructed from its `valueOf()` result.
|
|
1203
|
+
*
|
|
1204
|
+
* @param value
|
|
1205
|
+
* The value to check
|
|
1206
|
+
* @returns
|
|
1207
|
+
* Whether or not the value can be constructed from its `valueOf()` result.
|
|
1208
|
+
*/
|
|
1209
|
+
function isValueReconstructable(value) {
|
|
1210
|
+
return value instanceof Boolean || value instanceof Date || value instanceof Number || value instanceof String;
|
|
1211
|
+
}
|
|
1212
|
+
const wellKnownSymbols = /* @__PURE__ */ new Map();
|
|
1213
|
+
for (const name of Reflect.ownKeys(Symbol)) {
|
|
1214
|
+
const value = Symbol[name];
|
|
1215
|
+
if (typeof value === "symbol") wellKnownSymbols.set(value, name);
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Check whether a value is a Temporal value.
|
|
1219
|
+
*
|
|
1220
|
+
* @param value
|
|
1221
|
+
* The value to check
|
|
1222
|
+
* @returns
|
|
1223
|
+
* Whether or not the value is a Temporal value.
|
|
1224
|
+
*/
|
|
1225
|
+
function isTemporal(value) {
|
|
1226
|
+
return typeof Temporal !== "undefined" && (value instanceof Temporal.Duration || value instanceof Temporal.Instant || value instanceof Temporal.PlainDate || value instanceof Temporal.PlainDateTime || value instanceof Temporal.PlainYearMonth || value instanceof Temporal.PlainMonthDay || value instanceof Temporal.PlainTime || value instanceof Temporal.ZonedDateTime);
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Check whether a value is a typed array.
|
|
1230
|
+
*
|
|
1231
|
+
* @param value
|
|
1232
|
+
* The value to check
|
|
1233
|
+
* @returns
|
|
1234
|
+
* Whether or not the value is a typed array.
|
|
1235
|
+
*/
|
|
1236
|
+
function isTypedArray(value) {
|
|
1237
|
+
return value instanceof BigInt64Array || value instanceof BigUint64Array || typeof Float16Array !== "undefined" && value instanceof Float16Array || value instanceof Float32Array || value instanceof Float64Array || value instanceof Int8Array || value instanceof Int16Array || value instanceof Int32Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Uint16Array || value instanceof Uint32Array;
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Compare two value contexts for sorting them based on reference count.
|
|
1241
|
+
*
|
|
1242
|
+
* @param a
|
|
1243
|
+
* The first context to compare.
|
|
1244
|
+
* @param b
|
|
1245
|
+
* The second context to compare.
|
|
1246
|
+
* @returns
|
|
1247
|
+
* The count of context a minus the count of context b.
|
|
1248
|
+
*/
|
|
1249
|
+
function compareContexts(a, b) {
|
|
1250
|
+
const aReferencedByB = a.referencedBy.has(b.value);
|
|
1251
|
+
const bReferencedByA = b.referencedBy.has(a.value);
|
|
1252
|
+
if (aReferencedByB) {
|
|
1253
|
+
if (bReferencedByA) return a.count - b.count;
|
|
1254
|
+
return -1;
|
|
1255
|
+
}
|
|
1256
|
+
if (bReferencedByA) return 1;
|
|
1257
|
+
return a.count - b.count;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Replace the assigned right hand expression with the new expression.
|
|
1261
|
+
*
|
|
1262
|
+
* If there is no assignment expression, the original expression is returned. Otherwise the
|
|
1263
|
+
* assignment is modified and returned.
|
|
1264
|
+
*
|
|
1265
|
+
* @param expression
|
|
1266
|
+
* The expression to use for the assignment.
|
|
1267
|
+
* @param assignment
|
|
1268
|
+
* The existing assignmentexpression
|
|
1269
|
+
* @returns
|
|
1270
|
+
* The new expression.
|
|
1271
|
+
*/
|
|
1272
|
+
function replaceAssignment(expression, assignment) {
|
|
1273
|
+
if (!assignment || assignment.type !== "AssignmentExpression") return expression;
|
|
1274
|
+
let node = assignment;
|
|
1275
|
+
while (node.right.type === "AssignmentExpression") node = node.right;
|
|
1276
|
+
node.right = expression;
|
|
1277
|
+
return assignment;
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Create an ESTree epxression to represent a symbol. Global and well-known symbols are supported.
|
|
1281
|
+
*
|
|
1282
|
+
* @param symbol
|
|
1283
|
+
* The symbol to represent.
|
|
1284
|
+
* @returns
|
|
1285
|
+
* An ESTree expression to represent the symbol.
|
|
1286
|
+
*/
|
|
1287
|
+
function symbolToEstree(symbol) {
|
|
1288
|
+
const name = wellKnownSymbols.get(symbol);
|
|
1289
|
+
if (name) return {
|
|
1290
|
+
type: "MemberExpression",
|
|
1291
|
+
computed: false,
|
|
1292
|
+
optional: false,
|
|
1293
|
+
object: identifier("Symbol"),
|
|
1294
|
+
property: identifier(name)
|
|
1295
|
+
};
|
|
1296
|
+
if (symbol.description && symbol === Symbol.for(symbol.description)) return methodCall(identifier("Symbol"), "for", [literal(symbol.description)]);
|
|
1297
|
+
throw new TypeError(`Only global symbols are supported, got: ${String(symbol)}`, { cause: symbol });
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Create an ESTree property from a key and a value expression.
|
|
1301
|
+
*
|
|
1302
|
+
* @param key
|
|
1303
|
+
* The property key value
|
|
1304
|
+
* @param value
|
|
1305
|
+
* The property value as an ESTree expression.
|
|
1306
|
+
* @returns
|
|
1307
|
+
* The ESTree properry node.
|
|
1308
|
+
*/
|
|
1309
|
+
function property(key, value) {
|
|
1310
|
+
const isString = typeof key === "string";
|
|
1311
|
+
return {
|
|
1312
|
+
type: "Property",
|
|
1313
|
+
method: false,
|
|
1314
|
+
shorthand: false,
|
|
1315
|
+
computed: key === "__proto__" || !isString,
|
|
1316
|
+
kind: "init",
|
|
1317
|
+
key: isString ? literal(key) : symbolToEstree(key),
|
|
1318
|
+
value
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Convert a value to an ESTree node.
|
|
1323
|
+
*
|
|
1324
|
+
* @param value
|
|
1325
|
+
* The value to convert.
|
|
1326
|
+
* @param options
|
|
1327
|
+
* Additional options to configure the output.
|
|
1328
|
+
* @returns
|
|
1329
|
+
* The ESTree node.
|
|
1330
|
+
*/
|
|
1331
|
+
function valueToEstree(value, options = {}) {
|
|
1332
|
+
const stack = [];
|
|
1333
|
+
const collectedContexts = /* @__PURE__ */ new Map();
|
|
1334
|
+
const namedContexts = [];
|
|
1335
|
+
const customTrees = /* @__PURE__ */ new Map();
|
|
1336
|
+
/**
|
|
1337
|
+
* Analyze a value and collect all reference contexts.
|
|
1338
|
+
*
|
|
1339
|
+
* @param val
|
|
1340
|
+
* The value to analyze.
|
|
1341
|
+
*/
|
|
1342
|
+
function analyze(val) {
|
|
1343
|
+
if (typeof val !== "object" && typeof val !== "function") return;
|
|
1344
|
+
if (val == null) return;
|
|
1345
|
+
const context = collectedContexts.get(val);
|
|
1346
|
+
if (context) {
|
|
1347
|
+
if (options.preserveReferences) context.count += 1;
|
|
1348
|
+
for (const ancestor of stack) context.referencedBy.add(ancestor);
|
|
1349
|
+
if (stack.includes(val)) {
|
|
1350
|
+
if (!options.preserveReferences) throw new Error(`Found circular reference: ${val}`, { cause: val });
|
|
1351
|
+
const parent = stack.at(-1);
|
|
1352
|
+
const parentContext = collectedContexts.get(parent);
|
|
1353
|
+
parentContext.recursive = true;
|
|
1354
|
+
context.recursive = true;
|
|
1355
|
+
}
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
collectedContexts.set(val, {
|
|
1359
|
+
count: 1,
|
|
1360
|
+
recursive: false,
|
|
1361
|
+
referencedBy: new Set(stack),
|
|
1362
|
+
value: val
|
|
1363
|
+
});
|
|
1364
|
+
const estree = options?.replacer?.(val);
|
|
1365
|
+
if (estree) {
|
|
1366
|
+
customTrees.set(val, estree);
|
|
1367
|
+
return;
|
|
1368
|
+
}
|
|
1369
|
+
if (typeof val === "function") throw new TypeError(`Unsupported value: ${val}`, { cause: val });
|
|
1370
|
+
if (isTypedArray(val)) return;
|
|
1371
|
+
if (isStringReconstructable(val)) return;
|
|
1372
|
+
if (isValueReconstructable(val)) return;
|
|
1373
|
+
if (value instanceof RegExp) return;
|
|
1374
|
+
if (isTemporal(value)) return;
|
|
1375
|
+
stack.push(val);
|
|
1376
|
+
if (val instanceof Map) for (const pair of val) {
|
|
1377
|
+
analyze(pair[0]);
|
|
1378
|
+
analyze(pair[1]);
|
|
1379
|
+
}
|
|
1380
|
+
else if (Array.isArray(val) || val instanceof Set) for (const entry of val) analyze(entry);
|
|
1381
|
+
else {
|
|
1382
|
+
const proto = Object.getPrototypeOf(val);
|
|
1383
|
+
if (proto != null && proto !== Object.prototype && !options.instanceAsObject) throw new TypeError(`Unsupported value: ${val}`, { cause: val });
|
|
1384
|
+
for (const key of Reflect.ownKeys(val)) analyze(val[key]);
|
|
1385
|
+
}
|
|
1386
|
+
stack.pop();
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Recursively generate the ESTree expression needed to reconstruct the value.
|
|
1390
|
+
*
|
|
1391
|
+
* @param val
|
|
1392
|
+
* The value to process.
|
|
1393
|
+
* @param isDeclaration
|
|
1394
|
+
* Whether or not this is for a variable declaration.
|
|
1395
|
+
* @returns
|
|
1396
|
+
* The ESTree expression to reconstruct the value.
|
|
1397
|
+
*/
|
|
1398
|
+
function generate(val, isDeclaration) {
|
|
1399
|
+
if (val === void 0) return identifier(String(val));
|
|
1400
|
+
if (val == null || typeof val === "string" || typeof val === "boolean") return literal(val);
|
|
1401
|
+
if (typeof val === "bigint" || typeof val === "number") return processNumber(val);
|
|
1402
|
+
if (typeof val === "symbol") return symbolToEstree(val);
|
|
1403
|
+
const context = collectedContexts.get(val);
|
|
1404
|
+
if (!isDeclaration && context?.name) return identifier(context.name);
|
|
1405
|
+
const tree = customTrees.get(val);
|
|
1406
|
+
if (tree) return tree;
|
|
1407
|
+
if (isValueReconstructable(val)) return {
|
|
1408
|
+
type: "NewExpression",
|
|
1409
|
+
callee: identifier(val.constructor.name),
|
|
1410
|
+
arguments: [generate(val.valueOf())]
|
|
1411
|
+
};
|
|
1412
|
+
if (val instanceof RegExp) return {
|
|
1413
|
+
type: "Literal",
|
|
1414
|
+
regex: {
|
|
1415
|
+
pattern: val.source,
|
|
1416
|
+
flags: val.flags
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
if (typeof Buffer !== "undefined" && Buffer.isBuffer(val)) return methodCall(identifier("Buffer"), "from", [processNumberArray(val)]);
|
|
1420
|
+
if (isTypedArray(val)) return {
|
|
1421
|
+
type: "NewExpression",
|
|
1422
|
+
callee: identifier(val.constructor.name),
|
|
1423
|
+
arguments: [processNumberArray(val)]
|
|
1424
|
+
};
|
|
1425
|
+
if (isStringReconstructable(val)) return {
|
|
1426
|
+
type: "NewExpression",
|
|
1427
|
+
callee: identifier(val.constructor.name),
|
|
1428
|
+
arguments: [literal(String(val))]
|
|
1429
|
+
};
|
|
1430
|
+
if (isTemporal(val)) return methodCall({
|
|
1431
|
+
type: "MemberExpression",
|
|
1432
|
+
computed: false,
|
|
1433
|
+
optional: false,
|
|
1434
|
+
object: identifier("Temporal"),
|
|
1435
|
+
property: identifier(val.constructor.name)
|
|
1436
|
+
}, "from", [literal(String(val))]);
|
|
1437
|
+
if (Array.isArray(val)) {
|
|
1438
|
+
const elements = Array.from({ length: val.length });
|
|
1439
|
+
let trimmable;
|
|
1440
|
+
for (let index = 0; index < val.length; index += 1) {
|
|
1441
|
+
if (!(index in val)) {
|
|
1442
|
+
elements[index] = null;
|
|
1443
|
+
trimmable = void 0;
|
|
1444
|
+
continue;
|
|
1445
|
+
}
|
|
1446
|
+
const child = val[index];
|
|
1447
|
+
const childContext = collectedContexts.get(child);
|
|
1448
|
+
if (context && childContext && namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) {
|
|
1449
|
+
elements[index] = null;
|
|
1450
|
+
trimmable ||= index;
|
|
1451
|
+
childContext.assignment = {
|
|
1452
|
+
type: "AssignmentExpression",
|
|
1453
|
+
operator: "=",
|
|
1454
|
+
left: {
|
|
1455
|
+
type: "MemberExpression",
|
|
1456
|
+
computed: true,
|
|
1457
|
+
optional: false,
|
|
1458
|
+
object: identifier(context.name),
|
|
1459
|
+
property: literal(index)
|
|
1460
|
+
},
|
|
1461
|
+
right: childContext.assignment || identifier(childContext.name)
|
|
1462
|
+
};
|
|
1463
|
+
} else {
|
|
1464
|
+
elements[index] = generate(child);
|
|
1465
|
+
trimmable = void 0;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (trimmable != null) elements.splice(trimmable);
|
|
1469
|
+
return {
|
|
1470
|
+
type: "ArrayExpression",
|
|
1471
|
+
elements
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
if (val instanceof Set) {
|
|
1475
|
+
const elements = [];
|
|
1476
|
+
let finalizer;
|
|
1477
|
+
for (const child of val) if (finalizer) finalizer = methodCall(finalizer, "add", [generate(child)]);
|
|
1478
|
+
else {
|
|
1479
|
+
const childContext = collectedContexts.get(child);
|
|
1480
|
+
if (context && childContext && namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) finalizer = methodCall(identifier(context.name), "add", [generate(child)]);
|
|
1481
|
+
else elements.push(generate(child));
|
|
1482
|
+
}
|
|
1483
|
+
if (context && finalizer) context.assignment = replaceAssignment(finalizer, context.assignment);
|
|
1484
|
+
return {
|
|
1485
|
+
type: "NewExpression",
|
|
1486
|
+
callee: identifier("Set"),
|
|
1487
|
+
arguments: elements.length ? [{
|
|
1488
|
+
type: "ArrayExpression",
|
|
1489
|
+
elements
|
|
1490
|
+
}] : []
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
if (val instanceof Map) {
|
|
1494
|
+
const elements = [];
|
|
1495
|
+
let finalizer;
|
|
1496
|
+
for (const [key, item] of val) if (finalizer) finalizer = methodCall(finalizer, "set", [generate(key), generate(item)]);
|
|
1497
|
+
else {
|
|
1498
|
+
const keyContext = collectedContexts.get(key);
|
|
1499
|
+
const itemContext = collectedContexts.get(item);
|
|
1500
|
+
if (context && (keyContext && namedContexts.indexOf(keyContext) >= namedContexts.indexOf(context) || itemContext && namedContexts.indexOf(itemContext) >= namedContexts.indexOf(context))) finalizer = methodCall(identifier(context.name), "set", [generate(key), generate(item)]);
|
|
1501
|
+
else elements.push({
|
|
1502
|
+
type: "ArrayExpression",
|
|
1503
|
+
elements: [generate(key), generate(item)]
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
if (context && finalizer) context.assignment = replaceAssignment(finalizer, context.assignment);
|
|
1507
|
+
return {
|
|
1508
|
+
type: "NewExpression",
|
|
1509
|
+
callee: identifier("Map"),
|
|
1510
|
+
arguments: elements.length ? [{
|
|
1511
|
+
type: "ArrayExpression",
|
|
1512
|
+
elements
|
|
1513
|
+
}] : []
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
const properties = [];
|
|
1517
|
+
if (Object.getPrototypeOf(val) == null) properties.push({
|
|
1518
|
+
type: "Property",
|
|
1519
|
+
method: false,
|
|
1520
|
+
shorthand: false,
|
|
1521
|
+
computed: false,
|
|
1522
|
+
kind: "init",
|
|
1523
|
+
key: identifier("__proto__"),
|
|
1524
|
+
value: literal(null)
|
|
1525
|
+
});
|
|
1526
|
+
const object = val;
|
|
1527
|
+
const propertyDescriptors = [];
|
|
1528
|
+
for (const key of Reflect.ownKeys(val)) {
|
|
1529
|
+
const child = object[key];
|
|
1530
|
+
const { configurable, enumerable, writable } = Object.getOwnPropertyDescriptor(val, key);
|
|
1531
|
+
const childContext = collectedContexts.get(child);
|
|
1532
|
+
if (!configurable || !enumerable || !writable) {
|
|
1533
|
+
const propertyDescriptor = [property("value", generate(child))];
|
|
1534
|
+
if (configurable) propertyDescriptor.push(property("configurable", literal(true)));
|
|
1535
|
+
if (enumerable) propertyDescriptor.push(property("enumerable", literal(true)));
|
|
1536
|
+
if (writable) propertyDescriptor.push(property("writable", literal(true)));
|
|
1537
|
+
propertyDescriptors.push([key, {
|
|
1538
|
+
type: "ObjectExpression",
|
|
1539
|
+
properties: propertyDescriptor
|
|
1540
|
+
}]);
|
|
1541
|
+
} else if (context && childContext && namedContexts.indexOf(childContext) >= namedContexts.indexOf(context)) if (key === "__proto__") propertyDescriptors.push([key, {
|
|
1542
|
+
type: "ObjectExpression",
|
|
1543
|
+
properties: [
|
|
1544
|
+
property("value", generate(child)),
|
|
1545
|
+
property("configurable", literal(true)),
|
|
1546
|
+
property("enumerable", literal(true)),
|
|
1547
|
+
property("writable", literal(true))
|
|
1548
|
+
]
|
|
1549
|
+
}]);
|
|
1550
|
+
else childContext.assignment = {
|
|
1551
|
+
type: "AssignmentExpression",
|
|
1552
|
+
operator: "=",
|
|
1553
|
+
left: {
|
|
1554
|
+
type: "MemberExpression",
|
|
1555
|
+
computed: true,
|
|
1556
|
+
optional: false,
|
|
1557
|
+
object: identifier(context.name),
|
|
1558
|
+
property: generate(key)
|
|
1559
|
+
},
|
|
1560
|
+
right: childContext.assignment || generate(child)
|
|
1561
|
+
};
|
|
1562
|
+
else properties.push(property(key, generate(child)));
|
|
1563
|
+
}
|
|
1564
|
+
const objectExpression = {
|
|
1565
|
+
type: "ObjectExpression",
|
|
1566
|
+
properties
|
|
1567
|
+
};
|
|
1568
|
+
if (propertyDescriptors.length) {
|
|
1569
|
+
let name;
|
|
1570
|
+
let args;
|
|
1571
|
+
if (propertyDescriptors.length === 1) {
|
|
1572
|
+
const [[key, expression]] = propertyDescriptors;
|
|
1573
|
+
name = "defineProperty";
|
|
1574
|
+
args = [typeof key === "string" ? literal(key) : symbolToEstree(key), expression];
|
|
1575
|
+
} else {
|
|
1576
|
+
name = "defineProperties";
|
|
1577
|
+
args = [{
|
|
1578
|
+
type: "ObjectExpression",
|
|
1579
|
+
properties: propertyDescriptors.map(([key, expression]) => property(key, expression))
|
|
1580
|
+
}];
|
|
1581
|
+
}
|
|
1582
|
+
if (!context) return methodCall(identifier("Object"), name, [objectExpression, ...args]);
|
|
1583
|
+
context.assignment = replaceAssignment(methodCall(identifier("Object"), name, [identifier(context.name), ...args]), context.assignment);
|
|
1584
|
+
}
|
|
1585
|
+
return objectExpression;
|
|
1586
|
+
}
|
|
1587
|
+
analyze(value);
|
|
1588
|
+
for (const [val, context] of collectedContexts) if (context.recursive || context.count > 1) {
|
|
1589
|
+
context.name = `$${namedContexts.length}`;
|
|
1590
|
+
namedContexts.push(context);
|
|
1591
|
+
} else collectedContexts.delete(val);
|
|
1592
|
+
if (!namedContexts.length) return generate(value);
|
|
1593
|
+
const params = namedContexts.sort(compareContexts).map((context) => ({
|
|
1594
|
+
type: "AssignmentPattern",
|
|
1595
|
+
left: identifier(context.name),
|
|
1596
|
+
right: generate(context.value, true)
|
|
1597
|
+
}));
|
|
1598
|
+
const rootContext = collectedContexts.get(value);
|
|
1599
|
+
const finalizers = [];
|
|
1600
|
+
for (const context of collectedContexts.values()) if (context !== rootContext && context.assignment) finalizers.push(context.assignment);
|
|
1601
|
+
finalizers.push(rootContext ? rootContext.assignment || identifier(rootContext.name) : generate(value));
|
|
1602
|
+
return {
|
|
1603
|
+
type: "CallExpression",
|
|
1604
|
+
optional: false,
|
|
1605
|
+
arguments: [],
|
|
1606
|
+
callee: {
|
|
1607
|
+
type: "ArrowFunctionExpression",
|
|
1608
|
+
expression: false,
|
|
1609
|
+
params,
|
|
1610
|
+
body: {
|
|
1611
|
+
type: "SequenceExpression",
|
|
1612
|
+
expressions: finalizers
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
//#endregion
|
|
1618
|
+
//#region ../../node_modules/.pnpm/estree-util-is-identifier-name@3.0.0/node_modules/estree-util-is-identifier-name/lib/index.js
|
|
1619
|
+
const nameRe = /^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u;
|
|
1620
|
+
const nameReJsx = /^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u;
|
|
1621
|
+
/** @type {Options} */
|
|
1622
|
+
const emptyOptions = {};
|
|
1623
|
+
/**
|
|
1624
|
+
* Checks if the given value is a valid identifier name.
|
|
1625
|
+
*
|
|
1626
|
+
* @param {string} name
|
|
1627
|
+
* Identifier to check.
|
|
1628
|
+
* @param {Options | null | undefined} [options]
|
|
1629
|
+
* Configuration (optional).
|
|
1630
|
+
* @returns {boolean}
|
|
1631
|
+
* Whether `name` can be an identifier.
|
|
1632
|
+
*/
|
|
1633
|
+
function name(name, options) {
|
|
1634
|
+
return ((options || emptyOptions).jsx ? nameReJsx : nameRe).test(name);
|
|
1635
|
+
}
|
|
1636
|
+
//#endregion
|
|
1637
|
+
//#region ../../node_modules/.pnpm/estree-util-scope@1.0.0/node_modules/estree-util-scope/lib/index.js
|
|
1638
|
+
/**
|
|
1639
|
+
* @import {Node, Pattern} from 'estree'
|
|
1640
|
+
* @import {Scope, Visitors} from './types.js'
|
|
1641
|
+
*/
|
|
1642
|
+
/**
|
|
1643
|
+
* Create state to track what’s defined.
|
|
1644
|
+
*
|
|
1645
|
+
* @returns {Visitors}
|
|
1646
|
+
* State.
|
|
1647
|
+
*/
|
|
1648
|
+
function createVisitors() {
|
|
1649
|
+
/** @type {[topLevel: Scope, ...rest: Array<Scope>]} */
|
|
1650
|
+
const scopes = [{
|
|
1651
|
+
block: false,
|
|
1652
|
+
defined: []
|
|
1653
|
+
}];
|
|
1654
|
+
return {
|
|
1655
|
+
enter,
|
|
1656
|
+
exit,
|
|
1657
|
+
scopes
|
|
1658
|
+
};
|
|
1659
|
+
/**
|
|
1660
|
+
* @param {Node} node
|
|
1661
|
+
* Node.
|
|
1662
|
+
* @returns {undefined}
|
|
1663
|
+
* Nothing.
|
|
1664
|
+
*/
|
|
1665
|
+
function enter(node) {
|
|
1666
|
+
if (node.type === "ArrowFunctionExpression") {
|
|
1667
|
+
scopes.push({
|
|
1668
|
+
block: false,
|
|
1669
|
+
defined: []
|
|
1670
|
+
});
|
|
1671
|
+
for (const parameter of node.params) definePattern(parameter, false);
|
|
1672
|
+
} else if (node.type === "BlockStatement" || node.type === "DoWhileStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement" || node.type === "ForStatement" || node.type === "WhileStatement") scopes.push({
|
|
1673
|
+
block: true,
|
|
1674
|
+
defined: []
|
|
1675
|
+
});
|
|
1676
|
+
else if (node.type === "CatchClause") {
|
|
1677
|
+
scopes.push({
|
|
1678
|
+
block: true,
|
|
1679
|
+
defined: []
|
|
1680
|
+
});
|
|
1681
|
+
if (node.param) definePattern(node.param, true);
|
|
1682
|
+
} else if (node.type === "ClassDeclaration") defineIdentifier(node.id.name, false);
|
|
1683
|
+
else if (node.type === "FunctionDeclaration") {
|
|
1684
|
+
defineIdentifier(node.id.name, false);
|
|
1685
|
+
scopes.push({
|
|
1686
|
+
block: false,
|
|
1687
|
+
defined: []
|
|
1688
|
+
});
|
|
1689
|
+
for (const parameter of node.params) definePattern(parameter, false);
|
|
1690
|
+
} else if (node.type === "FunctionExpression") {
|
|
1691
|
+
if (node.id) defineIdentifier(node.id.name, false);
|
|
1692
|
+
scopes.push({
|
|
1693
|
+
block: false,
|
|
1694
|
+
defined: []
|
|
1695
|
+
});
|
|
1696
|
+
for (const parameter of node.params) definePattern(parameter, false);
|
|
1697
|
+
} else if (node.type === "ImportDeclaration") for (const specifier of node.specifiers) defineIdentifier(specifier.local.name, false);
|
|
1698
|
+
else if (node.type === "VariableDeclaration") for (const declaration of node.declarations) definePattern(declaration.id, node.kind !== "var");
|
|
1699
|
+
}
|
|
1700
|
+
/**
|
|
1701
|
+
* @param {Node} node
|
|
1702
|
+
* Node.
|
|
1703
|
+
* @returns {undefined}
|
|
1704
|
+
* Nothing.
|
|
1705
|
+
*/
|
|
1706
|
+
function exit(node) {
|
|
1707
|
+
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionDeclaration" || node.type === "FunctionExpression") scopes.pop().block;
|
|
1708
|
+
else if (node.type === "BlockStatement" || node.type === "CatchClause" || node.type === "DoWhileStatement" || node.type === "ForInStatement" || node.type === "ForOfStatement" || node.type === "ForStatement" || node.type === "WhileStatement") scopes.pop().block;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Define an identifier in a scope.
|
|
1712
|
+
*
|
|
1713
|
+
* @param {string} id
|
|
1714
|
+
* @param {boolean} block
|
|
1715
|
+
* @returns {undefined}
|
|
1716
|
+
*/
|
|
1717
|
+
function defineIdentifier(id, block) {
|
|
1718
|
+
let index = scopes.length;
|
|
1719
|
+
/** @type {Scope | undefined} */
|
|
1720
|
+
let scope;
|
|
1721
|
+
while (index--) {
|
|
1722
|
+
scope = scopes[index];
|
|
1723
|
+
if (block || !scope.block) break;
|
|
1724
|
+
}
|
|
1725
|
+
scope.defined.push(id);
|
|
1726
|
+
}
|
|
1727
|
+
/**
|
|
1728
|
+
* Define a pattern in a scope.
|
|
1729
|
+
*
|
|
1730
|
+
* @param {Pattern} pattern
|
|
1731
|
+
* @param {boolean} block
|
|
1732
|
+
*/
|
|
1733
|
+
function definePattern(pattern, block) {
|
|
1734
|
+
if (pattern.type === "ArrayPattern") {
|
|
1735
|
+
for (const element of pattern.elements) if (element) definePattern(element, block);
|
|
1736
|
+
} else if (pattern.type === "AssignmentPattern") definePattern(pattern.left, block);
|
|
1737
|
+
else if (pattern.type === "Identifier") defineIdentifier(pattern.name, block);
|
|
1738
|
+
else if (pattern.type === "ObjectPattern") for (const property of pattern.properties) if (property.type === "Property") definePattern(property.value, block);
|
|
1739
|
+
else {
|
|
1740
|
+
property.type;
|
|
1741
|
+
definePattern(property, block);
|
|
1742
|
+
}
|
|
1743
|
+
else {
|
|
1744
|
+
pattern.type;
|
|
1745
|
+
definePattern(pattern.argument, block);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
//#endregion
|
|
1750
|
+
//#region ../../node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/walker.js
|
|
1751
|
+
/**
|
|
1752
|
+
* @typedef { import('estree').Node} Node
|
|
1753
|
+
* @typedef {{
|
|
1754
|
+
* skip: () => void;
|
|
1755
|
+
* remove: () => void;
|
|
1756
|
+
* replace: (node: Node) => void;
|
|
1757
|
+
* }} WalkerContext
|
|
1758
|
+
*/
|
|
1759
|
+
var WalkerBase = class {
|
|
1760
|
+
constructor() {
|
|
1761
|
+
/** @type {boolean} */
|
|
1762
|
+
this.should_skip = false;
|
|
1763
|
+
/** @type {boolean} */
|
|
1764
|
+
this.should_remove = false;
|
|
1765
|
+
/** @type {Node | null} */
|
|
1766
|
+
this.replacement = null;
|
|
1767
|
+
/** @type {WalkerContext} */
|
|
1768
|
+
this.context = {
|
|
1769
|
+
skip: () => this.should_skip = true,
|
|
1770
|
+
remove: () => this.should_remove = true,
|
|
1771
|
+
replace: (node) => this.replacement = node
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* @template {Node} Parent
|
|
1776
|
+
* @param {Parent | null | undefined} parent
|
|
1777
|
+
* @param {keyof Parent | null | undefined} prop
|
|
1778
|
+
* @param {number | null | undefined} index
|
|
1779
|
+
* @param {Node} node
|
|
1780
|
+
*/
|
|
1781
|
+
replace(parent, prop, index, node) {
|
|
1782
|
+
if (parent && prop) if (index != null)
|
|
1783
|
+
/** @type {Array<Node>} */ parent[prop][index] = node;
|
|
1784
|
+
else
|
|
1785
|
+
/** @type {Node} */ parent[prop] = node;
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* @template {Node} Parent
|
|
1789
|
+
* @param {Parent | null | undefined} parent
|
|
1790
|
+
* @param {keyof Parent | null | undefined} prop
|
|
1791
|
+
* @param {number | null | undefined} index
|
|
1792
|
+
*/
|
|
1793
|
+
remove(parent, prop, index) {
|
|
1794
|
+
if (parent && prop) if (index !== null && index !== void 0)
|
|
1795
|
+
/** @type {Array<Node>} */ parent[prop].splice(index, 1);
|
|
1796
|
+
else delete parent[prop];
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
//#endregion
|
|
1800
|
+
//#region ../../node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/sync.js
|
|
1801
|
+
/**
|
|
1802
|
+
* @typedef { import('estree').Node} Node
|
|
1803
|
+
* @typedef { import('./walker.js').WalkerContext} WalkerContext
|
|
1804
|
+
* @typedef {(
|
|
1805
|
+
* this: WalkerContext,
|
|
1806
|
+
* node: Node,
|
|
1807
|
+
* parent: Node | null,
|
|
1808
|
+
* key: string | number | symbol | null | undefined,
|
|
1809
|
+
* index: number | null | undefined
|
|
1810
|
+
* ) => void} SyncHandler
|
|
1811
|
+
*/
|
|
1812
|
+
var SyncWalker = class extends WalkerBase {
|
|
1813
|
+
/**
|
|
1814
|
+
*
|
|
1815
|
+
* @param {SyncHandler} [enter]
|
|
1816
|
+
* @param {SyncHandler} [leave]
|
|
1817
|
+
*/
|
|
1818
|
+
constructor(enter, leave) {
|
|
1819
|
+
super();
|
|
1820
|
+
/** @type {boolean} */
|
|
1821
|
+
this.should_skip = false;
|
|
1822
|
+
/** @type {boolean} */
|
|
1823
|
+
this.should_remove = false;
|
|
1824
|
+
/** @type {Node | null} */
|
|
1825
|
+
this.replacement = null;
|
|
1826
|
+
/** @type {WalkerContext} */
|
|
1827
|
+
this.context = {
|
|
1828
|
+
skip: () => this.should_skip = true,
|
|
1829
|
+
remove: () => this.should_remove = true,
|
|
1830
|
+
replace: (node) => this.replacement = node
|
|
1831
|
+
};
|
|
1832
|
+
/** @type {SyncHandler | undefined} */
|
|
1833
|
+
this.enter = enter;
|
|
1834
|
+
/** @type {SyncHandler | undefined} */
|
|
1835
|
+
this.leave = leave;
|
|
1836
|
+
}
|
|
1837
|
+
/**
|
|
1838
|
+
* @template {Node} Parent
|
|
1839
|
+
* @param {Node} node
|
|
1840
|
+
* @param {Parent | null} parent
|
|
1841
|
+
* @param {keyof Parent} [prop]
|
|
1842
|
+
* @param {number | null} [index]
|
|
1843
|
+
* @returns {Node | null}
|
|
1844
|
+
*/
|
|
1845
|
+
visit(node, parent, prop, index) {
|
|
1846
|
+
if (node) {
|
|
1847
|
+
if (this.enter) {
|
|
1848
|
+
const _should_skip = this.should_skip;
|
|
1849
|
+
const _should_remove = this.should_remove;
|
|
1850
|
+
const _replacement = this.replacement;
|
|
1851
|
+
this.should_skip = false;
|
|
1852
|
+
this.should_remove = false;
|
|
1853
|
+
this.replacement = null;
|
|
1854
|
+
this.enter.call(this.context, node, parent, prop, index);
|
|
1855
|
+
if (this.replacement) {
|
|
1856
|
+
node = this.replacement;
|
|
1857
|
+
this.replace(parent, prop, index, node);
|
|
1858
|
+
}
|
|
1859
|
+
if (this.should_remove) this.remove(parent, prop, index);
|
|
1860
|
+
const skipped = this.should_skip;
|
|
1861
|
+
const removed = this.should_remove;
|
|
1862
|
+
this.should_skip = _should_skip;
|
|
1863
|
+
this.should_remove = _should_remove;
|
|
1864
|
+
this.replacement = _replacement;
|
|
1865
|
+
if (skipped) return node;
|
|
1866
|
+
if (removed) return null;
|
|
1867
|
+
}
|
|
1868
|
+
/** @type {keyof Node} */
|
|
1869
|
+
let key;
|
|
1870
|
+
for (key in node) {
|
|
1871
|
+
/** @type {unknown} */
|
|
1872
|
+
const value = node[key];
|
|
1873
|
+
if (value && typeof value === "object") {
|
|
1874
|
+
if (Array.isArray(value)) {
|
|
1875
|
+
const nodes = value;
|
|
1876
|
+
for (let i = 0; i < nodes.length; i += 1) {
|
|
1877
|
+
const item = nodes[i];
|
|
1878
|
+
if (isNode(item)) {
|
|
1879
|
+
if (!this.visit(item, node, key, i)) i--;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
} else if (isNode(value)) this.visit(value, node, key, null);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (this.leave) {
|
|
1886
|
+
const _replacement = this.replacement;
|
|
1887
|
+
const _should_remove = this.should_remove;
|
|
1888
|
+
this.replacement = null;
|
|
1889
|
+
this.should_remove = false;
|
|
1890
|
+
this.leave.call(this.context, node, parent, prop, index);
|
|
1891
|
+
if (this.replacement) {
|
|
1892
|
+
node = this.replacement;
|
|
1893
|
+
this.replace(parent, prop, index, node);
|
|
1894
|
+
}
|
|
1895
|
+
if (this.should_remove) this.remove(parent, prop, index);
|
|
1896
|
+
const removed = this.should_remove;
|
|
1897
|
+
this.replacement = _replacement;
|
|
1898
|
+
this.should_remove = _should_remove;
|
|
1899
|
+
if (removed) return null;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return node;
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
/**
|
|
1906
|
+
* Ducktype a node.
|
|
1907
|
+
*
|
|
1908
|
+
* @param {unknown} value
|
|
1909
|
+
* @returns {value is Node}
|
|
1910
|
+
*/
|
|
1911
|
+
function isNode(value) {
|
|
1912
|
+
return value !== null && typeof value === "object" && "type" in value && typeof value.type === "string";
|
|
1913
|
+
}
|
|
1914
|
+
//#endregion
|
|
1915
|
+
//#region ../../node_modules/.pnpm/estree-walker@3.0.3/node_modules/estree-walker/src/index.js
|
|
1916
|
+
/**
|
|
1917
|
+
* @typedef {import('estree').Node} Node
|
|
1918
|
+
* @typedef {import('./sync.js').SyncHandler} SyncHandler
|
|
1919
|
+
* @typedef {import('./async.js').AsyncHandler} AsyncHandler
|
|
1920
|
+
*/
|
|
1921
|
+
/**
|
|
1922
|
+
* @param {Node} ast
|
|
1923
|
+
* @param {{
|
|
1924
|
+
* enter?: SyncHandler
|
|
1925
|
+
* leave?: SyncHandler
|
|
1926
|
+
* }} walker
|
|
1927
|
+
* @returns {Node | null}
|
|
1928
|
+
*/
|
|
1929
|
+
function walk(ast, { enter, leave }) {
|
|
1930
|
+
return new SyncWalker(enter, leave).visit(ast, null);
|
|
1931
|
+
}
|
|
1932
|
+
//#endregion
|
|
1933
|
+
//#region ../../node_modules/.pnpm/unist-util-mdx-define@1.1.2/node_modules/unist-util-mdx-define/dist/unist-util-mdx-define.js
|
|
1934
|
+
/**
|
|
1935
|
+
* @param program
|
|
1936
|
+
* The ESTree program to scan.
|
|
1937
|
+
* @param file
|
|
1938
|
+
* The {@link VFile} to emit warnings to.
|
|
1939
|
+
* @param variables
|
|
1940
|
+
* The variables that should be injected.
|
|
1941
|
+
* @param options
|
|
1942
|
+
* {@link define}.options
|
|
1943
|
+
* @returns
|
|
1944
|
+
* The position in the body where the export may be injected.
|
|
1945
|
+
*/
|
|
1946
|
+
function scan(program, file, variables, options) {
|
|
1947
|
+
const visitors = createVisitors();
|
|
1948
|
+
const [scope] = visitors.scopes;
|
|
1949
|
+
const identifiers = /* @__PURE__ */ new Map();
|
|
1950
|
+
let injectIndex = 0;
|
|
1951
|
+
walk(program, {
|
|
1952
|
+
enter(node, parent) {
|
|
1953
|
+
visitors.enter(node);
|
|
1954
|
+
switch (node.type) {
|
|
1955
|
+
case "Identifier":
|
|
1956
|
+
if (scope.defined.includes(node.name) && !identifiers.has(node.name)) identifiers.set(node.name, node);
|
|
1957
|
+
break;
|
|
1958
|
+
case "ArrowFunctionExpression":
|
|
1959
|
+
case "ClassDeclaration":
|
|
1960
|
+
case "ClassExpression":
|
|
1961
|
+
case "FunctionExpression":
|
|
1962
|
+
case "FunctionDeclaration":
|
|
1963
|
+
this.skip();
|
|
1964
|
+
break;
|
|
1965
|
+
case "ExpressionStatement":
|
|
1966
|
+
if (parent === program && node.expression.type === "Literal" && typeof node.expression.value === "string") injectIndex = program.body.indexOf(node) + 1;
|
|
1967
|
+
break;
|
|
1968
|
+
default:
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
leave: visitors.exit
|
|
1972
|
+
});
|
|
1973
|
+
for (const name of scope.defined) if (variables.has(name)) {
|
|
1974
|
+
if (options?.conflict !== "skip") {
|
|
1975
|
+
const identifier = identifiers.get(name);
|
|
1976
|
+
const message = file.message(`Variable name conflict: ${name}`, {
|
|
1977
|
+
place: identifier?.loc,
|
|
1978
|
+
ruleId: "conflict",
|
|
1979
|
+
source: "unist-util-mdx-define"
|
|
1980
|
+
});
|
|
1981
|
+
message.url = "https://github.com/remcohaszing/unist-util-mdx-define";
|
|
1982
|
+
if (options?.conflict !== "warn") {
|
|
1983
|
+
message.fatal = true;
|
|
1984
|
+
throw message;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
variables.delete(name);
|
|
1988
|
+
}
|
|
1989
|
+
return injectIndex;
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Generate an export named declaration.
|
|
1993
|
+
*
|
|
1994
|
+
* @param variables
|
|
1995
|
+
* The variables for which to generate an declaration.
|
|
1996
|
+
* @param options
|
|
1997
|
+
* {@link define} options
|
|
1998
|
+
* @param returnStatement
|
|
1999
|
+
* The return statement of the program to inject into.
|
|
2000
|
+
* @returns
|
|
2001
|
+
* The export named declaration.
|
|
2002
|
+
*/
|
|
2003
|
+
function generate(variables, options, returnStatement) {
|
|
2004
|
+
if (options?.export === "namespace") {
|
|
2005
|
+
const statements = [];
|
|
2006
|
+
for (const [name$1, right] of variables) {
|
|
2007
|
+
const isIdentifier = name(name$1);
|
|
2008
|
+
statements.push({
|
|
2009
|
+
type: "ExpressionStatement",
|
|
2010
|
+
expression: {
|
|
2011
|
+
type: "AssignmentExpression",
|
|
2012
|
+
left: {
|
|
2013
|
+
type: "MemberExpression",
|
|
2014
|
+
computed: !isIdentifier,
|
|
2015
|
+
object: {
|
|
2016
|
+
type: "Identifier",
|
|
2017
|
+
name: "MDXContent"
|
|
2018
|
+
},
|
|
2019
|
+
optional: false,
|
|
2020
|
+
property: isIdentifier ? {
|
|
2021
|
+
type: "Identifier",
|
|
2022
|
+
name: name$1
|
|
2023
|
+
} : {
|
|
2024
|
+
type: "Literal",
|
|
2025
|
+
value: name$1
|
|
2026
|
+
}
|
|
2027
|
+
},
|
|
2028
|
+
operator: "=",
|
|
2029
|
+
right
|
|
2030
|
+
}
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
return statements;
|
|
2034
|
+
}
|
|
2035
|
+
const declarations = [];
|
|
2036
|
+
for (const [name, init] of variables) declarations.push({
|
|
2037
|
+
type: "VariableDeclaration",
|
|
2038
|
+
kind: "const",
|
|
2039
|
+
declarations: [{
|
|
2040
|
+
type: "VariableDeclarator",
|
|
2041
|
+
id: {
|
|
2042
|
+
type: "Identifier",
|
|
2043
|
+
name
|
|
2044
|
+
},
|
|
2045
|
+
init
|
|
2046
|
+
}]
|
|
2047
|
+
});
|
|
2048
|
+
if (options?.export === false) return declarations;
|
|
2049
|
+
if (!returnStatement) return declarations.map((declaration) => ({
|
|
2050
|
+
type: "ExportNamedDeclaration",
|
|
2051
|
+
declaration,
|
|
2052
|
+
specifiers: []
|
|
2053
|
+
}));
|
|
2054
|
+
if (returnStatement.argument?.type === "ObjectExpression") returnStatement.argument.properties.splice(-1, 0, ...Array.from(variables.keys(), (name) => ({
|
|
2055
|
+
type: "Property",
|
|
2056
|
+
computed: false,
|
|
2057
|
+
kind: "init",
|
|
2058
|
+
method: false,
|
|
2059
|
+
shorthand: true,
|
|
2060
|
+
key: {
|
|
2061
|
+
type: "Identifier",
|
|
2062
|
+
name
|
|
2063
|
+
},
|
|
2064
|
+
value: {
|
|
2065
|
+
type: "Identifier",
|
|
2066
|
+
name
|
|
2067
|
+
}
|
|
2068
|
+
})));
|
|
2069
|
+
return declarations;
|
|
2070
|
+
}
|
|
2071
|
+
/**
|
|
2072
|
+
* Define variables in an MDX related AST.
|
|
2073
|
+
*
|
|
2074
|
+
* @param ast
|
|
2075
|
+
* The AST in which to define an export
|
|
2076
|
+
* @param file
|
|
2077
|
+
* The {@link VFile} to emit warnings to.
|
|
2078
|
+
* @param variables
|
|
2079
|
+
* A mapping of variables to define. They keys are the names. The values are the ESTree expression
|
|
2080
|
+
* to represent them.
|
|
2081
|
+
* @param options
|
|
2082
|
+
* Additional options to configure behaviour.
|
|
2083
|
+
*/
|
|
2084
|
+
function define(ast, file, variables, options) {
|
|
2085
|
+
const map = new Map(Object.entries(variables));
|
|
2086
|
+
if (options?.export !== "namespace") for (const name$2 of map.keys()) {
|
|
2087
|
+
if (name$2 === "_createMdxContent" || name$2 === "_Fragment" || name$2 === "_jsx" || name$2 === "_jsxs" || name$2 === "_missingMdxReference" || name$2 === "MDXContent") {
|
|
2088
|
+
const message = file.message(`MDX internal name conflict: ${name$2}`, {
|
|
2089
|
+
ruleId: "internal",
|
|
2090
|
+
source: "unist-util-mdx-define"
|
|
2091
|
+
});
|
|
2092
|
+
message.url = "https://github.com/remcohaszing/unist-util-mdx-define";
|
|
2093
|
+
message.fatal = true;
|
|
2094
|
+
throw message;
|
|
2095
|
+
}
|
|
2096
|
+
if (!name(name$2)) {
|
|
2097
|
+
const message = file.message(`Invalid identifier name: ${name$2}`, {
|
|
2098
|
+
ruleId: "invalid-identifier",
|
|
2099
|
+
source: "unist-util-mdx-define"
|
|
2100
|
+
});
|
|
2101
|
+
message.url = "https://github.com/remcohaszing/unist-util-mdx-define";
|
|
2102
|
+
message.fatal = true;
|
|
2103
|
+
throw message;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (ast.type === "root") {
|
|
2107
|
+
for (const child of ast.children) {
|
|
2108
|
+
if (child.type !== "mdxjsEsm") continue;
|
|
2109
|
+
const program = child.data?.estree;
|
|
2110
|
+
/* c8 ignore start */
|
|
2111
|
+
if (!program) continue;
|
|
2112
|
+
/* c8 ignore stop */
|
|
2113
|
+
scan(program, file, map, options);
|
|
2114
|
+
}
|
|
2115
|
+
if (map.size) ast.children.unshift({
|
|
2116
|
+
type: "mdxjsEsm",
|
|
2117
|
+
value: "",
|
|
2118
|
+
data: { estree: {
|
|
2119
|
+
type: "Program",
|
|
2120
|
+
sourceType: "module",
|
|
2121
|
+
body: generate(map, options)
|
|
2122
|
+
} }
|
|
2123
|
+
});
|
|
2124
|
+
} else {
|
|
2125
|
+
const returnStatement = ast.body.find((node) => node.type === "ReturnStatement");
|
|
2126
|
+
const injectIndex = scan(ast, file, map, options);
|
|
2127
|
+
if (map.size) ast.body.splice(injectIndex, 0, ...generate(map, options, returnStatement));
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
//#endregion
|
|
2131
|
+
//#region src/markdown/remark-mdx-toc.ts
|
|
2132
|
+
function remarkMdxToc(options = {}) {
|
|
2133
|
+
const { name = "toc", levels = [2, 3] } = options;
|
|
2134
|
+
const [minLevel, maxLevel] = levels;
|
|
2135
|
+
return function(tree, file) {
|
|
2136
|
+
const items = [];
|
|
2137
|
+
let headingIndex = 0;
|
|
2138
|
+
visit(tree, "heading", (node) => {
|
|
2139
|
+
if (node.depth < minLevel || node.depth > maxLevel) return;
|
|
2140
|
+
const text = getHeadingText(node);
|
|
2141
|
+
const slug = slugify(text);
|
|
2142
|
+
const id = slug === "" ? `heading-${String(headingIndex)}` : slug;
|
|
2143
|
+
headingIndex++;
|
|
2144
|
+
items.push({
|
|
2145
|
+
id,
|
|
2146
|
+
text,
|
|
2147
|
+
level: node.depth
|
|
2148
|
+
});
|
|
2149
|
+
const hProperties = ensureHProperties(node);
|
|
2150
|
+
hProperties.id = id;
|
|
2151
|
+
});
|
|
2152
|
+
define(tree, file, { [name]: valueToEstree(items) });
|
|
2153
|
+
};
|
|
1047
2154
|
}
|
|
1048
2155
|
function getHeadingText(node) {
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
} else if (Array.isArray(typedChild.children)) {
|
|
1058
|
-
typedChild.children.forEach(extractText);
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
node.children.forEach(extractText);
|
|
1062
|
-
return textParts.join("");
|
|
2156
|
+
const parts = [];
|
|
2157
|
+
function extract(child) {
|
|
2158
|
+
if (!isRecord(child)) return;
|
|
2159
|
+
if (child.type === "text" || child.type === "inlineCode") parts.push(typeof child.value === "string" ? child.value : "");
|
|
2160
|
+
else if (Array.isArray(child.children)) for (const nested of child.children) extract(nested);
|
|
2161
|
+
}
|
|
2162
|
+
for (const child of node.children) extract(child);
|
|
2163
|
+
return parts.join("");
|
|
1063
2164
|
}
|
|
1064
2165
|
function slugify(text) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const result = [];
|
|
1069
|
-
const stack = [];
|
|
1070
|
-
for (const heading of headings) {
|
|
1071
|
-
const item = {
|
|
1072
|
-
id: heading.id,
|
|
1073
|
-
text: heading.text,
|
|
1074
|
-
level: heading.level
|
|
1075
|
-
};
|
|
1076
|
-
while (stack.length > 0 && stack[stack.length - 1].level >= heading.level) {
|
|
1077
|
-
stack.pop();
|
|
1078
|
-
}
|
|
1079
|
-
if (stack.length === 0) {
|
|
1080
|
-
result.push(item);
|
|
1081
|
-
} else {
|
|
1082
|
-
const parent = stack[stack.length - 1].item;
|
|
1083
|
-
if (!parent.children) {
|
|
1084
|
-
parent.children = [];
|
|
1085
|
-
}
|
|
1086
|
-
parent.children.push(item);
|
|
1087
|
-
}
|
|
1088
|
-
stack.push({ item, level: heading.level });
|
|
1089
|
-
}
|
|
1090
|
-
return result;
|
|
2166
|
+
let slug = text.toLowerCase().trim().replaceAll(/[^\s\w-]/g, "").replaceAll(/[\s_]/g, "-");
|
|
2167
|
+
while (slug.includes("--")) slug = slug.replaceAll("--", "-");
|
|
2168
|
+
return slug.replaceAll(/^-|-$/g, "");
|
|
1091
2169
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const normalizedBase = basePath === "/" ? "" : basePath.replace(/\/$/, "");
|
|
1098
|
-
return (tree) => {
|
|
1099
|
-
if (!normalizedBase) {
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
visit3(tree, "element", (node) => {
|
|
1103
|
-
if (node.tagName === "a") {
|
|
1104
|
-
const href = node.properties?.href;
|
|
1105
|
-
if (typeof href === "string") {
|
|
1106
|
-
if (href.startsWith("/") && !href.startsWith("//") && !href.startsWith(normalizedBase)) {
|
|
1107
|
-
node.properties = node.properties || {};
|
|
1108
|
-
node.properties.href = normalizedBase + href;
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
};
|
|
2170
|
+
function ensureHProperties(node) {
|
|
2171
|
+
const data = node.data ?? {};
|
|
2172
|
+
node.data = data;
|
|
2173
|
+
if (!isRecord(data.hProperties)) data.hProperties = {};
|
|
2174
|
+
return data.hProperties;
|
|
1114
2175
|
}
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
async function transformMarkdown(content, config, options = {}) {
|
|
1118
|
-
const { data: frontmatter, content: markdownContent } = matter2(content);
|
|
1119
|
-
const { basePath = "/", highlighter: providedHighlighter } = options;
|
|
1120
|
-
const tocExtraction = { toc: [] };
|
|
1121
|
-
const highlighter = providedHighlighter ?? await createShikiHighlighter(config);
|
|
1122
|
-
const processor = unified().use(remarkParse).use(remarkFrontmatter2, ["yaml"]).use(remarkGfm2).use(remarkExtractToc, { tocExtraction, levels: config.toc?.level ?? [2, 3] }).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeShikiFromHighlighter, { highlighter, config }).use(rehypeLinks, { basePath }).use(rehypeStringify, { allowDangerousHtml: true });
|
|
1123
|
-
if (config.remarkPlugins) {
|
|
1124
|
-
for (const plugin of config.remarkPlugins) {
|
|
1125
|
-
processor.use(plugin);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
if (config.rehypePlugins) {
|
|
1129
|
-
for (const plugin of config.rehypePlugins) {
|
|
1130
|
-
processor.use(plugin);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1133
|
-
const result = await processor.process(markdownContent);
|
|
1134
|
-
return {
|
|
1135
|
-
html: String(result),
|
|
1136
|
-
frontmatter,
|
|
1137
|
-
toc: tocExtraction.toc
|
|
1138
|
-
};
|
|
2176
|
+
function isRecord(value) {
|
|
2177
|
+
return value != null && typeof value === "object";
|
|
1139
2178
|
}
|
|
1140
|
-
|
|
1141
|
-
|
|
2179
|
+
//#endregion
|
|
2180
|
+
//#region src/vite/recma-wrap-export.ts
|
|
2181
|
+
function findDefaultExport(body) {
|
|
2182
|
+
for (const [i, node] of body.entries()) if (node.type === "ExportDefaultDeclaration" && node.declaration?.type === "FunctionDeclaration" && node.declaration.id?.name !== void 0 && node.declaration.id.name !== "") return {
|
|
2183
|
+
index: i,
|
|
2184
|
+
name: node.declaration.id.name
|
|
2185
|
+
};
|
|
2186
|
+
return {
|
|
2187
|
+
index: -1,
|
|
2188
|
+
name: ""
|
|
2189
|
+
};
|
|
1142
2190
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
if (!filePath || !fileContent) {
|
|
1163
|
-
return null;
|
|
1164
|
-
}
|
|
1165
|
-
const result = await transformMarkdown(fileContent, config.markdown);
|
|
1166
|
-
const relativePath = path3.relative(contentDir, filePath);
|
|
1167
|
-
let lastUpdated;
|
|
1168
|
-
try {
|
|
1169
|
-
const stat = await fs3.stat(filePath);
|
|
1170
|
-
lastUpdated = stat.mtimeMs;
|
|
1171
|
-
} catch {
|
|
1172
|
-
}
|
|
1173
|
-
return {
|
|
1174
|
-
content: result.html,
|
|
1175
|
-
frontmatter: result.frontmatter,
|
|
1176
|
-
toc: result.toc,
|
|
1177
|
-
filePath,
|
|
1178
|
-
relativePath,
|
|
1179
|
-
lastUpdated
|
|
1180
|
-
};
|
|
2191
|
+
function createImport(imported, local, source) {
|
|
2192
|
+
return {
|
|
2193
|
+
type: "ImportDeclaration",
|
|
2194
|
+
specifiers: [{
|
|
2195
|
+
type: "ImportSpecifier",
|
|
2196
|
+
imported: {
|
|
2197
|
+
type: "Identifier",
|
|
2198
|
+
name: imported
|
|
2199
|
+
},
|
|
2200
|
+
local: {
|
|
2201
|
+
type: "Identifier",
|
|
2202
|
+
name: local
|
|
2203
|
+
}
|
|
2204
|
+
}],
|
|
2205
|
+
source: {
|
|
2206
|
+
type: "Literal",
|
|
2207
|
+
value: source
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
1181
2210
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
} catch {
|
|
1199
|
-
}
|
|
1200
|
-
docs.push({
|
|
1201
|
-
title: result.frontmatter.title || formatTitle2(entry.name.replace(/\.md$/, "")),
|
|
1202
|
-
description: result.frontmatter.description,
|
|
1203
|
-
frontmatter: result.frontmatter,
|
|
1204
|
-
content: result.html,
|
|
1205
|
-
toc: result.toc,
|
|
1206
|
-
filePath: fullPath,
|
|
1207
|
-
relativePath,
|
|
1208
|
-
lastUpdated
|
|
1209
|
-
});
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
await scanDir(contentDir);
|
|
1214
|
-
return docs;
|
|
1215
|
-
}
|
|
1216
|
-
function formatTitle2(name) {
|
|
1217
|
-
return name.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2211
|
+
function createShorthandProp(name) {
|
|
2212
|
+
return {
|
|
2213
|
+
type: "Property",
|
|
2214
|
+
key: {
|
|
2215
|
+
type: "Identifier",
|
|
2216
|
+
name
|
|
2217
|
+
},
|
|
2218
|
+
value: {
|
|
2219
|
+
type: "Identifier",
|
|
2220
|
+
name
|
|
2221
|
+
},
|
|
2222
|
+
kind: "init",
|
|
2223
|
+
shorthand: true,
|
|
2224
|
+
computed: false,
|
|
2225
|
+
method: false
|
|
2226
|
+
};
|
|
1218
2227
|
}
|
|
1219
|
-
function
|
|
1220
|
-
|
|
2228
|
+
function createWrapperFunction(fnName) {
|
|
2229
|
+
return {
|
|
2230
|
+
type: "FunctionDeclaration",
|
|
2231
|
+
id: {
|
|
2232
|
+
type: "Identifier",
|
|
2233
|
+
name: "_ArdoWrapped"
|
|
2234
|
+
},
|
|
2235
|
+
params: [{
|
|
2236
|
+
type: "Identifier",
|
|
2237
|
+
name: "props"
|
|
2238
|
+
}],
|
|
2239
|
+
body: {
|
|
2240
|
+
type: "BlockStatement",
|
|
2241
|
+
body: [{
|
|
2242
|
+
type: "ReturnStatement",
|
|
2243
|
+
argument: {
|
|
2244
|
+
type: "CallExpression",
|
|
2245
|
+
callee: {
|
|
2246
|
+
type: "Identifier",
|
|
2247
|
+
name: "_ardoJsx"
|
|
2248
|
+
},
|
|
2249
|
+
arguments: [{
|
|
2250
|
+
type: "Identifier",
|
|
2251
|
+
name: "_ArdoPageDP"
|
|
2252
|
+
}, {
|
|
2253
|
+
type: "ObjectExpression",
|
|
2254
|
+
properties: [
|
|
2255
|
+
createShorthandProp("frontmatter"),
|
|
2256
|
+
createShorthandProp("toc"),
|
|
2257
|
+
{
|
|
2258
|
+
type: "Property",
|
|
2259
|
+
key: {
|
|
2260
|
+
type: "Identifier",
|
|
2261
|
+
name: "children"
|
|
2262
|
+
},
|
|
2263
|
+
value: {
|
|
2264
|
+
type: "CallExpression",
|
|
2265
|
+
callee: {
|
|
2266
|
+
type: "Identifier",
|
|
2267
|
+
name: "_ardoJsx"
|
|
2268
|
+
},
|
|
2269
|
+
arguments: [{
|
|
2270
|
+
type: "Identifier",
|
|
2271
|
+
name: fnName
|
|
2272
|
+
}, {
|
|
2273
|
+
type: "Identifier",
|
|
2274
|
+
name: "props"
|
|
2275
|
+
}],
|
|
2276
|
+
optional: false
|
|
2277
|
+
},
|
|
2278
|
+
kind: "init",
|
|
2279
|
+
shorthand: false,
|
|
2280
|
+
computed: false,
|
|
2281
|
+
method: false
|
|
2282
|
+
}
|
|
2283
|
+
]
|
|
2284
|
+
}],
|
|
2285
|
+
optional: false
|
|
2286
|
+
}
|
|
2287
|
+
}]
|
|
2288
|
+
},
|
|
2289
|
+
generator: false,
|
|
2290
|
+
async: false
|
|
2291
|
+
};
|
|
1221
2292
|
}
|
|
1222
|
-
function
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
2293
|
+
function recmaWrapExport() {
|
|
2294
|
+
return (tree) => {
|
|
2295
|
+
const { index, name } = findDefaultExport(tree.body);
|
|
2296
|
+
if (index === -1) return;
|
|
2297
|
+
const decl = tree.body[index].declaration;
|
|
2298
|
+
if (decl == null) return;
|
|
2299
|
+
tree.body[index] = decl;
|
|
2300
|
+
tree.body.unshift(createImport("ArdoPageDataProvider", "_ArdoPageDP", "ardo/runtime"), createImport("jsx", "_ardoJsx", "react/jsx-runtime"));
|
|
2301
|
+
tree.body.push(createWrapperFunction(name), {
|
|
2302
|
+
type: "ExportDefaultDeclaration",
|
|
2303
|
+
declaration: {
|
|
2304
|
+
type: "Identifier",
|
|
2305
|
+
name: "_ArdoWrapped"
|
|
2306
|
+
}
|
|
2307
|
+
});
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
//#endregion
|
|
2311
|
+
//#region src/vite/mdx-plugin.ts
|
|
2312
|
+
function createMdxPlugin(markdownConfig) {
|
|
2313
|
+
const themeConfig = markdownConfig?.theme ?? defaultMarkdownConfig.theme;
|
|
2314
|
+
const lineNumbers = markdownConfig?.lineNumbers ?? false;
|
|
2315
|
+
const shikiOptions = isShikiThemeObject(themeConfig) ? {
|
|
2316
|
+
themes: {
|
|
2317
|
+
light: themeConfig.light,
|
|
2318
|
+
dark: themeConfig.dark
|
|
2319
|
+
},
|
|
2320
|
+
defaultColor: false,
|
|
2321
|
+
transformers: [ardoLineTransformer({ globalLineNumbers: lineNumbers })]
|
|
2322
|
+
} : {
|
|
2323
|
+
theme: themeConfig,
|
|
2324
|
+
transformers: [ardoLineTransformer({ globalLineNumbers: lineNumbers })]
|
|
2325
|
+
};
|
|
2326
|
+
return mdx({
|
|
2327
|
+
include: /\.(md|mdx)$/,
|
|
2328
|
+
remarkPlugins: [
|
|
2329
|
+
remarkFrontmatter,
|
|
2330
|
+
[remarkMdxFrontmatter, { name: "frontmatter" }],
|
|
2331
|
+
remarkGfm,
|
|
2332
|
+
remarkCodeMeta,
|
|
2333
|
+
[remarkMdxToc, { levels: markdownConfig?.toc?.level ?? [2, 3] }]
|
|
2334
|
+
],
|
|
2335
|
+
rehypePlugins: [[rehypeShiki, shikiOptions]],
|
|
2336
|
+
recmaPlugins: [recmaWrapExport],
|
|
2337
|
+
providerImportSource: "ardo/mdx-provider"
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
function getReactRouterPlugins() {
|
|
2341
|
+
const routerPlugin = reactRouter();
|
|
2342
|
+
return Array.isArray(routerPlugin) ? routerPlugin : [routerPlugin];
|
|
2343
|
+
}
|
|
2344
|
+
function isShikiThemeObject(themeConfig) {
|
|
2345
|
+
return typeof themeConfig === "object" && themeConfig != null && "light" in themeConfig && "dark" in themeConfig;
|
|
2346
|
+
}
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/vite/project-meta.ts
|
|
2349
|
+
/**
|
|
2350
|
+
* Reads project metadata from package.json.
|
|
2351
|
+
*/
|
|
2352
|
+
function readProjectMeta(root) {
|
|
2353
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
2354
|
+
try {
|
|
2355
|
+
const rawPackageJson = fsSync.readFileSync(packageJsonPath, "utf8");
|
|
2356
|
+
const parsedPackageJson = JSON.parse(rawPackageJson);
|
|
2357
|
+
if (!isPackageJsonShape(parsedPackageJson)) return {};
|
|
2358
|
+
const repository = extractRepository(parsedPackageJson.repository);
|
|
2359
|
+
const author = extractAuthor(parsedPackageJson.author);
|
|
2360
|
+
return {
|
|
2361
|
+
name: parsedPackageJson.name,
|
|
2362
|
+
homepage: parsedPackageJson.homepage,
|
|
2363
|
+
repository,
|
|
2364
|
+
version: parsedPackageJson.version,
|
|
2365
|
+
author,
|
|
2366
|
+
license: parsedPackageJson.license
|
|
2367
|
+
};
|
|
2368
|
+
} catch {
|
|
2369
|
+
return {};
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
function extractRepository(repository) {
|
|
2373
|
+
if (typeof repository === "string") return normalizeRepository(repository);
|
|
2374
|
+
if (repository == null || typeof repository.url !== "string") return;
|
|
2375
|
+
return normalizeRepository(repository.url);
|
|
2376
|
+
}
|
|
2377
|
+
function extractAuthor(author) {
|
|
2378
|
+
if (typeof author === "string") return author;
|
|
2379
|
+
if (author == null || typeof author.name !== "string") return;
|
|
2380
|
+
return author.name;
|
|
2381
|
+
}
|
|
2382
|
+
function normalizeRepository(repository) {
|
|
2383
|
+
return repository.replace(/^git\+/, "").replace(/^git:\/\//, "https://").replace(/\.git$/, "");
|
|
2384
|
+
}
|
|
2385
|
+
function isPackageJsonShape(value) {
|
|
2386
|
+
return typeof value === "object" && value != null;
|
|
2387
|
+
}
|
|
2388
|
+
//#endregion
|
|
2389
|
+
//#region src/vite/routes-core.ts
|
|
2390
|
+
function scanRoutesSync(params) {
|
|
2391
|
+
const { dir, rootDir } = params;
|
|
2392
|
+
const routes = [];
|
|
2393
|
+
const entries = readDirectoryEntries(dir);
|
|
2394
|
+
for (const entry of entries) {
|
|
2395
|
+
const fullPath = path.join(dir, entry.name);
|
|
2396
|
+
if (entry.isDirectory()) {
|
|
2397
|
+
routes.push(...scanRoutesSync({
|
|
2398
|
+
dir: fullPath,
|
|
2399
|
+
rootDir
|
|
2400
|
+
}));
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
const route = createRouteInfo({
|
|
2404
|
+
entryName: entry.name,
|
|
2405
|
+
fullPath,
|
|
2406
|
+
rootDir
|
|
2407
|
+
});
|
|
2408
|
+
if (route != null) routes.push(route);
|
|
2409
|
+
}
|
|
2410
|
+
return routes;
|
|
1227
2411
|
}
|
|
2412
|
+
function readDirectoryEntries(dir) {
|
|
2413
|
+
try {
|
|
2414
|
+
return fsSync.readdirSync(dir, { withFileTypes: true });
|
|
2415
|
+
} catch {
|
|
2416
|
+
return [];
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
function createRouteInfo(params) {
|
|
2420
|
+
const { entryName, fullPath, rootDir } = params;
|
|
2421
|
+
if (!isRouteFile(entryName) || isIgnoredRouteFile(entryName)) return null;
|
|
2422
|
+
const extension = getRouteExtension(entryName);
|
|
2423
|
+
if (extension == null) return null;
|
|
2424
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
2425
|
+
const baseName = entryName.replace(extension, "");
|
|
2426
|
+
const urlPath = toRoutePath({
|
|
2427
|
+
baseName,
|
|
2428
|
+
extension,
|
|
2429
|
+
relativePath
|
|
2430
|
+
});
|
|
2431
|
+
return {
|
|
2432
|
+
file: `routes/${relativePath.replaceAll("\\", "/")}`,
|
|
2433
|
+
isIndex: isIndexRoute(baseName),
|
|
2434
|
+
path: urlPath
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
function isRouteFile(entryName) {
|
|
2438
|
+
return entryName.endsWith(".md") || entryName.endsWith(".mdx") || entryName.endsWith(".tsx");
|
|
2439
|
+
}
|
|
2440
|
+
function isIgnoredRouteFile(entryName) {
|
|
2441
|
+
return entryName === "root.tsx" || entryName.startsWith("_");
|
|
2442
|
+
}
|
|
2443
|
+
function getRouteExtension(entryName) {
|
|
2444
|
+
if (entryName.endsWith(".mdx")) return ".mdx";
|
|
2445
|
+
if (entryName.endsWith(".md")) return ".md";
|
|
2446
|
+
if (entryName.endsWith(".tsx")) return ".tsx";
|
|
2447
|
+
return null;
|
|
2448
|
+
}
|
|
2449
|
+
function toRoutePath(params) {
|
|
2450
|
+
const { baseName, extension, relativePath } = params;
|
|
2451
|
+
if (isIndexRoute(baseName)) {
|
|
2452
|
+
const parentDir = path.dirname(relativePath);
|
|
2453
|
+
return applyDynamicSegments(`/${parentDir === "." ? "" : parentDir.replaceAll("\\", "/")}`);
|
|
2454
|
+
}
|
|
2455
|
+
return applyDynamicSegments(`/${relativePath.replace(extension, "").replaceAll("\\", "/")}`);
|
|
2456
|
+
}
|
|
2457
|
+
function isIndexRoute(baseName) {
|
|
2458
|
+
return baseName === "index" || baseName === "home";
|
|
2459
|
+
}
|
|
2460
|
+
function applyDynamicSegments(urlPath) {
|
|
2461
|
+
return urlPath.replaceAll(/\$(\w+)/gu, ":$1");
|
|
2462
|
+
}
|
|
2463
|
+
function generateRoutesFile(routes) {
|
|
2464
|
+
return `// AUTO-GENERATED by Ardo - Do not edit manually
|
|
1228
2465
|
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
const configSidebar = config.themeConfig.sidebar;
|
|
1236
|
-
if (configSidebar) {
|
|
1237
|
-
if (Array.isArray(configSidebar) && configSidebar.length > 0) {
|
|
1238
|
-
return configSidebar;
|
|
1239
|
-
}
|
|
1240
|
-
if (!Array.isArray(configSidebar)) {
|
|
1241
|
-
return [];
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
return await scanDirectoryForSidebar(contentDir, contentDir, basePath);
|
|
1245
|
-
}
|
|
1246
|
-
async function scanDirectoryForSidebar(dir, rootDir, _basePath) {
|
|
1247
|
-
let entries;
|
|
1248
|
-
try {
|
|
1249
|
-
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
1250
|
-
} catch {
|
|
1251
|
-
return [];
|
|
1252
|
-
}
|
|
1253
|
-
const items = [];
|
|
1254
|
-
for (const entry of entries) {
|
|
1255
|
-
const fullPath = path4.join(dir, entry.name);
|
|
1256
|
-
const relativePath = path4.relative(rootDir, fullPath);
|
|
1257
|
-
if (entry.name.startsWith(".") || entry.name.startsWith("_")) {
|
|
1258
|
-
continue;
|
|
1259
|
-
}
|
|
1260
|
-
if (entry.isDirectory()) {
|
|
1261
|
-
const children = await scanDirectoryForSidebar(fullPath, rootDir, _basePath);
|
|
1262
|
-
if (children.length > 0) {
|
|
1263
|
-
const indexPath = path4.join(fullPath, "index.md");
|
|
1264
|
-
let link;
|
|
1265
|
-
let title = formatTitle3(entry.name);
|
|
1266
|
-
let order;
|
|
1267
|
-
try {
|
|
1268
|
-
const indexContent = await fs4.readFile(indexPath, "utf-8");
|
|
1269
|
-
const { data: frontmatter } = matter3(indexContent);
|
|
1270
|
-
if (frontmatter.title) {
|
|
1271
|
-
title = frontmatter.title;
|
|
1272
|
-
}
|
|
1273
|
-
if (typeof frontmatter.order === "number") {
|
|
1274
|
-
order = frontmatter.order;
|
|
1275
|
-
}
|
|
1276
|
-
link = normalizePath(relativePath);
|
|
1277
|
-
} catch {
|
|
1278
|
-
}
|
|
1279
|
-
items.push({
|
|
1280
|
-
text: title,
|
|
1281
|
-
link,
|
|
1282
|
-
collapsed: false,
|
|
1283
|
-
items: children,
|
|
1284
|
-
order
|
|
1285
|
-
});
|
|
1286
|
-
}
|
|
1287
|
-
} else if (entry.name.endsWith(".md") && entry.name !== "index.md") {
|
|
1288
|
-
const fileContent = await fs4.readFile(fullPath, "utf-8");
|
|
1289
|
-
const { data: frontmatter } = matter3(fileContent);
|
|
1290
|
-
if (frontmatter.sidebar === false) {
|
|
1291
|
-
continue;
|
|
1292
|
-
}
|
|
1293
|
-
const title = frontmatter.title || formatTitle3(entry.name.replace(/\.md$/, ""));
|
|
1294
|
-
const order = typeof frontmatter.order === "number" ? frontmatter.order : void 0;
|
|
1295
|
-
const link = normalizePath(relativePath.replace(/\.md$/, ""));
|
|
1296
|
-
items.push({
|
|
1297
|
-
text: title,
|
|
1298
|
-
link,
|
|
1299
|
-
order
|
|
1300
|
-
});
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
items.sort((a, b) => {
|
|
1304
|
-
if (a.order !== void 0 && b.order !== void 0) {
|
|
1305
|
-
return a.order - b.order;
|
|
1306
|
-
}
|
|
1307
|
-
if (a.order !== void 0) return -1;
|
|
1308
|
-
if (b.order !== void 0) return 1;
|
|
1309
|
-
return a.text.localeCompare(b.text);
|
|
1310
|
-
});
|
|
1311
|
-
return items.map(({ order: _order, ...item }) => item);
|
|
1312
|
-
}
|
|
1313
|
-
function formatTitle3(name) {
|
|
1314
|
-
return name.replace(/^\d+-/, "").replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2466
|
+
import { type RouteConfig, route, index } from "@react-router/dev/routes"
|
|
2467
|
+
|
|
2468
|
+
export default [
|
|
2469
|
+
${sortRoutes(routes).map((routeInfo) => renderRouteEntry(routeInfo)).join("\n")}
|
|
2470
|
+
] satisfies RouteConfig
|
|
2471
|
+
`;
|
|
1315
2472
|
}
|
|
1316
|
-
function
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
2473
|
+
function sortRoutes(routes) {
|
|
2474
|
+
return [...routes].sort((left, right) => {
|
|
2475
|
+
if (left.path === "/" && right.path !== "/") return -1;
|
|
2476
|
+
if (right.path === "/" && left.path !== "/") return 1;
|
|
2477
|
+
if (left.isIndex && !right.isIndex) return -1;
|
|
2478
|
+
if (right.isIndex && !left.isIndex) return 1;
|
|
2479
|
+
return left.path.localeCompare(right.path);
|
|
2480
|
+
});
|
|
2481
|
+
}
|
|
2482
|
+
function renderRouteEntry(routeInfo) {
|
|
2483
|
+
if (routeInfo.path === "/") return ` index("${routeInfo.file}"),`;
|
|
2484
|
+
return ` route("${routeInfo.path.slice(1)}", "${routeInfo.file}"),`;
|
|
2485
|
+
}
|
|
2486
|
+
function writeRoutesFileSync(params) {
|
|
2487
|
+
const { appDir, routesDir, routesFilePath } = params;
|
|
2488
|
+
const routes = scanRoutesSync({
|
|
2489
|
+
dir: routesDir,
|
|
2490
|
+
rootDir: routesDir
|
|
2491
|
+
});
|
|
2492
|
+
if (routes.length === 0) return;
|
|
2493
|
+
const content = generateRoutesFile(routes);
|
|
2494
|
+
if (!hasRoutesContentChangedSync(routesFilePath, content)) return;
|
|
2495
|
+
fsSync.mkdirSync(appDir, { recursive: true });
|
|
2496
|
+
fsSync.writeFileSync(routesFilePath, content, "utf8");
|
|
2497
|
+
console.log(`[ardo] Generated routes.ts with ${routes.length} routes`);
|
|
2498
|
+
}
|
|
2499
|
+
function hasRoutesContentChangedSync(routesFilePath, nextContent) {
|
|
2500
|
+
try {
|
|
2501
|
+
return fsSync.readFileSync(routesFilePath, "utf8") !== nextContent;
|
|
2502
|
+
} catch {
|
|
2503
|
+
return true;
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
async function writeRoutesFile(params) {
|
|
2507
|
+
const { appDir, routesDir, routesFilePath } = params;
|
|
2508
|
+
const routes = scanRoutesSync({
|
|
2509
|
+
dir: routesDir,
|
|
2510
|
+
rootDir: routesDir
|
|
2511
|
+
});
|
|
2512
|
+
if (routes.length === 0) return;
|
|
2513
|
+
const content = generateRoutesFile(routes);
|
|
2514
|
+
if (!await hasRoutesContentChanged(routesFilePath, content)) return;
|
|
2515
|
+
await fs.mkdir(appDir, { recursive: true });
|
|
2516
|
+
await fs.writeFile(routesFilePath, content, "utf8");
|
|
2517
|
+
}
|
|
2518
|
+
async function hasRoutesContentChanged(routesFilePath, nextContent) {
|
|
2519
|
+
try {
|
|
2520
|
+
return await fs.readFile(routesFilePath, "utf8") !== nextContent;
|
|
2521
|
+
} catch {
|
|
2522
|
+
return true;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
//#endregion
|
|
2526
|
+
//#region src/vite/routes-plugin.ts
|
|
2527
|
+
/**
|
|
2528
|
+
* Vite plugin that generates routes.ts for React Router Framework Mode.
|
|
2529
|
+
*/
|
|
2530
|
+
function ardoRoutesPlugin(options = {}) {
|
|
2531
|
+
let paths = createDefaultPaths(process.cwd(), options);
|
|
2532
|
+
return {
|
|
2533
|
+
name: "ardo:routes",
|
|
2534
|
+
enforce: "pre",
|
|
2535
|
+
config(userConfig) {
|
|
2536
|
+
paths = createDefaultPaths(userConfig.root ?? process.cwd(), options);
|
|
2537
|
+
try {
|
|
2538
|
+
writeRoutesFileSync(paths);
|
|
2539
|
+
} catch (error) {
|
|
2540
|
+
console.warn("[ardo] Could not generate routes.ts in config phase:", error);
|
|
2541
|
+
}
|
|
2542
|
+
},
|
|
2543
|
+
configResolved(resolvedConfig) {
|
|
2544
|
+
paths = createDefaultPaths(resolvedConfig.root, options);
|
|
2545
|
+
},
|
|
2546
|
+
async buildStart() {
|
|
2547
|
+
await writeRoutesFile(paths);
|
|
2548
|
+
},
|
|
2549
|
+
configureServer(server) {
|
|
2550
|
+
server.watcher.add(paths.routesDir);
|
|
2551
|
+
const handleChange = (changedPath) => {
|
|
2552
|
+
if (shouldHandleRouteChange(changedPath, paths.routesDir)) writeRoutesFile(paths);
|
|
2553
|
+
};
|
|
2554
|
+
server.watcher.on("add", handleChange);
|
|
2555
|
+
server.watcher.on("unlink", handleChange);
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
function createDefaultPaths(root, options) {
|
|
2560
|
+
const appDir = path.join(root, "app");
|
|
2561
|
+
return {
|
|
2562
|
+
appDir,
|
|
2563
|
+
routesDir: options.routesDir ?? path.join(appDir, "routes"),
|
|
2564
|
+
routesFilePath: path.join(appDir, "routes.ts")
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
function shouldHandleRouteChange(changedPath, routesDir) {
|
|
2568
|
+
if (!changedPath.startsWith(routesDir)) return false;
|
|
2569
|
+
return changedPath.endsWith(".md") || changedPath.endsWith(".mdx") || changedPath.endsWith(".tsx");
|
|
2570
|
+
}
|
|
2571
|
+
//#endregion
|
|
2572
|
+
//#region src/vite/search-index.ts
|
|
2573
|
+
async function generateSearchIndex(routesDir) {
|
|
2574
|
+
const context = {
|
|
2575
|
+
docs: [],
|
|
2576
|
+
routesDir
|
|
2577
|
+
};
|
|
2578
|
+
await scanDirectoryForSearch(routesDir, void 0, context);
|
|
2579
|
+
return context.docs;
|
|
2580
|
+
}
|
|
2581
|
+
async function scanDirectoryForSearch(dir, section, context) {
|
|
2582
|
+
let entries;
|
|
2583
|
+
try {
|
|
2584
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2587
|
+
console.warn("[ardo] Failed to scan for search index:", errorMessage);
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
for (const entry of entries) await processSearchEntry(entry, {
|
|
2591
|
+
dir,
|
|
2592
|
+
section,
|
|
2593
|
+
scanContext: context
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
async function processSearchEntry(entry, context) {
|
|
2597
|
+
const fullPath = path.join(context.dir, entry.name);
|
|
2598
|
+
if (entry.isDirectory()) {
|
|
2599
|
+
await scanDirectoryForSearch(fullPath, createNestedSection(context.section, entry.name), context.scanContext);
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
if (entry.name.endsWith(".mdx") || entry.name.endsWith(".md")) {
|
|
2603
|
+
const doc = await createSearchDocFromFile(entry.name, fullPath, {
|
|
2604
|
+
routesDir: context.scanContext.routesDir,
|
|
2605
|
+
section: context.section
|
|
2606
|
+
});
|
|
2607
|
+
if (doc != null) context.scanContext.docs.push(doc);
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
async function createSearchDocFromFile(fileName, filePath, context) {
|
|
2611
|
+
const parsed = matter(await fs.readFile(filePath, "utf8"));
|
|
2612
|
+
const extension = fileName.endsWith(".mdx") ? ".mdx" : ".md";
|
|
2613
|
+
const title = typeof parsed.data.title === "string" ? parsed.data.title : formatTitle$1(fileName.replace(extension, ""));
|
|
2614
|
+
const relativePath = path.relative(context.routesDir, filePath);
|
|
2615
|
+
const routePath = buildRoutePath(relativePath, fileName, extension);
|
|
2616
|
+
return {
|
|
2617
|
+
id: relativePath,
|
|
2618
|
+
title,
|
|
2619
|
+
content: sanitizeSearchContent(parsed.content),
|
|
2620
|
+
path: routePath,
|
|
2621
|
+
section: context.section
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function sanitizeSearchContent(content) {
|
|
2625
|
+
return collapseWhitespace(replacePunctuationWithSpaces(removeImportLines(removeCodeFences(content)))).slice(0, 2e3);
|
|
2626
|
+
}
|
|
2627
|
+
function removeCodeFences(content) {
|
|
2628
|
+
const lines = content.split("\n");
|
|
2629
|
+
const keptLines = [];
|
|
2630
|
+
let isInsideFence = false;
|
|
2631
|
+
for (const line of lines) {
|
|
2632
|
+
if (line.trimStart().startsWith("```")) {
|
|
2633
|
+
isInsideFence = !isInsideFence;
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
if (!isInsideFence) keptLines.push(line);
|
|
2637
|
+
}
|
|
2638
|
+
return keptLines.join("\n");
|
|
2639
|
+
}
|
|
2640
|
+
function removeImportLines(content) {
|
|
2641
|
+
const lines = content.split("\n");
|
|
2642
|
+
const keptLines = [];
|
|
2643
|
+
for (const line of lines) if (!line.trimStart().startsWith("import ")) keptLines.push(line);
|
|
2644
|
+
return keptLines.join("\n");
|
|
2645
|
+
}
|
|
2646
|
+
function replacePunctuationWithSpaces(content) {
|
|
2647
|
+
let normalized = content;
|
|
2648
|
+
for (const token of [
|
|
2649
|
+
"`",
|
|
2650
|
+
"#",
|
|
2651
|
+
"*",
|
|
2652
|
+
"_",
|
|
2653
|
+
"~",
|
|
2654
|
+
"[",
|
|
2655
|
+
"]",
|
|
2656
|
+
"(",
|
|
2657
|
+
")",
|
|
2658
|
+
"<",
|
|
2659
|
+
">",
|
|
2660
|
+
"|",
|
|
2661
|
+
"!"
|
|
2662
|
+
]) normalized = normalized.replaceAll(token, " ");
|
|
2663
|
+
return normalized;
|
|
2664
|
+
}
|
|
2665
|
+
function collapseWhitespace(content) {
|
|
2666
|
+
let result = "";
|
|
2667
|
+
let previousWasSpace = false;
|
|
2668
|
+
for (const character of content) {
|
|
2669
|
+
if (character === " " || character === "\n" || character === " " || character === "\r") {
|
|
2670
|
+
if (!previousWasSpace) result += " ";
|
|
2671
|
+
previousWasSpace = true;
|
|
2672
|
+
continue;
|
|
2673
|
+
}
|
|
2674
|
+
result += character;
|
|
2675
|
+
previousWasSpace = false;
|
|
2676
|
+
}
|
|
2677
|
+
return result.trim();
|
|
2678
|
+
}
|
|
2679
|
+
function buildRoutePath(relativePath, fileName, extension) {
|
|
2680
|
+
if (fileName === "index.mdx" || fileName === "index.md") {
|
|
2681
|
+
const directoryPath = path.dirname(relativePath).replaceAll("\\", "/");
|
|
2682
|
+
return directoryPath === "." ? "/" : `/${directoryPath}`;
|
|
2683
|
+
}
|
|
2684
|
+
return `/${relativePath.replace(extension, "").replaceAll("\\", "/")}`;
|
|
2685
|
+
}
|
|
2686
|
+
function createNestedSection(section, directoryName) {
|
|
2687
|
+
const currentTitle = formatTitle$1(directoryName);
|
|
2688
|
+
return section != null ? `${section} > ${currentTitle}` : currentTitle;
|
|
2689
|
+
}
|
|
2690
|
+
function formatTitle$1(name) {
|
|
2691
|
+
return name.replaceAll(/[_-]/g, " ").replaceAll(/\b\w/g, (char) => char.toUpperCase());
|
|
2692
|
+
}
|
|
2693
|
+
//#endregion
|
|
2694
|
+
//#region src/vite/sidebar-index.ts
|
|
2695
|
+
async function generateSidebar$1(routesDir) {
|
|
2696
|
+
try {
|
|
2697
|
+
return (await scanSidebarDirectory(routesDir, routesDir)).map((node) => stripOrderFromNode(node));
|
|
2698
|
+
} catch {
|
|
2699
|
+
return [];
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
async function scanSidebarDirectory(dir, rootDir) {
|
|
2703
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2704
|
+
const nodes = [];
|
|
2705
|
+
for (const entry of entries) {
|
|
2706
|
+
const fullPath = path.join(dir, entry.name);
|
|
2707
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
2708
|
+
if (entry.isDirectory()) {
|
|
2709
|
+
const directoryNode = await createDirectoryNode(entry, fullPath, rootDir);
|
|
2710
|
+
if (directoryNode != null) nodes.push(directoryNode);
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
const fileNode = await createMarkdownNode(entry, fullPath, relativePath);
|
|
2714
|
+
if (fileNode != null) nodes.push(fileNode);
|
|
2715
|
+
}
|
|
2716
|
+
sortNodes(nodes);
|
|
2717
|
+
return nodes;
|
|
2718
|
+
}
|
|
2719
|
+
async function createDirectoryNode(entry, fullPath, rootDir) {
|
|
2720
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
2721
|
+
const children = await scanSidebarDirectory(fullPath, rootDir);
|
|
2722
|
+
if (children.length === 0) return null;
|
|
2723
|
+
const link = await fileExists(path.join(fullPath, "index.mdx")) ? `/${relativePath.replaceAll("\\", "/")}` : void 0;
|
|
2724
|
+
return {
|
|
2725
|
+
text: formatTitle(entry.name),
|
|
2726
|
+
link,
|
|
2727
|
+
items: children
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
async function createMarkdownNode(entry, fullPath, relativePath) {
|
|
2731
|
+
if (!isSidebarMarkdownFile(entry.name)) return null;
|
|
2732
|
+
const extension = entry.name.endsWith(".mdx") ? ".mdx" : ".md";
|
|
2733
|
+
const frontmatter = readFrontmatter(await fs.readFile(fullPath, "utf8"));
|
|
2734
|
+
return {
|
|
2735
|
+
text: frontmatter.title ?? formatTitle(entry.name.replace(extension, "")),
|
|
2736
|
+
link: `/${relativePath.replace(extension, "").replaceAll("\\", "/")}`,
|
|
2737
|
+
order: frontmatter.order
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
function readFrontmatter(fileContent) {
|
|
2741
|
+
const parsed = matter(fileContent);
|
|
2742
|
+
return {
|
|
2743
|
+
title: typeof parsed.data.title === "string" ? parsed.data.title : void 0,
|
|
2744
|
+
order: typeof parsed.data.order === "number" ? parsed.data.order : void 0
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
function sortNodes(nodes) {
|
|
2748
|
+
nodes.sort((leftNode, rightNode) => {
|
|
2749
|
+
if (leftNode.order != null && rightNode.order != null) return leftNode.order - rightNode.order;
|
|
2750
|
+
if (leftNode.order != null) return -1;
|
|
2751
|
+
if (rightNode.order != null) return 1;
|
|
2752
|
+
return leftNode.text.localeCompare(rightNode.text);
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
function isSidebarMarkdownFile(fileName) {
|
|
2756
|
+
return (fileName.endsWith(".mdx") || fileName.endsWith(".md")) && !(fileName === "index.mdx" || fileName === "index.md");
|
|
2757
|
+
}
|
|
2758
|
+
function formatTitle(name) {
|
|
2759
|
+
return name.replaceAll(/[_-]/g, " ").replaceAll(/\b\w/g, (char) => char.toUpperCase());
|
|
2760
|
+
}
|
|
2761
|
+
function stripOrderFromNode(node) {
|
|
2762
|
+
return {
|
|
2763
|
+
text: node.text,
|
|
2764
|
+
link: node.link,
|
|
2765
|
+
items: node.items?.map((item) => stripOrderFromNode(item))
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
async function fileExists(filePath) {
|
|
2769
|
+
try {
|
|
2770
|
+
await fs.access(filePath);
|
|
2771
|
+
return true;
|
|
2772
|
+
} catch {
|
|
2773
|
+
return false;
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
//#endregion
|
|
2777
|
+
//#region src/vite/plugin.ts
|
|
2778
|
+
const VIRTUAL_MODULE_ID = "virtual:ardo/config";
|
|
2779
|
+
const VIRTUAL_SIDEBAR_ID = "virtual:ardo/sidebar";
|
|
2780
|
+
const VIRTUAL_SEARCH_ID = "virtual:ardo/search-index";
|
|
2781
|
+
const RESOLVED_IDS = {
|
|
2782
|
+
[VIRTUAL_MODULE_ID]: `\0${VIRTUAL_MODULE_ID}`,
|
|
2783
|
+
[VIRTUAL_SIDEBAR_ID]: `\0${VIRTUAL_SIDEBAR_ID}`,
|
|
2784
|
+
[VIRTUAL_SEARCH_ID]: `\0${VIRTUAL_SEARCH_ID}`
|
|
1334
2785
|
};
|
|
2786
|
+
let typedocGenerated = false;
|
|
2787
|
+
function ardoPlugin(options = {}) {
|
|
2788
|
+
const { routes, typedoc, githubPages = true, routesDir: routesDirOption, ...pressConfig } = options;
|
|
2789
|
+
const state = { routesDir: resolveRoutesDir(process.cwd(), routesDirOption) };
|
|
2790
|
+
const plugins = [createMainPlugin(state, {
|
|
2791
|
+
githubPages,
|
|
2792
|
+
pressConfig,
|
|
2793
|
+
routesDirOption
|
|
2794
|
+
})];
|
|
2795
|
+
addRoutesPlugin(plugins, routes, routesDirOption);
|
|
2796
|
+
addTypeDocPlugin(plugins, typedoc, state);
|
|
2797
|
+
plugins.push(ardoCodeBlockPlugin(pressConfig.markdown));
|
|
2798
|
+
plugins.push(createMdxPlugin(pressConfig.markdown));
|
|
2799
|
+
plugins.push(...vanillaExtractPlugin({ identifiers: "short" }));
|
|
2800
|
+
plugins.push(...getReactRouterPlugins());
|
|
2801
|
+
if (githubPages) plugins.push(createFlattenPlugin());
|
|
2802
|
+
return plugins;
|
|
2803
|
+
}
|
|
2804
|
+
function addRoutesPlugin(plugins, routes, routesDirOption) {
|
|
2805
|
+
if (routes === false) return;
|
|
2806
|
+
const routePluginOptions = routes ?? {};
|
|
2807
|
+
plugins.push(ardoRoutesPlugin({
|
|
2808
|
+
routesDir: routesDirOption,
|
|
2809
|
+
...routePluginOptions
|
|
2810
|
+
}));
|
|
2811
|
+
}
|
|
2812
|
+
function addTypeDocPlugin(plugins, typedoc, state) {
|
|
2813
|
+
const typedocConfig = resolveTypedocConfig(typedoc);
|
|
2814
|
+
if (typedocConfig != null) plugins.unshift(createTypeDocPlugin(typedocConfig, state));
|
|
2815
|
+
}
|
|
2816
|
+
function createMainPlugin(state, options) {
|
|
2817
|
+
return {
|
|
2818
|
+
name: "ardo",
|
|
2819
|
+
enforce: "pre",
|
|
2820
|
+
config(userConfig, env) {
|
|
2821
|
+
return createMainConfig(state, {
|
|
2822
|
+
userConfig,
|
|
2823
|
+
command: env.command,
|
|
2824
|
+
githubPages: options.githubPages,
|
|
2825
|
+
routesDirOption: options.routesDirOption
|
|
2826
|
+
});
|
|
2827
|
+
},
|
|
2828
|
+
configResolved(config) {
|
|
2829
|
+
state.routesDir = resolveRoutesDir(config.root, options.routesDirOption);
|
|
2830
|
+
state.resolvedConfig = resolveArdoConfig(config.root, state.routesDir, options.pressConfig);
|
|
2831
|
+
},
|
|
2832
|
+
resolveId(id) {
|
|
2833
|
+
return resolveVirtualModuleId(id);
|
|
2834
|
+
},
|
|
2835
|
+
async load(id) {
|
|
2836
|
+
return loadVirtualModule(id, state);
|
|
2837
|
+
},
|
|
2838
|
+
transform(code, id) {
|
|
2839
|
+
return transformMarkdownMeta(code, id, state);
|
|
2840
|
+
}
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
function createMainConfig(state, input) {
|
|
2844
|
+
const { command, githubPages, routesDirOption, userConfig } = input;
|
|
2845
|
+
const root = userConfig.root ?? process.cwd();
|
|
2846
|
+
state.routesDir = resolveRoutesDir(root, routesDirOption);
|
|
2847
|
+
const config = {
|
|
2848
|
+
define: { __BUILD_TIME__: JSON.stringify((/* @__PURE__ */ new Date()).toISOString()) },
|
|
2849
|
+
optimizeDeps: { exclude: ["ardo/ui/styles.css"] },
|
|
2850
|
+
ssr: { noExternal: ["ardo"] }
|
|
2851
|
+
};
|
|
2852
|
+
if (githubPages && command === "build" && userConfig.base == null) {
|
|
2853
|
+
const repoName = detectGitHubRepoName(root);
|
|
2854
|
+
if (repoName != null) {
|
|
2855
|
+
config.base = `/${repoName}/`;
|
|
2856
|
+
console.log(`[ardo] GitHub Pages detected, using base: ${config.base}`);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
return config;
|
|
2860
|
+
}
|
|
2861
|
+
function resolveArdoConfig(root, routesDir, pressConfig) {
|
|
2862
|
+
const project = {
|
|
2863
|
+
...readProjectMeta(root),
|
|
2864
|
+
...pressConfig.project
|
|
2865
|
+
};
|
|
2866
|
+
return resolveConfig({
|
|
2867
|
+
title: pressConfig.title ?? "Ardo",
|
|
2868
|
+
description: pressConfig.description ?? "Documentation powered by Ardo",
|
|
2869
|
+
...pressConfig,
|
|
2870
|
+
project,
|
|
2871
|
+
srcDir: routesDir
|
|
2872
|
+
}, root);
|
|
2873
|
+
}
|
|
2874
|
+
function resolveVirtualModuleId(id) {
|
|
2875
|
+
return RESOLVED_IDS[id];
|
|
2876
|
+
}
|
|
2877
|
+
async function loadVirtualModule(id, state) {
|
|
2878
|
+
if (state.resolvedConfig == null) return;
|
|
2879
|
+
if (id === RESOLVED_IDS[VIRTUAL_MODULE_ID]) {
|
|
2880
|
+
const clientConfig = {
|
|
2881
|
+
title: state.resolvedConfig.title,
|
|
2882
|
+
description: state.resolvedConfig.description,
|
|
2883
|
+
base: state.resolvedConfig.base,
|
|
2884
|
+
lang: state.resolvedConfig.lang,
|
|
2885
|
+
project: state.resolvedConfig.project,
|
|
2886
|
+
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2887
|
+
buildHash: detectGitHash(state.resolvedConfig.root)
|
|
2888
|
+
};
|
|
2889
|
+
return `export default ${JSON.stringify(clientConfig)}`;
|
|
2890
|
+
}
|
|
2891
|
+
if (id === RESOLVED_IDS[VIRTUAL_SIDEBAR_ID]) {
|
|
2892
|
+
const sidebar = await generateSidebar$1(state.routesDir);
|
|
2893
|
+
return `export default ${JSON.stringify(sidebar)}`;
|
|
2894
|
+
}
|
|
2895
|
+
if (id === RESOLVED_IDS[VIRTUAL_SEARCH_ID]) {
|
|
2896
|
+
const searchIndex = await generateSearchIndex(state.routesDir);
|
|
2897
|
+
return `export default ${JSON.stringify(searchIndex)}`;
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
function transformMarkdownMeta(code, id, state) {
|
|
2901
|
+
if (!shouldInjectMeta(code, id, state)) return;
|
|
2902
|
+
const pageTitle = extractFrontmatterValue(code, "title");
|
|
2903
|
+
if (pageTitle == null || pageTitle === "") return;
|
|
2904
|
+
return {
|
|
2905
|
+
code: `${code}\nexport const meta = () => [${buildMetaEntries({
|
|
2906
|
+
pageTitle,
|
|
2907
|
+
siteTitle: state.resolvedConfig?.title ?? "Ardo",
|
|
2908
|
+
titleSeparator: state.resolvedConfig?.titleSeparator ?? " | ",
|
|
2909
|
+
description: extractFrontmatterValue(code, "description")
|
|
2910
|
+
}).join(", ")}];\n`,
|
|
2911
|
+
map: null
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2914
|
+
function shouldInjectMeta(code, id, state) {
|
|
2915
|
+
return isMarkdownFile(id) && id.startsWith(state.routesDir) && !hasMetaExport(code);
|
|
2916
|
+
}
|
|
2917
|
+
function buildMetaEntries(input) {
|
|
2918
|
+
const fullTitle = `${input.pageTitle}${input.titleSeparator}${input.siteTitle}`;
|
|
2919
|
+
const entries = [`{ title: ${JSON.stringify(fullTitle)} }`];
|
|
2920
|
+
if (input.description != null && input.description !== "") entries.push(`{ name: "description", content: ${JSON.stringify(input.description)} }`);
|
|
2921
|
+
return entries;
|
|
2922
|
+
}
|
|
2923
|
+
function isMarkdownFile(id) {
|
|
2924
|
+
return id.endsWith(".md") || id.endsWith(".mdx");
|
|
2925
|
+
}
|
|
2926
|
+
function hasMetaExport(code) {
|
|
2927
|
+
return code.includes("export const meta") || code.includes("export function meta");
|
|
2928
|
+
}
|
|
2929
|
+
function extractFrontmatterValue(code, key) {
|
|
2930
|
+
const frontmatterStart = code.indexOf("export const frontmatter");
|
|
2931
|
+
if (frontmatterStart === -1) return;
|
|
2932
|
+
const valuePrefix = `${key}: "`;
|
|
2933
|
+
const valueStart = code.indexOf(valuePrefix, frontmatterStart);
|
|
2934
|
+
if (valueStart === -1) return;
|
|
2935
|
+
const startIndex = valueStart + valuePrefix.length;
|
|
2936
|
+
const endIndex = code.indexOf("\"", startIndex);
|
|
2937
|
+
if (endIndex === -1) return;
|
|
2938
|
+
return code.slice(startIndex, endIndex);
|
|
2939
|
+
}
|
|
2940
|
+
function resolveTypedocConfig(typedoc) {
|
|
2941
|
+
if (typedoc == null) return;
|
|
2942
|
+
const packageRoot = findPackageRoot(process.cwd());
|
|
2943
|
+
const defaultEntryPoint = packageRoot != null ? `${packageRoot}/src/index.ts` : "./src/index.ts";
|
|
2944
|
+
const defaultTsconfig = packageRoot != null ? `${packageRoot}/tsconfig.json` : "./tsconfig.json";
|
|
2945
|
+
const defaults = {
|
|
2946
|
+
enabled: true,
|
|
2947
|
+
entryPoints: [defaultEntryPoint],
|
|
2948
|
+
tsconfig: defaultTsconfig,
|
|
2949
|
+
out: "api-reference",
|
|
2950
|
+
excludePrivate: true,
|
|
2951
|
+
excludeInternal: true
|
|
2952
|
+
};
|
|
2953
|
+
return typedoc === true ? defaults : {
|
|
2954
|
+
...defaults,
|
|
2955
|
+
...typedoc
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
function createTypeDocPlugin(typedocConfig, state) {
|
|
2959
|
+
return {
|
|
2960
|
+
name: "ardo:typedoc",
|
|
2961
|
+
async buildStart() {
|
|
2962
|
+
if (!typedocConfig.enabled || typedocGenerated) return;
|
|
2963
|
+
typedocGenerated = true;
|
|
2964
|
+
console.log("[ardo] Generating API documentation with TypeDoc...");
|
|
2965
|
+
const startTime = Date.now();
|
|
2966
|
+
try {
|
|
2967
|
+
const docs = await generateApiDocs(typedocConfig, state.routesDir);
|
|
2968
|
+
const duration = Date.now() - startTime;
|
|
2969
|
+
console.log(`[ardo] Generated ${docs.length} API documentation pages in ${duration}ms`);
|
|
2970
|
+
} catch (error) {
|
|
2971
|
+
console.warn("[ardo] TypeDoc generation failed. API documentation will not be available.");
|
|
2972
|
+
console.warn("[ardo] Check your typedoc.entryPoints configuration.");
|
|
2973
|
+
if (error instanceof Error) console.warn(`[ardo] Error: ${error.message}`);
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
function resolveRoutesDir(root, routesDirOption) {
|
|
2979
|
+
return routesDirOption ?? path.join(root, "app", "routes");
|
|
2980
|
+
}
|
|
2981
|
+
//#endregion
|
|
2982
|
+
export { ardoPlugin as ardo, ardoPlugin, ardoPlugin as default, ardoRoutesPlugin, createShikiHighlighter, detectGitHubBasename, generateSidebar, getPageDataForRoute, getSlugFromPath, highlightCode, loadAllDocs, loadDoc, transformMarkdown, transformMarkdownToReact };
|
|
2983
|
+
|
|
1335
2984
|
//# sourceMappingURL=index.js.map
|