boltdocs 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CodeBlock-37XMKCYY.mjs +7 -0
- package/dist/PackageManagerTabs-4NWXLXQO.mjs +314 -0
- package/dist/Playground-OE2OE6B6.mjs +7 -0
- package/dist/SearchDialog-FTOQZ763.mjs +187 -0
- package/dist/SearchDialog-ZAZXYIFX.css +2147 -0
- package/dist/Video-I6QY4X7J.mjs +7 -0
- package/dist/chunk-2YRDWM6O.mjs +56 -0
- package/dist/chunk-PN4GCTYG.mjs +67 -0
- package/dist/chunk-X2TDGMTR.mjs +64 -0
- package/dist/chunk-X6BYQHVC.mjs +12 -0
- package/dist/chunk-Z7JHYNAS.mjs +57 -0
- package/dist/chunk-ZFCOLEXN.mjs +1644 -0
- package/dist/client/index.css +2147 -0
- package/dist/client/index.d.mts +298 -0
- package/dist/client/index.d.ts +298 -0
- package/dist/client/index.js +2793 -0
- package/dist/client/index.mjs +63 -0
- package/dist/client/ssr.css +2147 -0
- package/dist/client/ssr.d.mts +25 -0
- package/dist/client/ssr.d.ts +25 -0
- package/dist/client/ssr.js +2727 -0
- package/dist/client/ssr.mjs +32 -0
- package/dist/config-D2XmHJYe.d.mts +122 -0
- package/dist/config-D2XmHJYe.d.ts +122 -0
- package/dist/index-CRQKWAeo.d.mts +82 -0
- package/dist/index-CRQKWAeo.d.ts +82 -0
- package/dist/node/cli/index.d.mts +1 -0
- package/dist/node/cli/index.d.ts +1 -0
- package/dist/node/cli/index.js +199 -0
- package/dist/node/cli/index.mjs +154 -0
- package/dist/node/index.d.mts +79 -0
- package/dist/node/index.d.ts +79 -0
- package/dist/node/index.js +797 -0
- package/dist/node/index.mjs +719 -0
- package/package.json +79 -0
- package/src/client/app/index.tsx +422 -0
- package/src/client/app/preload.tsx +56 -0
- package/src/client/index.ts +40 -0
- package/src/client/ssr.tsx +50 -0
- package/src/client/theme/components/CodeBlock/CodeBlock.tsx +76 -0
- package/src/client/theme/components/CodeBlock/index.ts +1 -0
- package/src/client/theme/components/PackageManagerTabs/PackageManagerTabs.tsx +154 -0
- package/src/client/theme/components/PackageManagerTabs/index.ts +1 -0
- package/src/client/theme/components/PackageManagerTabs/pkg-tabs.css +64 -0
- package/src/client/theme/components/Playground/Playground.tsx +86 -0
- package/src/client/theme/components/Playground/index.ts +1 -0
- package/src/client/theme/components/Playground/playground.css +168 -0
- package/src/client/theme/components/Video/Video.tsx +84 -0
- package/src/client/theme/components/Video/index.ts +1 -0
- package/src/client/theme/components/Video/video.css +41 -0
- package/src/client/theme/components/mdx/Admonition.tsx +80 -0
- package/src/client/theme/components/mdx/Badge.tsx +31 -0
- package/src/client/theme/components/mdx/Button.tsx +50 -0
- package/src/client/theme/components/mdx/Card.tsx +80 -0
- package/src/client/theme/components/mdx/List.tsx +57 -0
- package/src/client/theme/components/mdx/Tabs.tsx +94 -0
- package/src/client/theme/components/mdx/index.ts +18 -0
- package/src/client/theme/components/mdx/mdx-components.css +405 -0
- package/src/client/theme/icons/bun.tsx +62 -0
- package/src/client/theme/icons/deno.tsx +20 -0
- package/src/client/theme/icons/discord.tsx +12 -0
- package/src/client/theme/icons/github.tsx +15 -0
- package/src/client/theme/icons/npm.tsx +13 -0
- package/src/client/theme/icons/pnpm.tsx +72 -0
- package/src/client/theme/icons/twitter.tsx +12 -0
- package/src/client/theme/styles/home.css +60 -0
- package/src/client/theme/styles/markdown.css +343 -0
- package/src/client/theme/styles/variables.css +162 -0
- package/src/client/theme/styles.css +38 -0
- package/src/client/theme/ui/BackgroundGradient/BackgroundGradient.tsx +10 -0
- package/src/client/theme/ui/BackgroundGradient/index.ts +1 -0
- package/src/client/theme/ui/Breadcrumbs/Breadcrumbs.tsx +68 -0
- package/src/client/theme/ui/Breadcrumbs/index.ts +1 -0
- package/src/client/theme/ui/Footer/footer.css +32 -0
- package/src/client/theme/ui/Head/Head.tsx +69 -0
- package/src/client/theme/ui/Head/index.ts +1 -0
- package/src/client/theme/ui/LanguageSwitcher/LanguageSwitcher.tsx +125 -0
- package/src/client/theme/ui/LanguageSwitcher/index.ts +1 -0
- package/src/client/theme/ui/LanguageSwitcher/language-switcher.css +98 -0
- package/src/client/theme/ui/Layout/Layout.tsx +213 -0
- package/src/client/theme/ui/Layout/base.css +76 -0
- package/src/client/theme/ui/Layout/index.ts +2 -0
- package/src/client/theme/ui/Layout/pagination.css +72 -0
- package/src/client/theme/ui/Layout/responsive.css +40 -0
- package/src/client/theme/ui/Link/Link.tsx +202 -0
- package/src/client/theme/ui/Link/index.ts +2 -0
- package/src/client/theme/ui/Loading/Loading.tsx +10 -0
- package/src/client/theme/ui/Loading/index.ts +1 -0
- package/src/client/theme/ui/Loading/loading.css +30 -0
- package/src/client/theme/ui/Navbar/GithubStars.tsx +27 -0
- package/src/client/theme/ui/Navbar/Navbar.tsx +145 -0
- package/src/client/theme/ui/Navbar/index.ts +2 -0
- package/src/client/theme/ui/Navbar/navbar.css +233 -0
- package/src/client/theme/ui/NotFound/NotFound.tsx +20 -0
- package/src/client/theme/ui/NotFound/index.ts +1 -0
- package/src/client/theme/ui/NotFound/not-found.css +64 -0
- package/src/client/theme/ui/OnThisPage/OnThisPage.tsx +192 -0
- package/src/client/theme/ui/OnThisPage/index.ts +1 -0
- package/src/client/theme/ui/OnThisPage/toc.css +132 -0
- package/src/client/theme/ui/PoweredBy/PoweredBy.tsx +18 -0
- package/src/client/theme/ui/PoweredBy/index.ts +1 -0
- package/src/client/theme/ui/PoweredBy/powered-by.css +76 -0
- package/src/client/theme/ui/SearchDialog/SearchDialog.tsx +199 -0
- package/src/client/theme/ui/SearchDialog/index.ts +1 -0
- package/src/client/theme/ui/SearchDialog/search.css +152 -0
- package/src/client/theme/ui/Sidebar/Sidebar.tsx +200 -0
- package/src/client/theme/ui/Sidebar/index.ts +1 -0
- package/src/client/theme/ui/Sidebar/sidebar.css +269 -0
- package/src/client/theme/ui/ThemeToggle/ThemeToggle.tsx +69 -0
- package/src/client/theme/ui/ThemeToggle/index.ts +1 -0
- package/src/client/theme/ui/VersionSwitcher/VersionSwitcher.tsx +136 -0
- package/src/client/theme/ui/VersionSwitcher/index.ts +1 -0
- package/src/client/utils.ts +26 -0
- package/src/node/cache.ts +94 -0
- package/src/node/cli/commands/config.ts +15 -0
- package/src/node/cli/commands/generate-css.ts +24 -0
- package/src/node/cli/constants.ts +70 -0
- package/src/node/cli/index.ts +22 -0
- package/src/node/config.ts +185 -0
- package/src/node/index.ts +21 -0
- package/src/node/mdx.ts +41 -0
- package/src/node/plugin/entry.ts +58 -0
- package/src/node/plugin/html.ts +55 -0
- package/src/node/plugin/index.ts +190 -0
- package/src/node/plugin/types.ts +11 -0
- package/src/node/routes/cache.ts +24 -0
- package/src/node/routes/index.ts +152 -0
- package/src/node/routes/parser.ts +127 -0
- package/src/node/routes/sorter.ts +42 -0
- package/src/node/routes/types.ts +49 -0
- package/src/node/ssg/index.ts +110 -0
- package/src/node/ssg/meta.ts +34 -0
- package/src/node/ssg/options.ts +13 -0
- package/src/node/ssg/sitemap.ts +54 -0
- package/src/node/utils.ts +134 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +22 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata representing a single documentation route.
|
|
3
|
+
* This information is used to build the client-side router and the sidebar navigation.
|
|
4
|
+
*/
|
|
5
|
+
export interface RouteMeta {
|
|
6
|
+
/** The final URL path for the route (e.g., '/docs/guide/start') */
|
|
7
|
+
path: string;
|
|
8
|
+
/** The absolute filesystem path to the source markdown/mdx file */
|
|
9
|
+
componentPath: string;
|
|
10
|
+
/** The title of the page, usually extracted from frontmatter or the filename */
|
|
11
|
+
title: string;
|
|
12
|
+
/** The relative path from the docs directory, used for edit links */
|
|
13
|
+
filePath: string;
|
|
14
|
+
/** Optional description of the page (for SEO/meta tags) */
|
|
15
|
+
description?: string;
|
|
16
|
+
/** Optional explicit position for ordering in the sidebar */
|
|
17
|
+
sidebarPosition?: number;
|
|
18
|
+
/** The group (directory) this route belongs to */
|
|
19
|
+
group?: string;
|
|
20
|
+
/** The display title for the route's group */
|
|
21
|
+
groupTitle?: string;
|
|
22
|
+
/** Optional explicit position for ordering the group itself */
|
|
23
|
+
groupPosition?: number;
|
|
24
|
+
/** Extracted markdown headings for search indexing */
|
|
25
|
+
headings?: { level: number; text: string; id: string }[];
|
|
26
|
+
/** The locale this route belongs to, if i18n is configured */
|
|
27
|
+
locale?: string;
|
|
28
|
+
/** The version this route belongs to, if versioning is configured */
|
|
29
|
+
version?: string;
|
|
30
|
+
/** Optional badge to display next to the sidebar item (e.g., 'New', 'Experimental') */
|
|
31
|
+
badge?: string | { text: string; expires?: string };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Internal representation of a parsed documentation file before finalizing groups.
|
|
36
|
+
* Stored in the file cache to avoid re-parsing unchanged files.
|
|
37
|
+
*/
|
|
38
|
+
export interface ParsedDocFile {
|
|
39
|
+
/** The core route metadata without group-level details (inferred later) */
|
|
40
|
+
route: Omit<RouteMeta, "group" | "groupTitle" | "groupPosition">;
|
|
41
|
+
/** The base directory of the file (used to group files together) */
|
|
42
|
+
relativeDir?: string;
|
|
43
|
+
/** Whether this file is the index file for its directory group */
|
|
44
|
+
isGroupIndex: boolean;
|
|
45
|
+
/** If this is a group index, any specific frontmatter metadata dictating the group's title and position */
|
|
46
|
+
groupMeta?: { title: string; position?: number };
|
|
47
|
+
/** Extracted group position from the directory name if it has a numeric prefix */
|
|
48
|
+
inferredGroupPosition?: number;
|
|
49
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { generateRoutes } from "../routes";
|
|
4
|
+
import { escapeHtml } from "../utils";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
|
|
8
|
+
import { SSGOptions } from "./options";
|
|
9
|
+
import { replaceMetaTags } from "./meta";
|
|
10
|
+
import { generateSitemap } from "./sitemap";
|
|
11
|
+
|
|
12
|
+
// Re-export options for consumers
|
|
13
|
+
export type { SSGOptions };
|
|
14
|
+
|
|
15
|
+
// Polyfill __dirname and require for ESM
|
|
16
|
+
const _filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const _dirname = path.dirname(_filename);
|
|
18
|
+
const _require = createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Generates static HTML files and a \`sitemap.xml\` for all documentation routes.
|
|
22
|
+
* Called automatically in the \`closeBundle\` hook of the Vite plugin during a production build.
|
|
23
|
+
*
|
|
24
|
+
* @param options - Configuration for paths and site metadata
|
|
25
|
+
*/
|
|
26
|
+
export async function generateStaticPages(options: SSGOptions): Promise<void> {
|
|
27
|
+
const { docsDir, outDir, config } = options;
|
|
28
|
+
const routes = await generateRoutes(docsDir, config);
|
|
29
|
+
const siteTitle = config?.themeConfig?.title || "Boltdocs";
|
|
30
|
+
const siteDescription = config?.themeConfig?.description || "";
|
|
31
|
+
|
|
32
|
+
// Resolve the SSR module (compiled by tsup)
|
|
33
|
+
const ssrModulePath = path.resolve(_dirname, "../client/ssr.js");
|
|
34
|
+
if (!fs.existsSync(ssrModulePath)) {
|
|
35
|
+
console.error(
|
|
36
|
+
"[boltdocs] SSR module not found at",
|
|
37
|
+
ssrModulePath,
|
|
38
|
+
"- Did you build the core package?",
|
|
39
|
+
);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { render } = _require(ssrModulePath);
|
|
43
|
+
|
|
44
|
+
// Read the built index.html as template
|
|
45
|
+
const templatePath = path.join(outDir, "index.html");
|
|
46
|
+
if (!fs.existsSync(templatePath)) {
|
|
47
|
+
console.warn("[boltdocs] No index.html found in outDir, skipping SSG.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const template = fs.readFileSync(templatePath, "utf-8");
|
|
51
|
+
|
|
52
|
+
// Load user's homePage if configured
|
|
53
|
+
let homePageComp;
|
|
54
|
+
if ((config as any)?._homePagePath) {
|
|
55
|
+
try {
|
|
56
|
+
// Simplistic: if there's a custom home page compiled, we'd need it available to SSR.
|
|
57
|
+
// In a full framework this is complex, but for Boltdocs we assume it's bundled if needed.
|
|
58
|
+
} catch (e) {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Generate an HTML file for each route concurrently
|
|
62
|
+
await Promise.all(
|
|
63
|
+
routes.map(async (route) => {
|
|
64
|
+
const pageTitle = `${route.title} | ${siteTitle}`;
|
|
65
|
+
const pageDescription = route.description || siteDescription;
|
|
66
|
+
|
|
67
|
+
// We mock the modules for SSR so it doesn't crash trying to dynamically import
|
|
68
|
+
const fakeModules: Record<string, any> = {};
|
|
69
|
+
fakeModules[route.componentPath] = { default: () => {} }; // Mock MDX component
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const appHtml = await render({
|
|
73
|
+
path: route.path,
|
|
74
|
+
routes: routes,
|
|
75
|
+
config: config || {},
|
|
76
|
+
modules: fakeModules,
|
|
77
|
+
homePage: homePageComp,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const html = replaceMetaTags(template, {
|
|
81
|
+
title: escapeHtml(pageTitle),
|
|
82
|
+
description: escapeHtml(pageDescription),
|
|
83
|
+
})
|
|
84
|
+
.replace("<!--app-html-->", appHtml)
|
|
85
|
+
.replace(`<div id="root"></div>`, `<div id="root">${appHtml}</div>`);
|
|
86
|
+
|
|
87
|
+
const routeDir = path.join(outDir, route.path);
|
|
88
|
+
await fs.promises.mkdir(routeDir, { recursive: true });
|
|
89
|
+
await fs.promises.writeFile(
|
|
90
|
+
path.join(routeDir, "index.html"),
|
|
91
|
+
html,
|
|
92
|
+
"utf-8",
|
|
93
|
+
);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
console.error(`[boltdocs] Error SSR rendering route ${route.path}:`, e);
|
|
96
|
+
}
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
// Generate sitemap.xml
|
|
101
|
+
const sitemap = generateSitemap(
|
|
102
|
+
routes.map((r) => r.path),
|
|
103
|
+
config,
|
|
104
|
+
);
|
|
105
|
+
fs.writeFileSync(path.join(outDir, "sitemap.xml"), sitemap, "utf-8");
|
|
106
|
+
|
|
107
|
+
console.log(
|
|
108
|
+
`[boltdocs] Generated ${routes.length} static pages + sitemap.xml`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces placeholder or default meta tags in the HTML template with page-specific values.
|
|
3
|
+
*
|
|
4
|
+
* @param html - The base HTML template string
|
|
5
|
+
* @param meta - An object containing the derived `title` and `description` for the specific page
|
|
6
|
+
* @returns The final HTML string for that specific page
|
|
7
|
+
*/
|
|
8
|
+
export function replaceMetaTags(
|
|
9
|
+
html: string,
|
|
10
|
+
meta: { title: string; description: string },
|
|
11
|
+
): string {
|
|
12
|
+
return html
|
|
13
|
+
.replace(/<title>.*?<\/title>/, `<title>${meta.title}</title>`)
|
|
14
|
+
.replace(
|
|
15
|
+
/(<meta name="description" content=")[^"]*(")/,
|
|
16
|
+
`$1${meta.description}$2`,
|
|
17
|
+
)
|
|
18
|
+
.replace(
|
|
19
|
+
/(<meta property="og:title" content=")[^"]*(")/,
|
|
20
|
+
`$1${meta.title}$2`,
|
|
21
|
+
)
|
|
22
|
+
.replace(
|
|
23
|
+
/(<meta property="og:description" content=")[^"]*(")/,
|
|
24
|
+
`$1${meta.description}$2`,
|
|
25
|
+
)
|
|
26
|
+
.replace(
|
|
27
|
+
/(<meta name="twitter:title" content=")[^"]*(")/,
|
|
28
|
+
`$1${meta.title}$2`,
|
|
29
|
+
)
|
|
30
|
+
.replace(
|
|
31
|
+
/(<meta name="twitter:description" content=")[^"]*(")/,
|
|
32
|
+
`$1${meta.description}$2`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BoltdocsConfig } from "../config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options for the Static Site Generation process.
|
|
5
|
+
*/
|
|
6
|
+
export interface SSGOptions {
|
|
7
|
+
/** The root directory containing markdown documentation files */
|
|
8
|
+
docsDir: string;
|
|
9
|
+
/** The output directory where Vite placed the compiled `index.html` and assets */
|
|
10
|
+
outDir: string;
|
|
11
|
+
/** Pre-resolved config (avoids re-resolving during the SSG phase) */
|
|
12
|
+
config?: BoltdocsConfig;
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { BoltdocsConfig } from "../config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a standard XML sitemap for search engine crawlers.
|
|
5
|
+
*
|
|
6
|
+
* @param routePaths - An array of existing URL paths (e.g., ['/docs/intro', '/docs/setup'])
|
|
7
|
+
* @param config - The Boltdocs configuration containing i18n and siteUrl settings
|
|
8
|
+
* @returns The formatted XML sitemap string
|
|
9
|
+
*/
|
|
10
|
+
export function generateSitemap(
|
|
11
|
+
routePaths: string[],
|
|
12
|
+
config?: BoltdocsConfig,
|
|
13
|
+
): string {
|
|
14
|
+
const baseUrl = config?.siteUrl?.replace(/\/$/, "") || "https://example.com";
|
|
15
|
+
const today = new Date().toISOString().split("T")[0];
|
|
16
|
+
|
|
17
|
+
const rootEntries = [{ url: "/", priority: "1.0", changefreq: "daily" }];
|
|
18
|
+
|
|
19
|
+
if (config?.i18n) {
|
|
20
|
+
const defaultLocale = config.i18n.defaultLocale;
|
|
21
|
+
for (const locale of Object.keys(config.i18n.locales)) {
|
|
22
|
+
if (locale !== defaultLocale) {
|
|
23
|
+
rootEntries.push({
|
|
24
|
+
url: `/${locale}/`,
|
|
25
|
+
priority: "1.0",
|
|
26
|
+
changefreq: "daily",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const entries = [
|
|
33
|
+
...rootEntries,
|
|
34
|
+
...routePaths.map((p) => ({
|
|
35
|
+
url: p,
|
|
36
|
+
priority: "0.8",
|
|
37
|
+
changefreq: "weekly",
|
|
38
|
+
})),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
42
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
43
|
+
${entries
|
|
44
|
+
.map(
|
|
45
|
+
(e) => ` <url>
|
|
46
|
+
<loc>${baseUrl}${e.url}</loc>
|
|
47
|
+
<lastmod>${today}</lastmod>
|
|
48
|
+
<changefreq>${e.changefreq}</changefreq>
|
|
49
|
+
<priority>${e.priority}</priority>
|
|
50
|
+
</url>`,
|
|
51
|
+
)
|
|
52
|
+
.join("\n")}
|
|
53
|
+
</urlset>`;
|
|
54
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import matter from "gray-matter";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalizes a file path by replacing Windows backslashes with forward slashes.
|
|
6
|
+
* Ensures consistent path handling across different operating systems.
|
|
7
|
+
*
|
|
8
|
+
* @param p - The file path to normalize
|
|
9
|
+
* @returns The normalized path using forward slashes
|
|
10
|
+
*/
|
|
11
|
+
export function normalizePath(p: string): string {
|
|
12
|
+
return p.replace(/\\/g, "/");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Strips a numeric prefix from a file or directory name (e.g., '1.guide' -> 'guide').
|
|
17
|
+
*
|
|
18
|
+
* @param name - The name to strip
|
|
19
|
+
* @returns The name without the numeric prefix
|
|
20
|
+
*/
|
|
21
|
+
export function stripNumberPrefix(name: string): string {
|
|
22
|
+
return name.replace(/^\d+\./, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extracts the numeric prefix from a file or directory name if it exists.
|
|
27
|
+
*
|
|
28
|
+
* @param name - The name to parse
|
|
29
|
+
* @returns The extracted number, or undefined if none exists
|
|
30
|
+
*/
|
|
31
|
+
export function extractNumberPrefix(name: string): number | undefined {
|
|
32
|
+
const match = name.match(/^(\d+)\./);
|
|
33
|
+
return match ? parseInt(match[1], 10) : undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Checks if a given file path points to a Markdown or MDX file.
|
|
38
|
+
*
|
|
39
|
+
* @param filePath - The path to check
|
|
40
|
+
* @returns True if the file ends with .md or .mdx, false otherwise
|
|
41
|
+
*/
|
|
42
|
+
export function isDocFile(filePath: string): boolean {
|
|
43
|
+
return /\.mdx?$/.test(filePath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Retrieves the modification time (mtime) of a file in milliseconds.
|
|
48
|
+
* Useful for caching strategies to detect if a file has changed.
|
|
49
|
+
* Returns 0 if the file doesn't exist or cannot be accessed.
|
|
50
|
+
*
|
|
51
|
+
* @param filePath - The absolute path to the file
|
|
52
|
+
* @returns The modification time in milliseconds, or 0 on error
|
|
53
|
+
*/
|
|
54
|
+
export function getFileMtime(filePath: string): number {
|
|
55
|
+
try {
|
|
56
|
+
return fs.statSync(filePath).mtimeMs;
|
|
57
|
+
} catch {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parses frontmatter and markdown content from a file synchronously.
|
|
64
|
+
* Uses `gray-matter` for parsing. Returns the parsed data and the remaining markdown content.
|
|
65
|
+
*
|
|
66
|
+
* @param filePath - The absolute path to the markdown/mdx file
|
|
67
|
+
* @returns An object containing the parsed metadata (`data`) and the raw markdown (`content`)
|
|
68
|
+
*/
|
|
69
|
+
export function parseFrontmatter(filePath: string): {
|
|
70
|
+
data: Record<string, any>;
|
|
71
|
+
content: string;
|
|
72
|
+
} {
|
|
73
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
74
|
+
const { data, content } = matter(raw);
|
|
75
|
+
return { data, content };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Escapes special HTML characters in a string to prevent XSS and ensure
|
|
80
|
+
* safe injection into HTML attributes or text content.
|
|
81
|
+
*
|
|
82
|
+
* @param str - The raw string to escape
|
|
83
|
+
* @returns The escaped string
|
|
84
|
+
*/
|
|
85
|
+
export function escapeHtml(str: string): string {
|
|
86
|
+
return str
|
|
87
|
+
.replace(/&/g, "&")
|
|
88
|
+
.replace(/"/g, """)
|
|
89
|
+
.replace(/</g, "<")
|
|
90
|
+
.replace(/>/g, ">");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Converts a file path relative to the `docsDir` into a URL route path.
|
|
95
|
+
* Handles removing extensions, converting `index` files to directory roots,
|
|
96
|
+
* and ensuring proper slash formatting.
|
|
97
|
+
*
|
|
98
|
+
* @param relativePath - The file path relative to the documentation source directory (e.g., 'guide/index.md')
|
|
99
|
+
* @returns The corresponding route path (e.g., '/guide')
|
|
100
|
+
*/
|
|
101
|
+
export function fileToRoutePath(relativePath: string): string {
|
|
102
|
+
// Strip number prefixes from every segment
|
|
103
|
+
let cleanedPath = relativePath.split("/").map(stripNumberPrefix).join("/");
|
|
104
|
+
|
|
105
|
+
let routePath = cleanedPath.replace(/\.mdx?$/, "");
|
|
106
|
+
|
|
107
|
+
// Handle index files → directory root
|
|
108
|
+
if (routePath === "index" || routePath.endsWith("/index")) {
|
|
109
|
+
routePath = routePath.replace(/index$/, "");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Ensure leading slash
|
|
113
|
+
if (!routePath.startsWith("/")) {
|
|
114
|
+
routePath = "/" + routePath;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Remove trailing slash (except for root '/')
|
|
118
|
+
if (routePath.length > 1 && routePath.endsWith("/")) {
|
|
119
|
+
routePath = routePath.slice(0, -1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return routePath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Capitalizes the first letter of a given string.
|
|
127
|
+
* Used primarily for generating default group titles.
|
|
128
|
+
*
|
|
129
|
+
* @param str - The string to capitalize
|
|
130
|
+
* @returns The capitalized string
|
|
131
|
+
*/
|
|
132
|
+
export function capitalize(str: string): string {
|
|
133
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
134
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Node",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"jsx": "react-jsx",
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"outDir": "dist"
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"src"
|
|
15
|
+
],
|
|
16
|
+
"exclude": [
|
|
17
|
+
"node_modules",
|
|
18
|
+
"dist",
|
|
19
|
+
]
|
|
20
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: [
|
|
5
|
+
"src/node/index.ts",
|
|
6
|
+
"src/node/cli/index.ts",
|
|
7
|
+
"src/client/index.ts",
|
|
8
|
+
"src/client/ssr.tsx",
|
|
9
|
+
],
|
|
10
|
+
format: ["cjs", "esm"],
|
|
11
|
+
dts: true,
|
|
12
|
+
tsconfig: "./tsconfig.json",
|
|
13
|
+
clean: true,
|
|
14
|
+
external: [
|
|
15
|
+
"vite",
|
|
16
|
+
"react",
|
|
17
|
+
"react-dom",
|
|
18
|
+
"react-router-dom",
|
|
19
|
+
"virtual:boltdocs-routes",
|
|
20
|
+
"virtual:boltdocs-config",
|
|
21
|
+
],
|
|
22
|
+
});
|