fumadocs-mdx 9.0.4 → 10.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/index.mjs CHANGED
@@ -1,111 +1,73 @@
1
- // src/resolve-files.ts
1
+ // src/runtime/resolve-files.ts
2
2
  import path from "node:path";
3
-
4
- // src/utils/schema.ts
5
- import { z } from "zod";
6
- var metaSchema = z.object({
7
- title: z.string().optional(),
8
- pages: z.array(z.string()).optional(),
9
- root: z.boolean().optional(),
10
- defaultOpen: z.boolean().optional(),
11
- icon: z.string().optional()
12
- });
13
- var frontmatterSchema = z.object({
14
- title: z.string(),
15
- description: z.string().optional(),
16
- icon: z.string().optional(),
17
- full: z.boolean().optional(),
18
- // Fumadocs OpenAPI generated
19
- _openapi: z.object({}).passthrough().optional()
20
- });
21
- var defaultSchemas = {
22
- frontmatter: frontmatterSchema,
23
- meta: metaSchema
24
- };
25
-
26
- // src/resolve-files.ts
27
3
  var pageTypes = [".md", ".mdx"];
28
- var metaTypes = [".json"];
29
- var DataError = class extends Error {
30
- constructor(name, error) {
31
- const info = error.flatten();
32
- super(
33
- `${name}: ${JSON.stringify(
34
- {
35
- root: info.formErrors,
36
- ...info.fieldErrors
37
- },
38
- null,
39
- 2
40
- )}`
41
- );
42
- this.name = "DataError";
43
- }
44
- };
45
- function parse(schema, object, errorName) {
46
- const result = schema.safeParse(object);
47
- if (!result.success) {
48
- throw new DataError(errorName, result.error);
49
- }
50
- return result.data;
51
- }
4
+ var metaTypes = [".json", ".yaml"];
52
5
  function resolveFiles({
53
- map,
54
- rootDir = "",
55
- schema = {}
6
+ docs,
7
+ meta,
8
+ rootDir = ""
56
9
  }) {
57
10
  const outputs = [];
58
- for (const [file, value] of Object.entries(map)) {
59
- if (!file.startsWith(rootDir)) continue;
60
- const parsed = path.parse(file);
61
- if (metaTypes.includes(parsed.ext)) {
11
+ for (const entry of docs) {
12
+ if (!entry._file.path.startsWith(rootDir)) continue;
13
+ const ext = path.extname(entry._file.path);
14
+ if (pageTypes.includes(ext)) {
62
15
  outputs.push({
63
- type: "meta",
64
- path: file,
65
- data: parse(
66
- schema.meta ?? defaultSchemas.meta,
67
- value,
68
- `Invalid meta file in ${file}`
69
- )
16
+ type: "page",
17
+ path: entry._file.path,
18
+ data: entry
70
19
  });
71
20
  continue;
72
21
  }
73
- if (pageTypes.includes(parsed.ext)) {
74
- const { frontmatter, ...data } = value;
75
- const parsedFrontmatter = parse(
76
- schema.frontmatter ?? defaultSchemas.frontmatter,
77
- frontmatter,
78
- `Invalid Frontmatter in ${file}`
79
- );
22
+ console.warn(
23
+ `Unknown Type: ${ext} on ${entry._file.path}, expected: ${pageTypes.toString()}`
24
+ );
25
+ }
26
+ for (const entry of meta) {
27
+ if (!entry._file.path.startsWith(rootDir)) continue;
28
+ const ext = path.extname(entry._file.path);
29
+ if (metaTypes.includes(ext)) {
80
30
  outputs.push({
81
- type: "page",
82
- path: file,
83
- data: {
84
- ...parsedFrontmatter,
85
- exports: data
86
- }
31
+ type: "meta",
32
+ path: entry._file.path,
33
+ data: entry
87
34
  });
88
35
  continue;
89
36
  }
90
- console.warn("Unknown Type:", parsed.ext);
37
+ console.warn(
38
+ `Unknown Type: ${ext} on ${entry._file.path}, expected: ${metaTypes.toString()}`
39
+ );
91
40
  }
92
41
  return outputs;
93
42
  }
94
43
 
95
- // src/create.ts
96
- function createMDXSource(map, options) {
44
+ // src/runtime/create.ts
45
+ function toRuntime(type, file, info) {
46
+ if (type === "doc") {
47
+ const { default: body, frontmatter, ...exports } = file;
48
+ return {
49
+ body,
50
+ ...exports,
51
+ ...frontmatter,
52
+ _exports: file,
53
+ _file: info
54
+ };
55
+ }
97
56
  return {
98
- files: (rootDir) => loadMDXSource(map, { ...options, rootDir })
57
+ ...file.default,
58
+ _file: info
99
59
  };
100
60
  }
101
- function loadMDXSource(map, options) {
102
- return resolveFiles({
103
- map,
104
- ...options
105
- });
61
+ function createMDXSource(docs, meta) {
62
+ return {
63
+ files: (rootDir) => resolveFiles({
64
+ docs,
65
+ meta,
66
+ rootDir
67
+ })
68
+ };
106
69
  }
107
70
  export {
108
71
  createMDXSource,
109
- defaultSchemas,
110
- loadMDXSource
72
+ toRuntime
111
73
  };
@@ -1,20 +1,20 @@
1
- import { ProcessorOptions } from '@mdx-js/mdx';
2
1
  import { LoaderContext } from 'webpack';
2
+ import { StructuredData } from 'fumadocs-core/mdx-plugins';
3
3
 
4
- interface Options extends ProcessorOptions {
4
+ interface Options {
5
5
  /**
6
- * Fetch last modified time with specified version control
7
- * @defaultValue 'none'
6
+ * @internal
8
7
  */
9
- lastModifiedTime?: 'git' | 'none';
8
+ _ctx: {
9
+ configPath: string;
10
+ };
10
11
  }
11
- interface InternalBuildInfo {
12
- __fumadocs?: {
13
- path: string;
14
- /**
15
- * `vfile.data` parsed from file
16
- */
17
- data: unknown;
12
+ interface MetaFile {
13
+ path: string;
14
+ data: {
15
+ frontmatter: Record<string, unknown>;
16
+ structuredData?: StructuredData;
17
+ [key: string]: unknown;
18
18
  };
19
19
  }
20
20
  /**
@@ -24,4 +24,4 @@ interface InternalBuildInfo {
24
24
  */
25
25
  declare function loader(this: LoaderContext<Options>, source: string, callback: LoaderContext<Options>['callback']): Promise<void>;
26
26
 
27
- export { type InternalBuildInfo, type Options, loader as default };
27
+ export { type MetaFile, type Options, loader as default };
@@ -1,31 +1,116 @@
1
+ import {
2
+ getConfigHash,
3
+ getKey,
4
+ loadConfigCached
5
+ } from "./chunk-EKBKQCSU.mjs";
6
+ import {
7
+ getDefaultMDXOptions
8
+ } from "./chunk-LRV535YP.mjs";
9
+
1
10
  // src/loader-mdx.ts
2
- import path2 from "node:path";
11
+ import path3 from "node:path";
3
12
  import fs2 from "node:fs/promises";
4
- import { createProcessor } from "@mdx-js/mdx";
13
+ import { parse } from "node:querystring";
5
14
  import grayMatter from "gray-matter";
6
15
 
7
- // src/utils/git-timestamp.ts
16
+ // src/utils/find-collection.ts
8
17
  import path from "node:path";
18
+ import micromatch from "micromatch";
19
+ function findCollectionId(config, file, type) {
20
+ const cached = config._runtime.files.get(file);
21
+ if (cached) return cached;
22
+ for (const [name, collection] of config.collections.entries()) {
23
+ if (collection.type !== type) continue;
24
+ const dirs = Array.isArray(collection.dir) ? collection.dir : [collection.dir];
25
+ const isInDir = dirs.some((dir) => {
26
+ const relative = path.relative(dir, path.dirname(file));
27
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
28
+ });
29
+ if (!isInDir) continue;
30
+ const isIncluded = collection.files ? micromatch.isMatch(file, collection.files) : true;
31
+ if (!isIncluded) continue;
32
+ config._runtime.files.set(file, name);
33
+ return name;
34
+ }
35
+ }
36
+
37
+ // src/utils/build-mdx.ts
38
+ import { createProcessor } from "@mdx-js/mdx";
39
+ var cache = /* @__PURE__ */ new Map();
40
+ function cacheKey(group, format) {
41
+ return `${group}:${format}`;
42
+ }
43
+ function buildMDX(group, configHash, source, options = {}) {
44
+ const { filePath, frontmatter, data, ...rest } = options;
45
+ let format = options.format;
46
+ if (!format && filePath) {
47
+ format = filePath.endsWith(".mdx") ? "mdx" : "md";
48
+ }
49
+ format ??= "mdx";
50
+ const key = cacheKey(group, format);
51
+ let cached = cache.get(key);
52
+ if (cached === void 0 || cached.configHash !== configHash) {
53
+ cached = {
54
+ processor: createProcessor({
55
+ outputFormat: "program",
56
+ development: process.env.NODE_ENV === "development",
57
+ ...rest,
58
+ format
59
+ }),
60
+ configHash
61
+ };
62
+ cache.set(key, cached);
63
+ }
64
+ return cached.processor.process({
65
+ value: source,
66
+ path: filePath,
67
+ data: {
68
+ ...data,
69
+ frontmatter
70
+ }
71
+ });
72
+ }
73
+
74
+ // src/utils/format-error.ts
75
+ function formatError(file, error) {
76
+ const lines = [];
77
+ function walk(key, { _errors, ...rest }, padStart = 0) {
78
+ if (key !== void 0 || _errors.length > 0) {
79
+ const text = key ? `${key}: ${_errors.join("\n ")}` : _errors.join("\n");
80
+ lines.push(
81
+ text.split("\n").map((line) => `${" ".repeat(padStart)}${line}`).join("\n")
82
+ );
83
+ }
84
+ for (const [k, v] of Object.entries(rest)) {
85
+ walk(key ? `${key}.${k}` : k, v, padStart + 2);
86
+ }
87
+ }
88
+ walk(void 0, error.format());
89
+ return [`in ${file}:`, ...lines].join("\n");
90
+ }
91
+
92
+ // src/utils/git-timestamp.ts
93
+ import path2 from "node:path";
9
94
  import fs from "node:fs";
10
95
  import { spawn } from "cross-spawn";
11
- var cache = /* @__PURE__ */ new Map();
96
+ var cache2 = /* @__PURE__ */ new Map();
12
97
  function getGitTimestamp(file) {
13
- const cachedTimestamp = cache.get(file);
98
+ const cachedTimestamp = cache2.get(file);
14
99
  if (cachedTimestamp) return Promise.resolve(cachedTimestamp);
15
100
  return new Promise((resolve, reject) => {
16
- const cwd = path.dirname(file);
101
+ const cwd = path2.dirname(file);
17
102
  if (!fs.existsSync(cwd)) {
18
103
  resolve(void 0);
19
104
  return;
20
105
  }
21
- const fileName = path.basename(file);
106
+ const fileName = path2.basename(file);
22
107
  const child = spawn("git", ["log", "-1", '--pretty="%ai"', fileName], {
23
108
  cwd
24
109
  });
25
110
  let output;
26
111
  child.stdout.on("data", (d) => output = new Date(String(d)));
27
112
  child.on("close", () => {
28
- if (output) cache.set(file, output);
113
+ if (output) cache2.set(file, output);
29
114
  resolve(output);
30
115
  });
31
116
  child.on("error", reject);
@@ -33,55 +118,93 @@ function getGitTimestamp(file) {
33
118
  }
34
119
 
35
120
  // src/loader-mdx.ts
36
- var cache2 = /* @__PURE__ */ new Map();
121
+ function getQuery(query) {
122
+ let collection;
123
+ let hash;
124
+ const parsed = parse(query.slice(1));
125
+ if (parsed.collection && typeof parsed.collection === "string")
126
+ collection = parsed.collection;
127
+ if (parsed.hash && typeof parsed.hash === "string") hash = parsed.hash;
128
+ return { collection, hash };
129
+ }
37
130
  async function loader(source, callback) {
38
131
  this.cacheable(true);
39
132
  const context = this.context;
40
133
  const filePath = this.resourcePath;
41
- const { lastModifiedTime, ...options } = this.getOptions();
42
- const detectedFormat = filePath.endsWith(".mdx") ? "mdx" : "md";
43
- const format = options.format ?? detectedFormat;
44
- let processor = cache2.get(format);
45
- if (processor === void 0) {
46
- processor = createProcessor({
47
- ...options,
48
- development: this.mode === "development",
49
- format
50
- });
51
- cache2.set(format, processor);
52
- }
134
+ const { _ctx } = this.getOptions();
53
135
  const matter = grayMatter(source);
136
+ const query = getQuery(this.resourceQuery);
137
+ const configHash = query.hash ?? await getConfigHash(_ctx.configPath);
138
+ const config = await loadConfigCached(_ctx.configPath, configHash);
139
+ const collectionId = query.collection ?? findCollectionId(config, filePath, "doc");
140
+ const collection = collectionId !== void 0 ? config.collections.get(collectionId) : void 0;
141
+ const mdxOptions = collection?.mdxOptions ?? getDefaultMDXOptions(config.global?.mdxOptions ?? {});
142
+ function getTransformContext() {
143
+ return {
144
+ buildMDX: async (v, options = mdxOptions) => {
145
+ const res = await buildMDX(
146
+ collectionId ?? "global",
147
+ configHash,
148
+ v,
149
+ options
150
+ );
151
+ return String(res.value);
152
+ },
153
+ source,
154
+ path: filePath
155
+ };
156
+ }
157
+ let frontmatter = matter.data;
158
+ if (collection?.schema) {
159
+ const schema = typeof collection.schema === "function" ? collection.schema(getTransformContext()) : collection.schema;
160
+ const result = await schema.safeParseAsync(frontmatter);
161
+ if (result.error) {
162
+ callback(new Error(formatError(filePath, result.error)));
163
+ return;
164
+ }
165
+ frontmatter = result.data;
166
+ }
54
167
  const props = matter.data._mdx ?? {};
55
168
  if (props.mirror) {
56
- const mirrorPath = path2.resolve(path2.dirname(filePath), props.mirror);
169
+ const mirrorPath = path3.resolve(path3.dirname(filePath), props.mirror);
57
170
  this.addDependency(mirrorPath);
58
171
  matter.content = await fs2.readFile(mirrorPath).then((res) => grayMatter(res.toString()).content);
59
172
  }
60
173
  let timestamp;
61
- if (lastModifiedTime === "git")
174
+ if (config.global?.lastModifiedTime === "git")
62
175
  timestamp = (await getGitTimestamp(filePath))?.getTime();
63
- processor.process({
64
- value: matter.content,
65
- path: filePath,
66
- data: {
67
- lastModified: timestamp,
68
- frontmatter: matter.data
176
+ try {
177
+ const file = await buildMDX(
178
+ collectionId ?? "global",
179
+ configHash,
180
+ matter.content,
181
+ {
182
+ development: this.mode === "development",
183
+ ...mdxOptions,
184
+ filePath,
185
+ frontmatter,
186
+ data: {
187
+ lastModified: timestamp
188
+ }
189
+ }
190
+ );
191
+ callback(void 0, String(file.value), file.map ?? void 0);
192
+ if (config.global?.generateManifest) {
193
+ await fs2.mkdir(".next/cache/fumadocs", { recursive: true });
194
+ await fs2.writeFile(
195
+ path3.resolve(".next/cache/fumadocs", `${getKey(filePath)}.json`),
196
+ JSON.stringify({
197
+ path: filePath,
198
+ data: file.data
199
+ })
200
+ );
69
201
  }
70
- }).then(
71
- (file) => {
72
- const info = this._module?.buildInfo;
73
- info.__fumadocs = {
74
- path: filePath,
75
- data: file.data
76
- };
77
- callback(void 0, String(file.value), file.map ?? void 0);
78
- },
79
- (error) => {
80
- const fpath = path2.relative(context, filePath);
81
- error.message = `${fpath}:${error.name}: ${error.message}`;
82
- callback(error);
83
- }
84
- );
202
+ } catch (error) {
203
+ if (!(error instanceof Error)) throw error;
204
+ const fpath = path3.relative(context, filePath);
205
+ error.message = `${fpath}:${error.name}: ${error.message}`;
206
+ callback(error);
207
+ }
85
208
  }
86
209
  export {
87
210
  loader as default
@@ -0,0 +1,13 @@
1
+ import { NextConfig } from 'next';
2
+
3
+ interface CreateMDXOptions {
4
+ /**
5
+ * Path to source configuration file
6
+ */
7
+ configPath?: string;
8
+ }
9
+ declare function createMDX({ configPath, }?: CreateMDXOptions): (nextConfig?: NextConfig) => NextConfig;
10
+
11
+ declare function postInstall(configPath?: string): Promise<void>;
12
+
13
+ export { type CreateMDXOptions, createMDX, postInstall };