fumadocs-mdx 11.7.1 → 11.7.4

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.
@@ -168,7 +168,7 @@ module.exports = __toCommonJS(vite_exports);
168
168
  // src/config/build.ts
169
169
  function buildConfig(config) {
170
170
  const collections = /* @__PURE__ */ new Map();
171
- let globalConfig;
171
+ let globalConfig = {};
172
172
  for (const [k, v] of Object.entries(config)) {
173
173
  if (!v) {
174
174
  continue;
@@ -183,34 +183,30 @@ function buildConfig(config) {
183
183
  continue;
184
184
  }
185
185
  }
186
- if (k === "default") {
186
+ if (k === "default" && v) {
187
187
  globalConfig = v;
188
188
  continue;
189
189
  }
190
- return [
191
- `Unknown export "${k}", you can only export collections from source configuration file.`,
192
- null
193
- ];
190
+ throw new Error(
191
+ `Unknown export "${k}", you can only export collections from source configuration file.`
192
+ );
194
193
  }
195
194
  let cachedMdxOptions;
196
- return [
197
- null,
198
- {
199
- global: globalConfig,
200
- collections,
201
- async getDefaultMDXOptions() {
202
- if (cachedMdxOptions) return cachedMdxOptions;
203
- const input = this.global?.mdxOptions;
204
- async function uncached() {
205
- const options = typeof input === "function" ? await input() : input;
206
- const { getDefaultMDXOptions: getDefaultMDXOptions2 } = await Promise.resolve().then(() => (init_mdx_options(), mdx_options_exports));
207
- if (options?.preset === "minimal") return options;
208
- return getDefaultMDXOptions2(options ?? {});
209
- }
210
- return cachedMdxOptions = uncached();
195
+ return {
196
+ global: globalConfig,
197
+ collections,
198
+ async getDefaultMDXOptions() {
199
+ if (cachedMdxOptions) return cachedMdxOptions;
200
+ const input = this.global.mdxOptions;
201
+ async function uncached() {
202
+ const options = typeof input === "function" ? await input() : input;
203
+ const { getDefaultMDXOptions: getDefaultMDXOptions2 } = await Promise.resolve().then(() => (init_mdx_options(), mdx_options_exports));
204
+ if (options?.preset === "minimal") return options;
205
+ return getDefaultMDXOptions2(options ?? {});
211
206
  }
207
+ return cachedMdxOptions = uncached();
212
208
  }
213
- ];
209
+ };
214
210
  }
215
211
 
216
212
  // src/utils/build-mdx.ts
@@ -244,9 +240,29 @@ function flattenNode(node) {
244
240
  if ("value" in node) return node.value;
245
241
  return "";
246
242
  }
243
+ function parseSpecifier(specifier) {
244
+ const idx = specifier.lastIndexOf("#");
245
+ if (idx === -1) return { file: specifier };
246
+ return {
247
+ file: specifier.slice(0, idx),
248
+ section: specifier.slice(idx + 1)
249
+ };
250
+ }
251
+ function extractSection(root, section) {
252
+ for (const node of root.children) {
253
+ if (node.type === "mdxJsxFlowElement" && node.name === "section" && node.attributes.some(
254
+ (attr) => attr.type === "mdxJsxAttribute" && attr.name === "id" && attr.value === section
255
+ )) {
256
+ return {
257
+ type: "root",
258
+ children: node.children
259
+ };
260
+ }
261
+ }
262
+ }
247
263
  function remarkInclude() {
248
264
  const TagName = "include";
249
- async function update(tree, file, processor, compiler) {
265
+ async function update(tree, directory, processor, compiler) {
250
266
  const queue = [];
251
267
  (0, import_unist_util_visit.visit)(
252
268
  tree,
@@ -266,27 +282,41 @@ function remarkInclude() {
266
282
  }
267
283
  }
268
284
  if (!specifier) return;
285
+ const { file, section } = parseSpecifier(specifier);
269
286
  const targetPath = path.resolve(
270
- "cwd" in params ? process.cwd() : path.dirname(file),
271
- specifier
287
+ "cwd" in params ? process.cwd() : directory,
288
+ file
272
289
  );
273
- const asCode = params.lang || !specifier.endsWith(".md") && !specifier.endsWith(".mdx");
290
+ const asCode = params.lang || !file.endsWith(".md") && !file.endsWith(".mdx");
274
291
  queue.push(
275
292
  fs.readFile(targetPath).then((buffer) => buffer.toString()).then(async (content) => {
276
293
  compiler?.addDependency(targetPath);
277
294
  if (asCode) {
278
- const lang = params.lang ?? path.extname(specifier).slice(1);
295
+ const lang = params.lang ?? path.extname(file).slice(1);
279
296
  Object.assign(node, {
280
297
  type: "code",
281
298
  lang,
282
299
  meta: params.meta,
283
- value: content.toString(),
300
+ value: content,
284
301
  data: {}
285
302
  });
286
303
  return;
287
304
  }
288
- const parsed = processor.parse(fumaMatter(content).content);
289
- await update(parsed, targetPath, processor, compiler);
305
+ let parsed = processor.parse(fumaMatter(content).content);
306
+ if (section) {
307
+ const extracted = extractSection(parsed, section);
308
+ if (!extracted)
309
+ throw new Error(
310
+ `Cannot find section ${section} in ${file}, make sure you have encapsulated the section in a <section id="${section}"> tag`
311
+ );
312
+ parsed = extracted;
313
+ }
314
+ await update(
315
+ parsed,
316
+ path.dirname(targetPath),
317
+ processor,
318
+ compiler
319
+ );
290
320
  Object.assign(
291
321
  parent && parent.type === "paragraph" ? parent : node,
292
322
  parsed
@@ -294,7 +324,8 @@ function remarkInclude() {
294
324
  }).catch((e) => {
295
325
  throw new Error(
296
326
  `failed to read file ${targetPath}
297
- ${e instanceof Error ? e.message : String(e)}`
327
+ ${e instanceof Error ? e.message : String(e)}`,
328
+ { cause: e }
298
329
  );
299
330
  })
300
331
  );
@@ -304,7 +335,7 @@ ${e instanceof Error ? e.message : String(e)}`
304
335
  await Promise.all(queue);
305
336
  }
306
337
  return async (tree, file) => {
307
- await update(tree, file.path, this, file.data._compiler);
338
+ await update(tree, path.dirname(file.path), this, file.data._compiler);
308
339
  };
309
340
  }
310
341
 
@@ -430,8 +461,8 @@ function ident(code, tab = 1) {
430
461
  }
431
462
 
432
463
  // src/vite/index.ts
433
- var import_promises = __toESM(require("fs/promises"), 1);
434
- var import_node_path2 = __toESM(require("path"), 1);
464
+ var fs2 = __toESM(require("fs/promises"), 1);
465
+ var path4 = __toESM(require("path"), 1);
435
466
  var import_js_yaml2 = require("js-yaml");
436
467
 
437
468
  // src/utils/collections.ts
@@ -446,14 +477,163 @@ function getGlobPatterns(collection) {
446
477
  return [`**/*.{${getSupportedFormats(collection).join(",")}}`];
447
478
  }
448
479
 
480
+ // src/vite/generate-glob.ts
481
+ function generateGlob(name, collection) {
482
+ const patterns = mapGlobPatterns(getGlobPatterns(collection));
483
+ const options = {
484
+ query: {
485
+ collection: name
486
+ },
487
+ base: getGlobBase(collection)
488
+ };
489
+ if (collection.type === "meta") {
490
+ options.import = "default";
491
+ }
492
+ return `import.meta.glob(${JSON.stringify(patterns)}, ${JSON.stringify(options, null, 2)})`;
493
+ }
494
+ function mapGlobPatterns(patterns) {
495
+ return patterns.map((file) => {
496
+ if (file.startsWith("./")) return file;
497
+ if (file.startsWith("/")) return `.${file}`;
498
+ return `./${file}`;
499
+ });
500
+ }
501
+ function getGlobBase(collection) {
502
+ let dir = collection.dir;
503
+ if (Array.isArray(dir)) {
504
+ if (dir.length !== 1)
505
+ throw new Error(
506
+ `[Fumadocs MDX] Vite Plugin doesn't support multiple \`dir\` for a collection at the moment.`
507
+ );
508
+ dir = dir[0];
509
+ }
510
+ if (!dir.startsWith("./") && !dir.startsWith("/")) {
511
+ return "/" + dir;
512
+ }
513
+ return dir;
514
+ }
515
+
516
+ // src/utils/git-timestamp.ts
517
+ var import_node_path2 = __toESM(require("path"), 1);
518
+ var import_tinyexec = require("tinyexec");
519
+ var cache2 = /* @__PURE__ */ new Map();
520
+ async function getGitTimestamp(file) {
521
+ const cached = cache2.get(file);
522
+ if (cached) return cached;
523
+ try {
524
+ const out = await (0, import_tinyexec.x)(
525
+ "git",
526
+ ["log", "-1", '--pretty="%ai"', import_node_path2.default.relative(process.cwd(), file)],
527
+ {
528
+ throwOnError: true
529
+ }
530
+ );
531
+ const time = new Date(out.stdout);
532
+ cache2.set(file, time);
533
+ return time;
534
+ } catch {
535
+ return;
536
+ }
537
+ }
538
+
449
539
  // src/vite/index.ts
450
- var fileRegex = /\.(md|mdx)$/;
451
540
  var onlySchema = import_zod2.z.literal(["frontmatter", "all"]);
452
541
  function mdx(config, options = {}) {
453
542
  const { generateIndexFile = true, configPath = "source.config.ts" } = options;
454
- const [err, loaded] = buildConfig(config);
455
- if (err || !loaded) {
456
- throw new Error(err);
543
+ const loaded = buildConfig(config);
544
+ async function transformMeta(path5, query, value) {
545
+ const isJson = path5.endsWith(".json");
546
+ const parsed = (0, import_node_querystring.parse)(query);
547
+ const collection = parsed.collection ? loaded.collections.get(parsed.collection) : void 0;
548
+ if (!collection) return null;
549
+ let schema;
550
+ switch (collection.type) {
551
+ case "meta":
552
+ schema = collection.schema;
553
+ break;
554
+ case "docs":
555
+ schema = collection.meta.schema;
556
+ break;
557
+ }
558
+ if (!schema) return null;
559
+ let data;
560
+ try {
561
+ data = isJson ? JSON.parse(value) : (0, import_js_yaml2.load)(value);
562
+ } catch {
563
+ return null;
564
+ }
565
+ const out = await validate(
566
+ schema,
567
+ data,
568
+ { path: path5, source: value },
569
+ `invalid data in ${path5}`
570
+ );
571
+ return {
572
+ code: isJson ? JSON.stringify(out) : `export default ${JSON.stringify(out)}`,
573
+ map: null
574
+ };
575
+ }
576
+ async function transformContent(file, query, value) {
577
+ const matter = fumaMatter(value);
578
+ const isDevelopment = this.environment.mode === "dev";
579
+ const parsed = (0, import_node_querystring.parse)(query);
580
+ const collection = parsed.collection ? loaded.collections.get(parsed.collection) : void 0;
581
+ const only = parsed.only ? onlySchema.parse(parsed.only) : "all";
582
+ let schema;
583
+ let mdxOptions;
584
+ switch (collection?.type) {
585
+ case "doc":
586
+ mdxOptions = collection.mdxOptions;
587
+ schema = collection.schema;
588
+ break;
589
+ case "docs":
590
+ mdxOptions = collection.docs.mdxOptions;
591
+ schema = collection.docs.schema;
592
+ break;
593
+ }
594
+ if (schema) {
595
+ matter.data = await validate(
596
+ schema,
597
+ matter.data,
598
+ {
599
+ source: value,
600
+ path: file
601
+ },
602
+ `invalid frontmatter in ${file}`
603
+ );
604
+ }
605
+ if (only === "frontmatter") {
606
+ return {
607
+ code: `export const frontmatter = ${JSON.stringify(matter.data)}`,
608
+ map: null
609
+ };
610
+ }
611
+ const data = {};
612
+ if (loaded.global.lastModifiedTime === "git") {
613
+ data.lastModified = (await getGitTimestamp(file))?.getTime();
614
+ }
615
+ mdxOptions ??= await loaded.getDefaultMDXOptions();
616
+ const lineOffset = isDevelopment ? countLines(matter.matter) : 0;
617
+ const compiled = await buildMDX(
618
+ parsed.collection ?? "global",
619
+ "\n".repeat(lineOffset) + matter.content,
620
+ {
621
+ development: isDevelopment,
622
+ ...mdxOptions,
623
+ data,
624
+ filePath: file,
625
+ frontmatter: matter.data,
626
+ _compiler: {
627
+ addDependency: (file2) => {
628
+ this.addWatchFile(file2);
629
+ }
630
+ }
631
+ }
632
+ );
633
+ return {
634
+ code: String(compiled.value),
635
+ map: compiled.map
636
+ };
457
637
  }
458
638
  return {
459
639
  name: "fumadocs-mdx",
@@ -465,6 +645,7 @@ function mdx(config, options = {}) {
465
645
  const outdir = process.cwd();
466
646
  const outFile = "source.generated.ts";
467
647
  const lines = [
648
+ '/// <reference types="vite/client" />',
468
649
  `import { fromConfig } from 'fumadocs-mdx/runtime/vite';`,
469
650
  `import type * as Config from '${toImportPath(configPath, {
470
651
  relativeTo: outdir
@@ -497,139 +678,22 @@ ${args}
497
678
  lines.push(doc(name, collection));
498
679
  }
499
680
  }
500
- await import_promises.default.writeFile(import_node_path2.default.join(outdir, outFile), lines.join("\n"));
681
+ await fs2.writeFile(path4.join(outdir, outFile), lines.join("\n"));
501
682
  },
502
683
  async transform(value, id) {
503
- const [path4, query = ""] = id.split("?");
504
- const isJson = path4.endsWith(".json");
505
- const isYaml = path4.endsWith(".yaml");
506
- if (isJson || isYaml) {
507
- const parsed2 = (0, import_node_querystring.parse)(query);
508
- const collection2 = parsed2.collection ? loaded.collections.get(parsed2.collection) : void 0;
509
- if (!collection2) return null;
510
- let schema2;
511
- switch (collection2.type) {
512
- case "meta":
513
- schema2 = collection2.schema;
514
- break;
515
- case "docs":
516
- schema2 = collection2.meta.schema;
517
- break;
518
- }
519
- if (!schema2) return null;
520
- let data;
521
- try {
522
- data = isJson ? JSON.parse(value) : (0, import_js_yaml2.load)(value);
523
- } catch {
524
- return null;
684
+ const [file, query = ""] = id.split("?");
685
+ const ext = path4.extname(file);
686
+ try {
687
+ if ([".yaml", ".json"].includes(ext))
688
+ return await transformMeta(file, query, value);
689
+ if ([".md", ".mdx"].includes(ext))
690
+ return await transformContent.call(this, file, query, value);
691
+ } catch (e) {
692
+ if (e instanceof ValidationError) {
693
+ throw new Error(e.toStringFormatted());
525
694
  }
526
- const out = await validate(
527
- schema2,
528
- data,
529
- { path: path4, source: value },
530
- `invalid data in ${path4}`
531
- );
532
- return {
533
- code: isJson ? JSON.stringify(out) : `export default ${JSON.stringify(out)}`,
534
- map: null
535
- };
536
- }
537
- if (!fileRegex.test(path4)) return;
538
- const matter = fumaMatter(value);
539
- const isDevelopment = this.environment.mode === "dev";
540
- const parsed = (0, import_node_querystring.parse)(query);
541
- const collection = parsed.collection ? loaded.collections.get(parsed.collection) : void 0;
542
- const only = parsed.only ? onlySchema.parse(parsed.only) : "all";
543
- let schema;
544
- let mdxOptions;
545
- switch (collection?.type) {
546
- case "doc":
547
- mdxOptions = collection.mdxOptions;
548
- schema = collection.schema;
549
- break;
550
- case "docs":
551
- mdxOptions = collection.docs.mdxOptions;
552
- schema = collection.docs.schema;
553
- break;
695
+ throw e;
554
696
  }
555
- if (schema) {
556
- try {
557
- matter.data = await validate(
558
- schema,
559
- matter.data,
560
- {
561
- source: value,
562
- path: path4
563
- },
564
- `invalid frontmatter in ${path4}`
565
- );
566
- } catch (e) {
567
- if (e instanceof ValidationError) {
568
- throw new Error(e.toStringFormatted());
569
- }
570
- throw e;
571
- }
572
- }
573
- if (only === "frontmatter") {
574
- return {
575
- code: `export const frontmatter = ${JSON.stringify(matter.data)}`
576
- };
577
- }
578
- mdxOptions ??= await loaded.getDefaultMDXOptions();
579
- const lineOffset = isDevelopment ? countLines(matter.matter) : 0;
580
- const file = await buildMDX(
581
- parsed.collection ?? "global",
582
- "\n".repeat(lineOffset) + matter.content,
583
- {
584
- development: isDevelopment,
585
- ...mdxOptions,
586
- filePath: path4,
587
- frontmatter: matter.data,
588
- _compiler: {
589
- addDependency: (file2) => {
590
- this.addWatchFile(file2);
591
- }
592
- }
593
- }
594
- );
595
- return {
596
- code: String(file.value),
597
- map: file.map
598
- };
599
697
  }
600
698
  };
601
699
  }
602
- function generateGlob(name, collection) {
603
- const patterns = mapGlobPatterns(getGlobPatterns(collection));
604
- const options = {
605
- query: {
606
- collection: name
607
- },
608
- base: getGlobBase(collection)
609
- };
610
- if (collection.type === "meta") {
611
- options.import = "default";
612
- }
613
- return `import.meta.glob(${JSON.stringify(patterns)}, ${JSON.stringify(options, null, 2)})`;
614
- }
615
- function mapGlobPatterns(patterns) {
616
- return patterns.map((file) => {
617
- if (file.startsWith("./")) return file;
618
- if (file.startsWith("/")) return `.${file}`;
619
- return `./${file}`;
620
- });
621
- }
622
- function getGlobBase(collection) {
623
- let dir = collection.dir;
624
- if (Array.isArray(dir)) {
625
- if (dir.length !== 1)
626
- throw new Error(
627
- `[Fumadocs MDX] Vite Plugin doesn't support multiple \`dir\` for a collection at the moment.`
628
- );
629
- dir = dir[0];
630
- }
631
- if (!dir.startsWith("./") && !dir.startsWith("/")) {
632
- return "/" + dir;
633
- }
634
- return dir;
635
- }