fumapress 0.5.0 → 0.5.2

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.
Files changed (63) hide show
  1. package/css/generated.css +217 -3
  2. package/dist/adapters/mdx.d.ts +1 -1
  3. package/dist/client.d.ts +2 -23
  4. package/dist/client.js +3 -9
  5. package/dist/components/blog.js +4 -4
  6. package/dist/components/image.d.ts +30 -0
  7. package/dist/components/image.js +143 -0
  8. package/dist/components/link.d.ts +25 -0
  9. package/dist/components/link.js +18 -0
  10. package/dist/components/provider.d.ts +6 -0
  11. package/dist/components/provider.js +39 -0
  12. package/dist/config.d.ts +73 -30
  13. package/dist/config.js +14 -7
  14. package/dist/image.d.ts +2 -0
  15. package/dist/image.js +2 -0
  16. package/dist/index.d.ts +2 -2
  17. package/dist/layouts/blog.d.ts +1 -1
  18. package/dist/layouts/blog.index.js +3 -3
  19. package/dist/layouts/blog.js +3 -3
  20. package/dist/layouts/blog.tags.js +2 -2
  21. package/dist/layouts/docs.d.ts +2 -2
  22. package/dist/layouts/home.d.ts +1 -1
  23. package/dist/layouts/notebook.d.ts +2 -2
  24. package/dist/layouts/root.d.ts +5 -3
  25. package/dist/layouts/root.js +13 -16
  26. package/dist/layouts/switch.d.ts +2 -2
  27. package/dist/lib/pathname.js +14 -0
  28. package/dist/lib/shared.d.ts +10 -15
  29. package/dist/lib/shared.js +68 -33
  30. package/dist/lib/types.d.ts +30 -16
  31. package/dist/node_modules/.pnpm/http-cache-semantics@4.2.0/node_modules/http-cache-semantics/index.js +596 -0
  32. package/dist/plugins/blog.d.ts +3 -6
  33. package/dist/plugins/blog.js +6 -6
  34. package/dist/plugins/flexsearch.d.ts +1 -1
  35. package/dist/plugins/image/self-hosted.client.js +40 -0
  36. package/dist/plugins/image/self-hosted.d.ts +29 -0
  37. package/dist/plugins/image/self-hosted.js +30 -0
  38. package/dist/plugins/image/self-hosted.utils.js +270 -0
  39. package/dist/plugins/image/vercel.client.js +34 -0
  40. package/dist/plugins/image/vercel.d.ts +39 -0
  41. package/dist/plugins/image/vercel.enhancer.d.ts +10 -0
  42. package/dist/plugins/image/vercel.enhancer.js +16 -0
  43. package/dist/plugins/image/vercel.js +30 -0
  44. package/dist/plugins/image/vercel.utils.d.ts +10 -0
  45. package/dist/plugins/image/vercel.utils.js +30 -0
  46. package/dist/plugins/link-validation.d.ts +24 -0
  47. package/dist/plugins/link-validation.js +30 -0
  48. package/dist/plugins/llms.txt.d.ts +1 -1
  49. package/dist/plugins/llms.txt.js +2 -2
  50. package/dist/plugins/openapi.d.ts +2 -0
  51. package/dist/plugins/openapi.js +8 -1
  52. package/dist/plugins/orama-search.d.ts +1 -1
  53. package/dist/plugins/sitemap.d.ts +196 -0
  54. package/dist/plugins/sitemap.js +90 -0
  55. package/dist/plugins/takumi.d.ts +1 -1
  56. package/dist/plugins/takumi.js +3 -4
  57. package/dist/router/fs.js +1 -1
  58. package/dist/router/index.d.ts +17 -0
  59. package/dist/{router.js → router/index.js} +41 -26
  60. package/dist/vite.js +10 -10
  61. package/package.json +27 -9
  62. package/dist/lib/join-pathname.js +0 -9
  63. package/dist/router.d.ts +0 -15
@@ -0,0 +1,14 @@
1
+ //#region src/lib/pathname.ts
2
+ function joinPathname(...paths) {
3
+ let combined = paths.join("/").replaceAll(/\/+/g, "/");
4
+ if (!combined.startsWith("/")) combined = "/" + combined;
5
+ if (combined.endsWith("/")) combined = combined.slice(0, -1);
6
+ return combined;
7
+ }
8
+ const PATHNAME_SEGMENT_REGEX = /^[A-Za-z0-9\-._~!$&'()*+,;=:@]+$/;
9
+ /** Check if the string is a full pathname (one that does not include `.` or `..`) */
10
+ function isPlainPathname(s) {
11
+ return s.startsWith("/") && s.slice(1).split("/").every((seg) => seg !== "." && seg !== ".." && PATHNAME_SEGMENT_REGEX.test(seg));
12
+ }
13
+ //#endregion
14
+ export { isPlainPathname, joinPathname };
@@ -1,30 +1,28 @@
1
- import { BuildMode, ConfigContext, Layouts, MetaConfig } from "../config.js";
1
+ import { BaseConfig, BuildMode, ConfigContext, Layouts } from "../config.js";
2
2
  import { Adapter, AppContextData, Awaitable, ServerPlugin } from "./types.js";
3
- import { LoaderOutput } from "fumadocs-core/source";
4
3
  import { ReactNode } from "react";
4
+ import { LoaderOutput } from "fumadocs-core/source";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
- import { I18nConfig, SingularTranslationsAPI, TranslationsAPI } from "fumadocs-core/i18n";
7
- import { Translations } from "fumadocs-ui/i18n";
8
6
  //#region src/lib/shared.d.ts
9
7
  interface AppContext<C extends ConfigContext = ConfigContext> {
10
8
  mode: BuildMode;
11
- getLoader: () => Awaitable<LoaderOutput<C["loaderConfig"]>>;
9
+ getLoader: () => Awaitable<LoaderOutput<C>>;
12
10
  plugins: ServerPlugin<C>[];
13
11
  adapters: Adapter<C>[];
14
12
  layouts: Layouts<C>;
13
+ /** revalidate [dynamic content sources](https://fumadocs.dev/docs/headless/source-api/source#dynamic-source) */
14
+ revalidateLoader: (() => Promise<void>) | (C["source"] extends string ? (name: C["source"]) => Promise<void> : never);
15
+ /** invalidate [dynamic content sources](https://fumadocs.dev/docs/headless/source-api/source#dynamic-source) */
16
+ invalidateLoader: (() => void) | (C["source"] extends string ? (name: C["source"]) => void : never);
15
17
  /** always `undefined`, easier way to infer types */
16
18
  $context: C;
17
19
  /**
18
20
  * custom data in app context, can be referenced from plugins/pages etc
19
21
  */
20
22
  data: AppContextData & Record<string, unknown>;
21
- i18nConfig?: I18nConfig<C["lang"]>;
22
- translationsConfig?: TranslationsAPI<C["lang"], {
23
- ui: Translations;
24
- }> | SingularTranslationsAPI<{
25
- ui: Translations;
26
- }>;
27
- metaConfig?: MetaConfig<C>;
23
+ translationsConfig?: BaseConfig<C>["translations"];
24
+ i18nConfig?: C["i18n"];
25
+ metaConfig?: BaseConfig<C>["meta"];
28
26
  siteConfig: {
29
27
  name: string;
30
28
  baseUrl?: string;
@@ -36,9 +34,6 @@ interface AppContext<C extends ConfigContext = ConfigContext> {
36
34
  };
37
35
  };
38
36
  }
39
- declare global {
40
- var appContextTemp: AppContext | undefined;
41
- }
42
37
  declare function getPressContext<C extends ConfigContext = ConfigContext>(): AppContext<C>;
43
38
  type TransformChildren<T> = Omit<T, "children"> & {
44
39
  children?: ((nodes: ReactNode) => ReactNode)[];
@@ -3,63 +3,98 @@ import { getGitRootDir } from "./fs.js";
3
3
  import { require_deepmerge } from "../node_modules/.pnpm/@fastify_deepmerge@3.2.1/node_modules/@fastify/deepmerge/index.js";
4
4
  import { disableSearchPlugin } from "../plugins/internal/disable-search.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { Fragment as Fragment$1, isValidElement } from "react";
6
7
  import path from "node:path";
7
8
  import { loader } from "fumadocs-core/source";
8
- import { Fragment as Fragment$1, isValidElement } from "react";
9
9
  import { AsyncLocalStorage } from "node:async_hooks";
10
+ import { dynamicLoader } from "fumadocs-core/source/dynamic";
10
11
  //#region src/lib/shared.tsx
11
12
  var import_deepmerge = /* @__PURE__ */ __toESM(require_deepmerge());
12
13
  const appContext = new AsyncLocalStorage({ name: "fumapress:core" });
13
14
  function getPressContext() {
14
- let store = appContext.getStore();
15
- if (!store) store = global.appContextTemp;
16
- else delete global.appContextTemp;
15
+ const store = appContext.getStore();
17
16
  if (!store) throw new Error("[Fumapress] Missing server context for Fumapress, make sure to use the middlewares from createRouter()");
18
17
  return store;
19
18
  }
20
- async function parseConfig(config) {
21
- const ORDER = {
22
- pre: -1,
23
- post: 1,
24
- _: 0
25
- };
26
- let EMPTY;
27
- function resolvePlugins(plugins) {
28
- const flat = plugins.flat(Infinity);
29
- flat.push(disableSearchPlugin());
30
- return flat.sort((a, b) => ORDER[a.enforce ?? "_"] - ORDER[b.enforce ?? "_"]);
19
+ const PLUGIN_ORDER = {
20
+ pre: -1,
21
+ post: 1,
22
+ _: 0
23
+ };
24
+ function flattenPlugins(plugins) {
25
+ const out = [];
26
+ for (const plugin of plugins) {
27
+ if (!plugin) continue;
28
+ if (Array.isArray(plugin)) out.push(...flattenPlugins(plugin));
29
+ else out.push(plugin);
31
30
  }
32
- const layouts = config.getLayouts();
33
- return {
31
+ return out;
32
+ }
33
+ function resolvePlugins(plugins) {
34
+ return flattenPlugins(plugins).sort((a, b) => PLUGIN_ORDER[a.enforce ?? "_"] - PLUGIN_ORDER[b.enforce ?? "_"]);
35
+ }
36
+ async function initApp(builder) {
37
+ const config = builder.get();
38
+ const { translations, site, mode = "default", layouts } = config;
39
+ const plugins = resolvePlugins([...config.plugins, disableSearchPlugin()]);
40
+ const ctx = {
41
+ $context: void 0,
34
42
  getLoader() {
35
- if (typeof config.loader === "function") return config.loader();
36
- if (config.loader) return config.loader;
37
- console.warn("[Fumapress] loader is not specified in your config, is it a mistake?");
38
- return EMPTY ??= loader({}, { baseUrl: "/" });
43
+ throw new Error("[Fumapress] Content loader is not initialized yet, please access it after init()");
39
44
  },
45
+ revalidateLoader: () => Promise.resolve(void 0),
46
+ invalidateLoader: () => void 0,
47
+ i18nConfig: translations && "config" in translations ? translations.config : void 0,
40
48
  layouts: {
41
49
  ...layouts,
42
50
  root: layouts.root ?? (await import("../layouts/root.js")).createRootLayout(),
43
51
  page: layouts.page ?? (await import("../layouts/docs.js")).createDocsLayoutPage(),
44
52
  notFound: layouts.notFound ?? (await import("fumadocs-ui/layouts/home/not-found")).DefaultNotFound
45
53
  },
46
- plugins: resolvePlugins(config.getPlugins()),
47
- adapters: config.getAdapters(),
48
- $context: void 0,
54
+ plugins,
55
+ adapters: config.adapters,
49
56
  data: {},
50
- i18nConfig: config.i18n ?? (config.translations && "config" in config.translations ? config.translations.config : void 0),
51
- translationsConfig: config.translations,
52
- mode: config.mode ?? "default",
57
+ translationsConfig: translations,
58
+ mode,
53
59
  metaConfig: config.meta,
54
60
  siteConfig: {
55
- name: config.site?.name ?? "Fumapress",
56
- baseUrl: config.site?.baseUrl ?? getDefaultBaseUrl(),
57
- git: config.site?.git ? {
58
- ...config.site.git,
59
- rootDir: config.site.git.rootDir ?? getGitRootDir() ?? process.cwd()
61
+ name: site?.name ?? "Fumapress",
62
+ baseUrl: site?.baseUrl ?? getDefaultBaseUrl(),
63
+ git: site?.git ? {
64
+ ...site.git,
65
+ rootDir: site.git.rootDir ?? getGitRootDir() ?? process.cwd()
60
66
  } : void 0
61
67
  }
62
68
  };
69
+ if ("loader" in config && config.loader) {
70
+ ctx.i18nConfig ??= config.loader._i18n;
71
+ ctx.getLoader = () => config.loader;
72
+ } else if ("content" in config) ctx.i18nConfig ??= config.i18n;
73
+ else {
74
+ console.warn("[Fumapress] loader is not specified in your config, is it a mistake?");
75
+ const emptyLoader = loader({}, { baseUrl: "/" });
76
+ ctx.getLoader = () => emptyLoader;
77
+ }
78
+ for (const plugin of plugins) await plugin.init?.call(ctx);
79
+ if ("content" in config) {
80
+ let loaderOptions = {
81
+ baseUrl: "/",
82
+ i18n: ctx.i18nConfig,
83
+ ...config.loaderOptions
84
+ };
85
+ for (const plugin of plugins) {
86
+ if (!plugin.configureLoader) continue;
87
+ loaderOptions = await plugin.configureLoader.call(ctx, loaderOptions);
88
+ }
89
+ const source = dynamicLoader(config.content, loaderOptions);
90
+ ctx.revalidateLoader = source.revalidate.bind(source);
91
+ ctx.invalidateLoader = source.invalidate.bind(source);
92
+ ctx.getLoader = () => {
93
+ if (config.loaderOptions?.alwaysRevalidate) source.invalidate();
94
+ return source.get();
95
+ };
96
+ }
97
+ return ctx;
63
98
  }
64
99
  function getDefaultBaseUrl() {
65
100
  console.warn("[Fumapress] It is recommended to specify \"site.baseUrl\" in your config for better SEO.");
@@ -140,4 +175,4 @@ const mergeLayoutConfigs = (0, import_deepmerge.default)({
140
175
  }
141
176
  });
142
177
  //#endregion
143
- export { appContext, baseLayoutProps, createTransformChildren, getCreationDate, getGitHubFileUrl, getLastModifiedDate, getPressContext, mergeLayoutConfigs, parseConfig, renderBody, renderPageMeta, renderRootMeta, renderToc };
178
+ export { appContext, baseLayoutProps, createTransformChildren, getCreationDate, getGitHubFileUrl, getLastModifiedDate, getPressContext, initApp, mergeLayoutConfigs, renderBody, renderPageMeta, renderRootMeta, renderToc };
@@ -3,25 +3,31 @@ import { ConfigContext } from "../config.js";
3
3
  import { DocsLayoutContextData } from "../layouts/docs.js";
4
4
  import { HomeLayoutContextData } from "../layouts/home.js";
5
5
  import { NotebookLayoutContextData } from "../layouts/notebook.js";
6
- import { Page } from "fumadocs-core/source";
6
+ import { RootLayoutContextData } from "../layouts/root.js";
7
7
  import { ReactNode } from "react";
8
- import { CreateApi, CreateLayout, CreatePage, CreateRoot, CreateSlice } from "waku/router/server";
8
+ import { ContentStorage, LoaderOptions, LoaderPluginOption, Page } from "fumadocs-core/source";
9
+ import { I18nConfig } from "fumadocs-core/i18n";
10
+ import { CreateApi, CreateLayout, CreatePage, CreateRoot, CreateSlice, createPages } from "waku/router/server";
9
11
  import { TOCItemType } from "fumadocs-core/toc";
10
12
  import { StructuredData } from "fumadocs-core/mdx-plugins";
11
- import { RootProviderProps } from "fumadocs-ui/provider/base";
12
13
  import { MiddlewareHandler } from "hono";
14
+ import { unstable_createServerEntryAdapter } from "waku/adapter-builders";
13
15
 
14
16
  //#region src/lib/types.d.ts
15
17
  type Awaitable<T> = T | Promise<T>;
16
18
  /** allow content sources to implement interfaces for pages, instead of requiring consumers to specify manually */
17
19
  interface Adapter<C extends ConfigContext = ConfigContext> {
18
- "core:get-text"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<string | undefined>;
19
- "core:get-structured-data"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<StructuredData | undefined>;
20
- "core:render-body"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<ReactNode>;
21
- "core:render-toc"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<TOCItemType[] | undefined>;
22
- "core:get-creation-date"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<Date | undefined>;
23
- "core:get-modified-date"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<Date | undefined>;
24
- "blog:get-tags"?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<string[] | undefined>;
20
+ "core:get-text"?: (this: AppContext<C>, page: C["page"]) => Awaitable<string | undefined>;
21
+ "core:get-structured-data"?: (this: AppContext<C>, page: C["page"]) => Awaitable<StructuredData | undefined>;
22
+ "core:render-body"?: (this: AppContext<C>, page: C["page"]) => Awaitable<ReactNode>;
23
+ "core:render-toc"?: (this: AppContext<C>, page: C["page"]) => Awaitable<TOCItemType[] | undefined>;
24
+ "core:get-creation-date"?: (this: AppContext<C>, page: C["page"]) => Awaitable<Date | undefined>;
25
+ "core:get-modified-date"?: (this: AppContext<C>, page: C["page"]) => Awaitable<Date | undefined>;
26
+ "blog:get-tags"?: (this: AppContext<C>, page: C["page"]) => Awaitable<string[] | undefined>;
27
+ }
28
+ /** make plugins an array for easier modification */
29
+ interface PressLoaderOptions<S extends ContentStorage = ContentStorage, I18n extends I18nConfig | undefined = I18nConfig | undefined> extends Omit<LoaderOptions<S, I18n>, "plugins"> {
30
+ plugins?: LoaderPluginOption[];
25
31
  }
26
32
  interface ServerPlugin<C extends ConfigContext = ConfigContext> {
27
33
  name?: string;
@@ -37,17 +43,22 @@ interface ServerPlugin<C extends ConfigContext = ConfigContext> {
37
43
  * - `false`: render not found (will also exclude from static pre-rendering).
38
44
  * - `undefined`: fallback to default.
39
45
  */
40
- resolvePage?: (this: AppContext<C>, page: C["loaderConfig"]["page"]) => Awaitable<C["loaderConfig"]["page"] | false | undefined>;
46
+ resolvePage?: (this: AppContext<C>, page: C["page"]) => Awaitable<C["page"] | false | undefined>;
41
47
  /**
42
48
  * Override the page renderer, use default fallback if `undefined` is returned.
43
49
  */
44
50
  renderPage?: (this: AppContext<C>, env: {
45
- page: C["loaderConfig"]["page"];
51
+ page: C["page"];
46
52
  fallback: ReactNode;
47
53
  lang?: string;
48
54
  slugs: string[];
49
55
  }) => Awaitable<ReactNode>;
50
- createMiddlewares?: (this: AppContext<C>) => MiddlewareHandler[] | undefined;
56
+ /** resolve content loader options */
57
+ configureLoader?: (this: AppContext<C>, options: PressLoaderOptions) => Awaitable<PressLoaderOptions>;
58
+ /** create Hono middlewares */
59
+ createMiddlewares?: (this: AppContext<C>) => Awaitable<MiddlewareHandler[] | undefined>;
60
+ unstable_onSSGRequest?: <T>(req: Request, next: () => T) => T;
61
+ unstable_onServerEntry?: (entry: ReturnType<ReturnType<typeof unstable_createServerEntryAdapter>>) => ReturnType<ReturnType<typeof unstable_createServerEntryAdapter>>;
51
62
  }
52
63
  interface BaseRouteFns {
53
64
  createPage: CreatePage;
@@ -56,6 +67,7 @@ interface BaseRouteFns {
56
67
  createApi: CreateApi;
57
68
  createSlice: CreateSlice;
58
69
  }
70
+ type CreatePagesResult = ReturnType<typeof createPages>;
59
71
  interface RouteFns extends BaseRouteFns {
60
72
  createApiIsomorphic: (config: {
61
73
  render: "static" | "dynamic";
@@ -65,15 +77,17 @@ interface RouteFns extends BaseRouteFns {
65
77
  params: Record<string, string | string[]>;
66
78
  }) => Promise<Response>;
67
79
  }) => void;
80
+ /** access `createPages()` output */
81
+ unstable_getCreated: () => CreatePagesResult;
68
82
  }
69
- type ServerPluginOption<C extends ConfigContext = ConfigContext> = ServerPlugin<C> | ServerPluginOption<C>[];
83
+ type ServerPluginOption<C extends ConfigContext = ConfigContext> = ServerPlugin<C> | false | undefined | null | ServerPluginOption<C>[];
70
84
  /** can be extended from other libraries */
71
85
  interface AppContextData {
72
86
  "core:page-meta"?: ((page: Page) => ReactNode)[];
73
87
  "core:notebook-layout"?: NotebookLayoutContextData;
74
88
  "core:docs-layout"?: DocsLayoutContextData;
75
89
  "core:home-layout"?: HomeLayoutContextData;
76
- "core:provider"?: ((props: RootProviderProps) => Awaitable<RootProviderProps>)[];
90
+ "core:provider"?: RootLayoutContextData;
77
91
  }
78
92
  /**
79
93
  * For file-system router, route files can export a `getConfig()` function that returns a `RouteConfig` object.
@@ -88,4 +102,4 @@ interface RouteConfig {
88
102
  autoI18n?: boolean;
89
103
  }
90
104
  //#endregion
91
- export { Adapter, AppContextData, Awaitable, RouteConfig, RouteFns, ServerPlugin, ServerPluginOption };
105
+ export { Adapter, AppContextData, Awaitable, PressLoaderOptions, RouteConfig, RouteFns, ServerPlugin, ServerPluginOption };