fumapress 0.2.3 → 0.2.5

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.
@@ -0,0 +1,38 @@
1
+ import { renderRootMeta } from "../lib/shared.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import styles from "virtual:root.css?inline";
4
+ import { RootProvider } from "fumadocs-ui/provider/waku";
5
+ //#region src/layouts/root.tsx
6
+ function createRootLayout(options) {
7
+ return async function(props) {
8
+ const { children, lang, i18nConfig, data } = props;
9
+ const hooks = data["core:provider"];
10
+ let providerProps = { ...options?.providerProps };
11
+ if (i18nConfig) {
12
+ const { languages } = i18nConfig;
13
+ providerProps.i18n ??= {
14
+ locale: lang,
15
+ locales: Object.entries(languages).map(([k, v]) => ({
16
+ name: v.displayName,
17
+ locale: k
18
+ })),
19
+ translations: lang ? languages[lang]?.translations : void 0
20
+ };
21
+ }
22
+ if (hooks) for (const hook of hooks) providerProps = await hook(providerProps);
23
+ return /* @__PURE__ */ jsxs("html", {
24
+ lang: lang ?? "en",
25
+ suppressHydrationWarning: true,
26
+ children: [/* @__PURE__ */ jsxs("head", { children: [/* @__PURE__ */ jsx("style", { children: styles }), renderRootMeta(props)] }), /* @__PURE__ */ jsx("body", {
27
+ "data-version": "1.0",
28
+ className: "flex flex-col min-h-screen",
29
+ children: /* @__PURE__ */ jsx(RootProvider, {
30
+ ...providerProps,
31
+ children
32
+ })
33
+ })]
34
+ });
35
+ };
36
+ }
37
+ //#endregion
38
+ export { createRootLayout };
package/dist/lib/fs.js ADDED
@@ -0,0 +1,18 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ //#region src/lib/fs.ts
4
+ /**
5
+ * Returns the absolute path to the root directory of the current git repository.
6
+ */
7
+ function getGitRootDir(startDir = process.cwd()) {
8
+ let dir = startDir;
9
+ while (true) {
10
+ if (existsSync(join(dir, ".git"))) return dir;
11
+ const parent = dirname(dir);
12
+ if (parent === dir) break;
13
+ dir = parent;
14
+ }
15
+ return null;
16
+ }
17
+ //#endregion
18
+ export { getGitRootDir };
@@ -0,0 +1,46 @@
1
+ import { DocsLayoutContextData } from "../layouts/docs.js";
2
+ import { HomeLayoutContextData } from "../layouts/home.js";
3
+ import { NotebookLayoutContextData } from "../layouts/notebook.js";
4
+ import { BuildMode, Config, ConfigContext, I18nConfig } from "../config.js";
5
+ import { Adapter, Awaitable, ServerPlugin } from "./types.js";
6
+ import { ComponentType, ReactNode } from "react";
7
+ import { RootProviderProps } from "fumadocs-ui/provider/waku";
8
+ import { LoaderOutput, Page } from "fumadocs-core/source";
9
+
10
+ //#region src/lib/shared.d.ts
11
+ interface AppContext<C extends ConfigContext = ConfigContext> {
12
+ mode: BuildMode;
13
+ getLoader: () => Awaitable<LoaderOutput<C["loaderConfig"]>>;
14
+ plugins: ServerPlugin<C>[];
15
+ adapters: Adapter<C>[];
16
+ /** always `undefined`, easier way to infer types */
17
+ $context: C;
18
+ /**
19
+ * custom data in app context, can be referenced from plugins/pages etc
20
+ */
21
+ data: AppContextData & Record<string, unknown>;
22
+ i18nConfig?: I18nConfig<C["lang"]>;
23
+ metaConfig?: Config<C>["meta"];
24
+ siteConfig: {
25
+ name: string;
26
+ baseUrl?: string;
27
+ git?: {
28
+ user: string;
29
+ repo: string;
30
+ branch: string;
31
+ rootDir: string;
32
+ };
33
+ };
34
+ }
35
+ interface AppContextData {
36
+ "core:page-meta"?: ((page: Page) => ReactNode)[];
37
+ "core:notebook-layout"?: NotebookLayoutContextData;
38
+ "core:docs-layout"?: DocsLayoutContextData;
39
+ "core:home-layout"?: HomeLayoutContextData;
40
+ "core:provider"?: ((props: RootProviderProps) => Awaitable<RootProviderProps>)[];
41
+ }
42
+ type TransformChildren<T> = Omit<T, "children"> & {
43
+ children?: ((nodes: ReactNode) => ReactNode)[];
44
+ };
45
+ //#endregion
46
+ export { AppContext, AppContextData, TransformChildren };
@@ -0,0 +1,70 @@
1
+ import { getGitRootDir } from "./fs.js";
2
+ import { fumadocsMdx } from "../adapters/mdx.js";
3
+ import path from "node:path";
4
+ import { Fragment } from "react";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/lib/shared.tsx
7
+ function parseConfig(config) {
8
+ return {
9
+ getLoader() {
10
+ if (typeof config.loader === "function") return config.loader();
11
+ return config.loader;
12
+ },
13
+ plugins: config.plugins ?? [],
14
+ adapters: config.adapters ?? [fumadocsMdx()],
15
+ $context: void 0,
16
+ data: {},
17
+ i18nConfig: config.i18n,
18
+ mode: config.mode ?? "default",
19
+ metaConfig: config.meta,
20
+ siteConfig: {
21
+ name: config.site?.name ?? "Fumapress",
22
+ baseUrl: config.site?.baseUrl,
23
+ git: config.site?.git ? {
24
+ ...config.site.git,
25
+ rootDir: config.site.git.rootDir ?? getGitRootDir() ?? process.cwd()
26
+ } : void 0
27
+ }
28
+ };
29
+ }
30
+ function renderRootMeta(context) {
31
+ return context.metaConfig?.root?.call(context);
32
+ }
33
+ function renderPageMeta(page, context) {
34
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
35
+ /* @__PURE__ */ jsx("title", { children: page.data.title }),
36
+ /* @__PURE__ */ jsx("meta", {
37
+ property: "og:title",
38
+ content: page.data.title
39
+ }),
40
+ page.data.description && /* @__PURE__ */ jsx("meta", {
41
+ property: "og:description",
42
+ content: page.data.description
43
+ }),
44
+ context.metaConfig?.page?.call(context, page),
45
+ context.data["core:page-meta"]?.map((hook, i) => /* @__PURE__ */ jsx(Fragment, { children: hook(page) }, i))
46
+ ] });
47
+ }
48
+ function getGitHubFileUrl(ctx, absolutePath) {
49
+ const { git } = ctx.siteConfig;
50
+ if (!git) return;
51
+ const p = path.relative(git.rootDir, absolutePath).replaceAll(path.sep, "/");
52
+ if (p.startsWith("../")) return;
53
+ return `https://github.com/${git.user}/${git.repo}/blob/${git.branch}/${p}`;
54
+ }
55
+ function baseOptions(ctx) {
56
+ const { name, git } = ctx.siteConfig;
57
+ return {
58
+ nav: { title: name },
59
+ githubUrl: git ? `https://github.com/${git.user}/${git.repo}` : void 0
60
+ };
61
+ }
62
+ function TransformChildrenSlot({ Comp, props, children }) {
63
+ if (props.children) for (const transformer of props.children) children = transformer(children);
64
+ return /* @__PURE__ */ jsx(Comp, {
65
+ ...props,
66
+ children
67
+ });
68
+ }
69
+ //#endregion
70
+ export { TransformChildrenSlot, baseOptions, getGitHubFileUrl, parseConfig, renderPageMeta, renderRootMeta };
@@ -0,0 +1,33 @@
1
+ import { AppContext } from "./shared.js";
2
+ import { ConfigContext } from "../config.js";
3
+ import { createPages } from "waku";
4
+ import { ReactNode } from "react";
5
+ import { StructuredData } from "fumadocs-core/mdx-plugins";
6
+ import { TOCItemType } from "fumadocs-core/toc";
7
+
8
+ //#region src/lib/types.d.ts
9
+ type Awaitable<T> = T | Promise<T>;
10
+ /** allow content sources to implement interfaces for pages, instead of requiring consumers to specify manually */
11
+ interface Adapter<C extends ConfigContext = ConfigContext> {
12
+ "core:get-text"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<string | undefined>;
13
+ "core:get-structured-data"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<StructuredData | undefined>;
14
+ "core:render-body"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<ReactNode>;
15
+ "core:render-toc"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<TOCItemType[] | undefined>;
16
+ }
17
+ interface ServerPlugin<C extends ConfigContext = ConfigContext> {
18
+ /** receive & modify context */
19
+ init?: (this: AppContext<C>) => void;
20
+ createPages?: (this: AppContext<C>, fns: RouteFns) => Awaitable<void>;
21
+ }
22
+ type RouteFns = Parameters<Parameters<typeof createPages>[0]>[0] & {
23
+ createApiIsomorphic: (config: {
24
+ render: "static" | "dynamic";
25
+ path: string;
26
+ staticPaths?: string[][];
27
+ handler: (req: Request, ctx: {
28
+ params: Record<string, string | string[]>;
29
+ }) => Promise<Response>;
30
+ }) => void;
31
+ };
32
+ //#endregion
33
+ export { Adapter, Awaitable, RouteFns, ServerPlugin };
@@ -0,0 +1,179 @@
1
+ import fsSync from "node:fs";
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ import { createRequire } from "node:module";
5
+ //#region src/lib/vitefu.ts
6
+ let pnp;
7
+ let pnpWorkspaceLocators = [];
8
+ if (process.versions.pnp) try {
9
+ pnp = createRequire(import.meta.url)("pnpapi");
10
+ pnpWorkspaceLocators = pnp.getDependencyTreeRoots();
11
+ } catch {}
12
+ async function crawlFrameworkPkgs(options) {
13
+ const pkgJsonPath = await findClosestPkgJsonPath(options.root);
14
+ if (!pkgJsonPath) return {
15
+ optimizeDeps: {
16
+ include: [],
17
+ exclude: []
18
+ },
19
+ ssr: {
20
+ noExternal: [],
21
+ external: []
22
+ }
23
+ };
24
+ const pkgJson = await readJson(pkgJsonPath).catch((e) => {
25
+ throw new Error(`Unable to read ${pkgJsonPath}`, { cause: e });
26
+ });
27
+ const optimizeDepsIncludeByPkgJsonPath = /* @__PURE__ */ new Map();
28
+ let optimizeDepsInclude = [];
29
+ let optimizeDepsExclude = [];
30
+ let ssrNoExternal = [];
31
+ let ssrExternal = [];
32
+ await crawl(pkgJsonPath, pkgJson);
33
+ optimizeDepsInclude = [...optimizeDepsIncludeByPkgJsonPath.values()];
34
+ if (options.viteUserConfig) {
35
+ const userOptimizeDepsExclude = options.viteUserConfig.optimizeDeps?.exclude;
36
+ if (userOptimizeDepsExclude) optimizeDepsInclude = optimizeDepsInclude.filter((dep) => !isDepExcluded(dep, userOptimizeDepsExclude));
37
+ const userOptimizeDepsInclude = options.viteUserConfig.optimizeDeps?.include;
38
+ if (userOptimizeDepsInclude) optimizeDepsExclude = optimizeDepsExclude.filter((dep) => !isDepIncluded(dep, userOptimizeDepsInclude));
39
+ const userSsrExternal = options.viteUserConfig.ssr?.external;
40
+ if (userSsrExternal) ssrNoExternal = ssrNoExternal.filter((dep) => !isDepExternaled(dep, userSsrExternal));
41
+ const userSsrNoExternal = options.viteUserConfig.ssr?.noExternal;
42
+ if (userSsrNoExternal) ssrExternal = ssrExternal.filter((dep) => !isDepNoExternaled(dep, userSsrNoExternal));
43
+ }
44
+ optimizeDepsInclude.sort();
45
+ optimizeDepsExclude.sort();
46
+ ssrExternal.sort();
47
+ ssrNoExternal.sort();
48
+ return {
49
+ optimizeDeps: {
50
+ include: optimizeDepsInclude,
51
+ exclude: optimizeDepsExclude
52
+ },
53
+ ssr: {
54
+ noExternal: ssrNoExternal,
55
+ external: ssrExternal
56
+ }
57
+ };
58
+ async function crawl(currentPkgJsonPath, currentPkgJson, parentDepNames = [], parentIsFrameworkPkg = false, hasFrameworkAncestor = false) {
59
+ const crawlDevDependencies = parentDepNames.length === 0 || isPrivateWorkspacePackage(currentPkgJsonPath, currentPkgJson, options.workspaceRoot);
60
+ const deps = [...Object.keys(currentPkgJson.dependencies ?? {}), ...crawlDevDependencies ? Object.keys(currentPkgJson.devDependencies ?? {}) : []].filter((dep) => !parentDepNames.includes(dep));
61
+ await Promise.all(deps.map(async (dep) => {
62
+ const frameworkByName = options.isFrameworkPkgByName?.(dep);
63
+ const semiFrameworkByName = options.isSemiFrameworkPkgByName?.(dep);
64
+ if (frameworkByName === false || semiFrameworkByName === false) return;
65
+ const depPkgJsonPath = await findDepPkgJsonPath(dep, currentPkgJsonPath, !!options.workspaceRoot);
66
+ if (!depPkgJsonPath) return;
67
+ const depPkgJson = await readJson(depPkgJsonPath).catch(() => {});
68
+ if (!depPkgJson) return;
69
+ const isFrameworkPkg = frameworkByName === true || options.isFrameworkPkgByJson?.(depPkgJson) === true;
70
+ const isSemiFrameworkPkg = semiFrameworkByName === true || options.isSemiFrameworkPkgByJson?.(depPkgJson) === true;
71
+ const depChain = parentDepNames.concat(dep);
72
+ if (isFrameworkPkg || isSemiFrameworkPkg) {
73
+ if (isFrameworkPkg) {
74
+ pushUnique(optimizeDepsExclude, dep);
75
+ pushUnique(ssrNoExternal, dep);
76
+ } else pushUnique(ssrNoExternal, dep);
77
+ await crawl(depPkgJsonPath, depPkgJson, depChain, true, true);
78
+ return;
79
+ }
80
+ if (!hasFrameworkAncestor) return;
81
+ if (await pkgNeedsOptimization(depPkgJson, depPkgJsonPath)) addOptimizedDep(depPkgJsonPath, depChain);
82
+ else await crawl(depPkgJsonPath, depPkgJson, depChain, false, true);
83
+ if (!options.isBuild && parentIsFrameworkPkg) pushUnique(ssrExternal, dep);
84
+ }));
85
+ }
86
+ function addOptimizedDep(depPkgJsonPath, depChain) {
87
+ const includePath = depChain.join(" > ");
88
+ const current = optimizeDepsIncludeByPkgJsonPath.get(depPkgJsonPath);
89
+ if (!current || compareDepChains(includePath, current) < 0) optimizeDepsIncludeByPkgJsonPath.set(depPkgJsonPath, includePath);
90
+ }
91
+ }
92
+ async function findClosestPkgJsonPath(dir, predicate) {
93
+ let currentDir = dir.endsWith("package.json") ? path.dirname(dir) : dir;
94
+ while (currentDir) {
95
+ const pkg = path.join(currentDir, "package.json");
96
+ try {
97
+ if ((await fs.stat(pkg)).isFile() && (!predicate || await predicate(pkg))) return pkg;
98
+ } catch {}
99
+ const nextDir = path.dirname(currentDir);
100
+ if (nextDir === currentDir) break;
101
+ currentDir = nextDir;
102
+ }
103
+ }
104
+ async function pkgNeedsOptimization(pkgJson, pkgJsonPath) {
105
+ if (pkgJson.module || pkgJson.exports) return false;
106
+ if (pkgJson.main) {
107
+ const entryExt = path.extname(pkgJson.main);
108
+ return !entryExt || entryExt === ".js" || entryExt === ".cjs";
109
+ }
110
+ try {
111
+ await fs.access(path.join(path.dirname(pkgJsonPath), "index.js"));
112
+ return true;
113
+ } catch {
114
+ return false;
115
+ }
116
+ }
117
+ async function findDepPkgJsonPath(dep, parent, usePnpWorkspaceLocators) {
118
+ if (pnp) {
119
+ if (usePnpWorkspaceLocators) try {
120
+ const locator = pnpWorkspaceLocators.find((root) => root.name === dep);
121
+ if (locator) {
122
+ const pkgPath = pnp.getPackageInformation(locator).packageLocation;
123
+ return path.resolve(pkgPath, "package.json");
124
+ }
125
+ } catch {}
126
+ try {
127
+ const depRoot = pnp.resolveToUnqualified(dep, parent);
128
+ if (!depRoot) return;
129
+ return path.join(depRoot, "package.json");
130
+ } catch {
131
+ return;
132
+ }
133
+ }
134
+ let root = parent;
135
+ while (root) {
136
+ const pkg = path.join(root, "node_modules", dep, "package.json");
137
+ try {
138
+ await fs.access(pkg);
139
+ return fsSync.realpathSync(pkg);
140
+ } catch {}
141
+ const nextRoot = path.dirname(root);
142
+ if (nextRoot === root) break;
143
+ root = nextRoot;
144
+ }
145
+ }
146
+ async function readJson(pkgJsonPath) {
147
+ return JSON.parse(await fs.readFile(pkgJsonPath, "utf8"));
148
+ }
149
+ function isPrivateWorkspacePackage(pkgJsonPath, pkgJson, workspaceRoot) {
150
+ return !!(workspaceRoot && pkgJson.private && !pkgJsonPath.match(/[/\\]node_modules[/\\]/) && !path.relative(workspaceRoot, pkgJsonPath).startsWith(".."));
151
+ }
152
+ function pushUnique(array, value) {
153
+ if (!array.includes(value)) array.push(value);
154
+ }
155
+ function compareDepChains(left, right) {
156
+ const leftDepth = left.split(" > ").length;
157
+ const rightDepth = right.split(" > ").length;
158
+ if (leftDepth !== rightDepth) return leftDepth - rightDepth;
159
+ return left.localeCompare(right);
160
+ }
161
+ function isDepIncluded(dep, optimizeDepsInclude) {
162
+ return optimizeDepsInclude.some((include) => dep === include);
163
+ }
164
+ function isDepExcluded(dep, optimizeDepsExclude) {
165
+ return optimizeDepsExclude.some((exclude) => dep === exclude || dep.startsWith(`${exclude} > `));
166
+ }
167
+ function isDepNoExternaled(dep, ssrNoExternal) {
168
+ if (typeof ssrNoExternal === "boolean") return ssrNoExternal;
169
+ return (Array.isArray(ssrNoExternal) ? ssrNoExternal : [ssrNoExternal]).some((noExternal) => {
170
+ if (typeof noExternal === "string") return dep === noExternal;
171
+ return noExternal.test(dep);
172
+ });
173
+ }
174
+ function isDepExternaled(dep, ssrExternal) {
175
+ if (typeof ssrExternal === "boolean") return ssrExternal;
176
+ return ssrExternal.some((external) => dep === external);
177
+ }
178
+ //#endregion
179
+ export { crawlFrameworkPkgs };
@@ -0,0 +1,14 @@
1
+ import { AppContext } from "../lib/shared.js";
2
+ import { ConfigContext } from "../config.js";
3
+ import { Awaitable, ServerPlugin } from "../lib/types.js";
4
+ import { Index } from "fumadocs-core/search/flexsearch";
5
+
6
+ //#region src/plugins/flexsearch.d.ts
7
+ interface FlexsearchOptions<C extends ConfigContext = ConfigContext> {
8
+ buildIndex?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<Index>;
9
+ }
10
+ declare function flexsearchPlugin<C extends ConfigContext = ConfigContext>({
11
+ buildIndex
12
+ }?: FlexsearchOptions<NoInfer<C>>): ServerPlugin<C>;
13
+ //#endregion
14
+ export { FlexsearchOptions, flexsearchPlugin };
@@ -0,0 +1,35 @@
1
+ //#region src/plugins/flexsearch.ts
2
+ function flexsearchPlugin({ buildIndex = async function buildIndexDefault(page) {
3
+ for (const adapter of this.adapters) {
4
+ const structuredData = await adapter["core:get-structured-data"]?.call(this, page);
5
+ if (structuredData !== void 0) return {
6
+ id: page.url,
7
+ title: page.data.title ?? page.path,
8
+ description: page.data.description,
9
+ url: page.url,
10
+ structuredData
11
+ };
12
+ }
13
+ throw new Error("[Fumapress] Please specify the `buildIndex` option to flexsearchPlugin()");
14
+ } } = {}) {
15
+ return {
16
+ init() {
17
+ if (this.mode === "static") (this.data["core:provider"] ??= []).push(async (props) => {
18
+ props.search ??= {};
19
+ props.search.SearchDialog ??= (await import("../components/flexsearch-static.js")).default;
20
+ return props;
21
+ });
22
+ },
23
+ async createPages({ createApiIsomorphic }) {
24
+ const { flexsearchFromSource } = await import("fumadocs-core/search/flexsearch");
25
+ const server = flexsearchFromSource(this.getLoader, { buildIndex: buildIndex.bind(this) });
26
+ createApiIsomorphic({
27
+ render: this.mode === "static" ? "static" : "dynamic",
28
+ path: "/api/search",
29
+ handler: this.mode === "static" ? server.staticGET : server.GET
30
+ });
31
+ }
32
+ };
33
+ }
34
+ //#endregion
35
+ export { flexsearchPlugin };
@@ -0,0 +1,11 @@
1
+ import { AppContext } from "../lib/shared.js";
2
+ import { ConfigContext } from "../config.js";
3
+ import { Awaitable, ServerPlugin } from "../lib/types.js";
4
+
5
+ //#region src/plugins/llms.txt.d.ts
6
+ interface LLMsOptions<C extends ConfigContext = ConfigContext> {
7
+ getLLMText?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<string>;
8
+ }
9
+ declare function llmsPlugin<C extends ConfigContext = ConfigContext>(options?: LLMsOptions<NoInfer<C>>): ServerPlugin<C>;
10
+ //#endregion
11
+ export { LLMsOptions, llmsPlugin };
@@ -0,0 +1,72 @@
1
+ import { unstable_notFound } from "waku/router/server";
2
+ import { llms } from "fumadocs-core/source/llms";
3
+ //#region src/plugins/llms.txt.ts
4
+ function llmsPlugin(options = {}) {
5
+ const { getLLMText: _getLLMText = async function getLLMTextDefault(page) {
6
+ for (const adapter of this.adapters) {
7
+ const txt = await adapter["core:get-text"]?.call(this, page);
8
+ if (txt !== void 0) return `# ${page.data.title} (${page.url})\n\n${txt}`;
9
+ }
10
+ throw new Error("[Fumapress] Please specify the `getLLMText()` option in llmsPlugin()");
11
+ } } = options;
12
+ return {
13
+ init() {
14
+ this.data["core:docs-layout"] ??= {};
15
+ this.data["core:docs-layout"].renderers ??= [];
16
+ this.data["core:docs-layout"].renderers.push(function(res) {
17
+ res.markdownUrl ??= slugsToMarkdownPath(this.page.slugs, this.page.locale).url;
18
+ return res;
19
+ });
20
+ },
21
+ async createPages({ createApiIsomorphic }) {
22
+ const defaultRenderMode = this.mode === "dynamic" ? "dynamic" : "static";
23
+ const getLLMText = _getLLMText.bind(this);
24
+ createApiIsomorphic({
25
+ render: defaultRenderMode,
26
+ path: "/llms.txt",
27
+ handler: async () => {
28
+ const source = await this.getLoader();
29
+ return new Response(llms(source).index());
30
+ }
31
+ });
32
+ createApiIsomorphic({
33
+ render: defaultRenderMode,
34
+ path: "/llms-full.txt",
35
+ handler: async () => {
36
+ const scan = (await this.getLoader()).getPages().map(getLLMText);
37
+ const scanned = await Promise.all(scan);
38
+ return new Response(scanned.join("\n\n"));
39
+ }
40
+ });
41
+ createApiIsomorphic({
42
+ render: defaultRenderMode,
43
+ path: this.i18nConfig ? "/[lang]/[...slugs]" : "/[...slugs]",
44
+ staticPaths: defaultRenderMode === "static" ? (await this.getLoader()).getPages().map((page) => slugsToMarkdownPath(page.slugs, page.locale).segments) : void 0,
45
+ handler: async (_req, { params }) => {
46
+ const page = (await this.getLoader()).getPage(markdownPathToSlugs(params.slugs), params.lang);
47
+ if (!page) unstable_notFound();
48
+ return new Response(await getLLMText(page), { headers: { "Content-Type": "text/markdown" } });
49
+ }
50
+ });
51
+ }
52
+ };
53
+ }
54
+ function markdownPathToSlugs(segs) {
55
+ const slugs = [...segs];
56
+ if (slugs.length === 0) return slugs;
57
+ slugs[slugs.length - 1] = slugs[slugs.length - 1].replace(/\.md$/, "");
58
+ if (slugs.length === 1 && slugs[0] === "index") slugs.pop();
59
+ return slugs;
60
+ }
61
+ function slugsToMarkdownPath(slugs, lang) {
62
+ const segments = [...slugs];
63
+ if (segments.length === 0) segments.push("index.md");
64
+ else segments[segments.length - 1] += ".md";
65
+ if (lang) segments.unshift(lang);
66
+ return {
67
+ segments,
68
+ url: `/${segments.join("/")}`
69
+ };
70
+ }
71
+ //#endregion
72
+ export { llmsPlugin };
@@ -0,0 +1,14 @@
1
+ import { AppContext } from "../lib/shared.js";
2
+ import { ConfigContext } from "../config.js";
3
+ import { Awaitable, ServerPlugin } from "../lib/types.js";
4
+ import { AdvancedIndex } from "fumadocs-core/search/server";
5
+
6
+ //#region src/plugins/orama-search.d.ts
7
+ interface OramaSearchOptions<C extends ConfigContext = ConfigContext> {
8
+ buildIndex?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<AdvancedIndex>;
9
+ }
10
+ declare function oramaSearchPlugin<C extends ConfigContext = ConfigContext>({
11
+ buildIndex
12
+ }?: OramaSearchOptions<NoInfer<C>>): ServerPlugin<C>;
13
+ //#endregion
14
+ export { OramaSearchOptions, oramaSearchPlugin };
@@ -0,0 +1,35 @@
1
+ //#region src/plugins/orama-search.ts
2
+ function oramaSearchPlugin({ buildIndex = async function buildIndexDefault(page) {
3
+ for (const adapter of this.adapters) {
4
+ const structuredData = await adapter["core:get-structured-data"]?.call(this, page);
5
+ if (structuredData !== void 0) return {
6
+ id: page.url,
7
+ title: page.data.title ?? page.path,
8
+ description: page.data.description,
9
+ url: page.url,
10
+ structuredData
11
+ };
12
+ }
13
+ throw new Error("[Fumapress] Please specify the `buildIndex` option to oramaSearchPlugin()");
14
+ } } = {}) {
15
+ return {
16
+ init() {
17
+ if (this.mode === "static") (this.data["core:provider"] ??= []).push(async (props) => {
18
+ props.search ??= {};
19
+ props.search.SearchDialog ??= (await import("../components/orama-search-static.js")).default;
20
+ return props;
21
+ });
22
+ },
23
+ async createPages({ createApiIsomorphic }) {
24
+ const { createFromSource } = await import("fumadocs-core/search/server");
25
+ const server = createFromSource(this.getLoader, { buildIndex: buildIndex.bind(this) });
26
+ createApiIsomorphic({
27
+ render: this.mode === "static" ? "static" : "dynamic",
28
+ path: "/api/search",
29
+ handler: this.mode === "static" ? server.staticGET : server.GET
30
+ });
31
+ }
32
+ };
33
+ }
34
+ //#endregion
35
+ export { oramaSearchPlugin };
@@ -0,0 +1,16 @@
1
+ import { AppContext } from "../lib/shared.js";
2
+ import { ConfigContext } from "../config.js";
3
+ import { Awaitable, ServerPlugin } from "../lib/types.js";
4
+ import { ReactNode } from "react";
5
+ import { ImageResponseOptions } from "@takumi-rs/image-response";
6
+
7
+ //#region src/plugins/takumi.d.ts
8
+ interface TakumiOptions<C extends ConfigContext = ConfigContext> {
9
+ generate?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<{
10
+ node: ReactNode;
11
+ options?: Partial<ImageResponseOptions>;
12
+ }>;
13
+ }
14
+ declare function takumiPlugin<C extends ConfigContext = ConfigContext>(options?: TakumiOptions<NoInfer<C>>): ServerPlugin<C>;
15
+ //#endregion
16
+ export { TakumiOptions, takumiPlugin };
@@ -0,0 +1,79 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { unstable_notFound } from "waku/router/server";
3
+ import { ImageResponse } from "@takumi-rs/image-response";
4
+ //#region src/plugins/takumi.tsx
5
+ function takumiPlugin(options = {}) {
6
+ const { generate = async function generateDefault(page) {
7
+ const { generate } = await import("fumadocs-ui/og/takumi");
8
+ return { node: generate({
9
+ title: page.data.title,
10
+ description: page.data.description,
11
+ site: this.siteConfig.name
12
+ }) };
13
+ } } = options;
14
+ const width = 1200;
15
+ const height = 630;
16
+ return {
17
+ init() {
18
+ (this.data["core:page-meta"] ??= []).push((page) => {
19
+ const pathname = slugsToImagePath(page.slugs, page.locale).url;
20
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
21
+ /* @__PURE__ */ jsx("meta", {
22
+ property: "og:image",
23
+ content: this.siteConfig.baseUrl ? new URL(pathname, this.siteConfig.baseUrl).href : pathname
24
+ }),
25
+ /* @__PURE__ */ jsx("meta", {
26
+ property: "og:image:width",
27
+ content: `${width}`
28
+ }),
29
+ /* @__PURE__ */ jsx("meta", {
30
+ property: "og:image:height",
31
+ content: `${height}`
32
+ }),
33
+ /* @__PURE__ */ jsx("meta", {
34
+ property: "twitter:card",
35
+ content: "summary_large_image"
36
+ })
37
+ ] });
38
+ });
39
+ },
40
+ async createPages({ createApiIsomorphic }) {
41
+ const renderMode = this.mode === "dynamic" ? "dynamic" : "static";
42
+ createApiIsomorphic({
43
+ render: renderMode,
44
+ path: this.i18nConfig ? "/[lang]/[...slugs]" : "/[...slugs]",
45
+ staticPaths: renderMode === "static" ? (await this.getLoader()).getPages().map((page) => slugsToImagePath(page.slugs, page.locale).segments) : void 0,
46
+ handler: async (_, { params }) => {
47
+ const page = (await this.getLoader()).getPage(imagePathToSlugs(params.slugs), params.lang);
48
+ if (!page) unstable_notFound();
49
+ const { node, options } = await generate.call(this, page);
50
+ return new ImageResponse(node, {
51
+ width,
52
+ height,
53
+ ...options,
54
+ format: "webp"
55
+ });
56
+ }
57
+ });
58
+ }
59
+ };
60
+ }
61
+ function slugsToImagePath(slugs, lang) {
62
+ const segments = [...slugs];
63
+ if (segments.length === 0) segments.push("index.webp");
64
+ else segments[segments.length - 1] += ".webp";
65
+ if (lang) segments.unshift(lang);
66
+ return {
67
+ segments,
68
+ url: `/${segments.join("/")}`
69
+ };
70
+ }
71
+ function imagePathToSlugs(segs) {
72
+ const slugs = [...segs];
73
+ if (slugs.length === 0) return slugs;
74
+ slugs[slugs.length - 1] = slugs[slugs.length - 1].replace(/\.webp$/, "");
75
+ if (slugs.length === 1 && slugs[0] === "index") slugs.pop();
76
+ return slugs;
77
+ }
78
+ //#endregion
79
+ export { takumiPlugin };