@bliztek/mdx-utils 1.0.0 → 2.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.d.cts CHANGED
@@ -1,42 +1 @@
1
- type TableOfContentsEntry = {
2
- title: string;
3
- link: string;
4
- };
5
- type TableOfContents = TableOfContentsEntry[];
6
- interface ReadTimeOptions {
7
- /** Words per minute. Defaults to 238. */
8
- wordsPerMinute?: number;
9
- }
10
- interface ReadTimeResult {
11
- /** Estimated read time in minutes (minimum 1). */
12
- minutes: number;
13
- /** Total word count after stripping MDX/JSX syntax. */
14
- words: number;
15
- }
16
- interface GetContentSlugsOptions {
17
- /** File extensions to include. Defaults to `[".mdx"]`. */
18
- extensions?: string[];
19
- /** Recursively search subdirectories. Defaults to `false`. */
20
- recursive?: boolean;
21
- }
22
-
23
- /**
24
- * Strip MDX/markdown syntax from content, returning plain text.
25
- * Removes export blocks, import statements, JSX tags, and markdown
26
- * formatting characters. Useful for generating excerpts, search
27
- * indexes, or OpenGraph descriptions from raw MDX.
28
- */
29
- declare function stripMdxSyntax(content: string): string;
30
- /**
31
- * Calculate estimated read time for MDX/markdown content.
32
- * Strips export blocks, import statements, JSX tags, and markdown syntax
33
- * before counting words.
34
- */
35
- declare function calculateReadTime(content: string, options?: ReadTimeOptions): ReadTimeResult;
36
- /**
37
- * Sort items by date in descending order (newest first).
38
- * Returns a new array without mutating the original.
39
- */
40
- declare function sortByDateDescending<T>(items: T[], getDate: (item: T) => string): T[];
41
-
42
- export { type GetContentSlugsOptions, type ReadTimeOptions, type ReadTimeResult, type TableOfContents, type TableOfContentsEntry, calculateReadTime, sortByDateDescending, stripMdxSyntax };
1
+ export { F as FrontmatterSchema, G as GetContentSlugsOptions, d as MdxEntry, R as ReadTimeOptions, e as ReadTimeResult, T as TableOfContents, f as TableOfContentsEntry, g as calculateReadTime, s as sortByDateDescending, h as stripMdxSyntax } from './index-is67XHX5.cjs';
package/dist/index.d.ts CHANGED
@@ -1,42 +1 @@
1
- type TableOfContentsEntry = {
2
- title: string;
3
- link: string;
4
- };
5
- type TableOfContents = TableOfContentsEntry[];
6
- interface ReadTimeOptions {
7
- /** Words per minute. Defaults to 238. */
8
- wordsPerMinute?: number;
9
- }
10
- interface ReadTimeResult {
11
- /** Estimated read time in minutes (minimum 1). */
12
- minutes: number;
13
- /** Total word count after stripping MDX/JSX syntax. */
14
- words: number;
15
- }
16
- interface GetContentSlugsOptions {
17
- /** File extensions to include. Defaults to `[".mdx"]`. */
18
- extensions?: string[];
19
- /** Recursively search subdirectories. Defaults to `false`. */
20
- recursive?: boolean;
21
- }
22
-
23
- /**
24
- * Strip MDX/markdown syntax from content, returning plain text.
25
- * Removes export blocks, import statements, JSX tags, and markdown
26
- * formatting characters. Useful for generating excerpts, search
27
- * indexes, or OpenGraph descriptions from raw MDX.
28
- */
29
- declare function stripMdxSyntax(content: string): string;
30
- /**
31
- * Calculate estimated read time for MDX/markdown content.
32
- * Strips export blocks, import statements, JSX tags, and markdown syntax
33
- * before counting words.
34
- */
35
- declare function calculateReadTime(content: string, options?: ReadTimeOptions): ReadTimeResult;
36
- /**
37
- * Sort items by date in descending order (newest first).
38
- * Returns a new array without mutating the original.
39
- */
40
- declare function sortByDateDescending<T>(items: T[], getDate: (item: T) => string): T[];
41
-
42
- export { type GetContentSlugsOptions, type ReadTimeOptions, type ReadTimeResult, type TableOfContents, type TableOfContentsEntry, calculateReadTime, sortByDateDescending, stripMdxSyntax };
1
+ export { F as FrontmatterSchema, G as GetContentSlugsOptions, d as MdxEntry, R as ReadTimeOptions, e as ReadTimeResult, T as TableOfContents, f as TableOfContentsEntry, g as calculateReadTime, s as sortByDateDescending, h as stripMdxSyntax } from './index-is67XHX5.js';
package/dist/node.cjs CHANGED
@@ -30,15 +30,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/node.ts
31
31
  var node_exports = {};
32
32
  __export(node_exports, {
33
+ MdxValidationError: () => MdxValidationError,
33
34
  calculateReadTime: () => calculateReadTime,
35
+ collectRedirects: () => collectRedirects,
36
+ createMdxCollection: () => createMdxCollection,
37
+ defaultParseFrontmatter: () => defaultParseFrontmatter,
34
38
  getContentSlugs: () => getContentSlugs,
35
39
  readMdxFile: () => readMdxFile,
36
40
  sortByDateDescending: () => sortByDateDescending,
37
41
  stripMdxSyntax: () => stripMdxSyntax
38
42
  });
39
43
  module.exports = __toCommonJS(node_exports);
40
- var import_promises = require("fs/promises");
41
- var import_path = __toESM(require("path"), 1);
44
+ var import_promises2 = require("fs/promises");
45
+ var import_node_path2 = __toESM(require("path"), 1);
42
46
 
43
47
  // src/index.ts
44
48
  var DEFAULT_WPM = 238;
@@ -60,12 +64,330 @@ function sortByDateDescending(items, getDate) {
60
64
  );
61
65
  }
62
66
 
67
+ // src/collection.ts
68
+ var import_promises = require("fs/promises");
69
+ var import_node_path = __toESM(require("path"), 1);
70
+
71
+ // src/frontmatter.ts
72
+ function defaultParseFrontmatter(raw) {
73
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
74
+ if (!match) return {};
75
+ const lines = match[1].split(/\r?\n/);
76
+ const result = {};
77
+ let i = 0;
78
+ while (i < lines.length) {
79
+ const line = lines[i];
80
+ if (/^\s*(#|$)/.test(line)) {
81
+ i++;
82
+ continue;
83
+ }
84
+ const kv = line.match(/^(\s*)([A-Za-z_][\w-]*)\s*:\s*(.*)$/);
85
+ if (!kv) {
86
+ i++;
87
+ continue;
88
+ }
89
+ const key = kv[2];
90
+ const rawValue = kv[3].trim();
91
+ if (rawValue === "") {
92
+ const items = [];
93
+ let j = i + 1;
94
+ while (j < lines.length) {
95
+ const next = lines[j];
96
+ if (/^\s*$/.test(next)) {
97
+ j++;
98
+ continue;
99
+ }
100
+ const blockItem = next.match(/^\s+-\s+(.*)$/);
101
+ if (!blockItem) break;
102
+ items.push(parseScalar(blockItem[1].trim()));
103
+ j++;
104
+ }
105
+ if (items.length > 0) {
106
+ result[key] = items;
107
+ i = j;
108
+ continue;
109
+ }
110
+ result[key] = "";
111
+ i++;
112
+ continue;
113
+ }
114
+ if (rawValue.startsWith("[") && rawValue.endsWith("]")) {
115
+ const inner = rawValue.slice(1, -1).trim();
116
+ result[key] = inner === "" ? [] : splitInlineArray(inner).map(parseScalar);
117
+ i++;
118
+ continue;
119
+ }
120
+ result[key] = parseScalar(rawValue);
121
+ i++;
122
+ }
123
+ return result;
124
+ }
125
+ function parseScalar(value) {
126
+ if (value.length >= 2) {
127
+ const first = value[0];
128
+ const last = value[value.length - 1];
129
+ if (first === '"' && last === '"' || first === "'" && last === "'") {
130
+ return value.slice(1, -1);
131
+ }
132
+ }
133
+ return value;
134
+ }
135
+ function splitInlineArray(inner) {
136
+ const out = [];
137
+ let buf = "";
138
+ let quote = null;
139
+ for (let i = 0; i < inner.length; i++) {
140
+ const ch = inner[i];
141
+ if (quote) {
142
+ buf += ch;
143
+ if (ch === quote) quote = null;
144
+ continue;
145
+ }
146
+ if (ch === '"' || ch === "'") {
147
+ quote = ch;
148
+ buf += ch;
149
+ continue;
150
+ }
151
+ if (ch === ",") {
152
+ out.push(buf.trim());
153
+ buf = "";
154
+ continue;
155
+ }
156
+ buf += ch;
157
+ }
158
+ if (buf.trim() !== "") out.push(buf.trim());
159
+ return out;
160
+ }
161
+
162
+ // src/errors.ts
163
+ var MAX_INLINE_ISSUES = 10;
164
+ function formatMessage(issues) {
165
+ if (issues.length === 0) {
166
+ return "MdxValidationError: no issues (this is a bug \u2014 do not throw with an empty list)";
167
+ }
168
+ const head = `MdxValidationError: ${issues.length} issue${issues.length === 1 ? "" : "s"} found in frontmatter`;
169
+ const shown = issues.slice(0, MAX_INLINE_ISSUES);
170
+ const lines = shown.map(
171
+ (issue) => ` - ${issue.filePath} @ ${issue.path}: ${issue.message}`
172
+ );
173
+ if (issues.length > shown.length) {
174
+ lines.push(` ... and ${issues.length - shown.length} more`);
175
+ }
176
+ return [head, ...lines].join("\n");
177
+ }
178
+ var MdxValidationError = class extends Error {
179
+ constructor(issues) {
180
+ super(formatMessage(issues));
181
+ this.name = "MdxValidationError";
182
+ this.issues = issues;
183
+ }
184
+ };
185
+
186
+ // src/collection.ts
187
+ async function walkCollection(rootAbs, namespaceDepth) {
188
+ const dirents = await (0, import_promises.readdir)(rootAbs, {
189
+ withFileTypes: true,
190
+ recursive: true
191
+ });
192
+ const files = [];
193
+ for (const dirent of dirents) {
194
+ if (dirent.isDirectory()) continue;
195
+ if (!dirent.name.endsWith(".mdx")) continue;
196
+ const parentPath = dirent.parentPath ?? rootAbs;
197
+ const relativeDir = import_node_path.default.relative(rootAbs, parentPath);
198
+ const segments = relativeDir === "" ? [] : relativeDir.split(import_node_path.default.sep);
199
+ if (segments.length !== namespaceDepth) {
200
+ const filePath = import_node_path.default.join(parentPath, dirent.name);
201
+ throw new Error(
202
+ `[mdx-utils] File ${filePath} is at depth ${segments.length} but the collection declares namespaceDepth=${namespaceDepth}. Move the file or adjust namespaceDepth.`
203
+ );
204
+ }
205
+ files.push({
206
+ namespace: segments.join("/"),
207
+ slug: dirent.name.slice(0, -".mdx".length),
208
+ filePath: import_node_path.default.join(parentPath, dirent.name)
209
+ });
210
+ }
211
+ return files;
212
+ }
213
+ function stripFrontmatterBlock(raw) {
214
+ const match = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
215
+ return match ? raw.slice(match[0].length) : raw;
216
+ }
217
+ function sortEntries(entries) {
218
+ const withDate = [];
219
+ const withoutDate = [];
220
+ for (const entry of entries) {
221
+ const meta = entry.metadata;
222
+ const publishedAt = meta?.publishedAt;
223
+ if (typeof publishedAt === "string") {
224
+ const ts = new Date(publishedAt).getTime();
225
+ if (!Number.isNaN(ts)) {
226
+ withDate.push({ entry, ts });
227
+ continue;
228
+ }
229
+ }
230
+ withoutDate.push(entry);
231
+ }
232
+ withDate.sort((a, b) => b.ts - a.ts);
233
+ return [...withDate.map((x) => x.entry), ...withoutDate];
234
+ }
235
+ function extractIssues(filePath, error) {
236
+ if (error && typeof error === "object" && "issues" in error && Array.isArray(error.issues)) {
237
+ const raw = error.issues;
238
+ const mapped = [];
239
+ for (const entry of raw) {
240
+ const pathValue = entry.path;
241
+ const dotted = Array.isArray(pathValue) ? pathValue.join(".") : typeof pathValue === "string" ? pathValue : "(root)";
242
+ const message2 = typeof entry.message === "string" ? entry.message : String(entry);
243
+ mapped.push({ filePath, path: dotted || "(root)", message: message2 });
244
+ }
245
+ if (mapped.length > 0) return mapped;
246
+ }
247
+ const message = error instanceof Error ? error.message : String(error);
248
+ return [{ filePath, path: "(root)", message }];
249
+ }
250
+ function createMdxCollection(options) {
251
+ const {
252
+ root,
253
+ namespaceDepth = 0,
254
+ frontmatterSchema,
255
+ parseFrontmatter = defaultParseFrontmatter,
256
+ wordsPerMinute
257
+ } = options;
258
+ const rootAbs = import_node_path.default.resolve(root);
259
+ let entriesPromise = null;
260
+ async function loadEntries() {
261
+ const files = await walkCollection(rootAbs, namespaceDepth);
262
+ const loaded = await Promise.all(
263
+ files.map(async (file) => {
264
+ const raw = await (0, import_promises.readFile)(file.filePath, "utf-8");
265
+ const metadata = parseFrontmatter(raw);
266
+ const body = stripFrontmatterBlock(raw);
267
+ const { minutes } = calculateReadTime(body, { wordsPerMinute });
268
+ return {
269
+ namespace: file.namespace,
270
+ slug: file.slug,
271
+ metadata,
272
+ readTime: minutes,
273
+ tableOfContents: void 0,
274
+ filePath: file.filePath
275
+ };
276
+ })
277
+ );
278
+ return sortEntries(loaded);
279
+ }
280
+ function getAll() {
281
+ if (!entriesPromise) {
282
+ entriesPromise = loadEntries().catch((err) => {
283
+ entriesPromise = null;
284
+ throw err;
285
+ });
286
+ }
287
+ return entriesPromise;
288
+ }
289
+ async function getAllByNamespace(namespace) {
290
+ const all = await getAll();
291
+ return all.filter((entry) => entry.namespace === namespace);
292
+ }
293
+ async function get(args) {
294
+ const targetNamespace = args.namespace ?? "";
295
+ const all = await getAll();
296
+ return all.find(
297
+ (entry) => entry.namespace === targetNamespace && entry.slug === args.slug
298
+ ) ?? null;
299
+ }
300
+ async function resolveRelated(namespace, slugs) {
301
+ const all = await getAll();
302
+ const inNamespace = new Map(
303
+ all.filter((entry) => entry.namespace === namespace).map((entry) => [entry.slug, entry])
304
+ );
305
+ const resolved = [];
306
+ for (const slug of slugs) {
307
+ const entry = inNamespace.get(slug);
308
+ if (entry) {
309
+ resolved.push(entry);
310
+ } else {
311
+ console.warn(
312
+ `[mdx-utils] resolveRelated: no entry for "${slug}" in namespace "${namespace}"`
313
+ );
314
+ }
315
+ }
316
+ return resolved;
317
+ }
318
+ async function validate() {
319
+ if (!frontmatterSchema) return;
320
+ const all = await getAll();
321
+ const issues = [];
322
+ for (const entry of all) {
323
+ try {
324
+ frontmatterSchema.parse(entry.metadata);
325
+ } catch (err) {
326
+ issues.push(...extractIssues(entry.filePath, err));
327
+ }
328
+ }
329
+ if (issues.length > 0) {
330
+ throw new MdxValidationError(issues);
331
+ }
332
+ }
333
+ function invalidate() {
334
+ entriesPromise = null;
335
+ }
336
+ return {
337
+ getAll,
338
+ getAllByNamespace,
339
+ get,
340
+ resolveRelated,
341
+ validate,
342
+ invalidate
343
+ };
344
+ }
345
+
346
+ // src/redirects.ts
347
+ function applyTokens(template, namespace, slug) {
348
+ return template.replace(/\{namespace\}/g, namespace).replace(/\{slug\}/g, slug);
349
+ }
350
+ async function collectRedirects(options) {
351
+ const {
352
+ root,
353
+ namespaceDepth = 0,
354
+ basePath,
355
+ permanent = true,
356
+ aliasField = "aliases"
357
+ } = options;
358
+ const collection = createMdxCollection({ root, namespaceDepth });
359
+ const entries = await collection.getAll();
360
+ const redirects = [];
361
+ const sourceOwners = /* @__PURE__ */ new Map();
362
+ for (const entry of entries) {
363
+ const meta = entry.metadata;
364
+ const rawAliases = meta[aliasField];
365
+ if (!Array.isArray(rawAliases)) continue;
366
+ const destination = applyTokens(basePath, entry.namespace, entry.slug);
367
+ for (const alias of rawAliases) {
368
+ if (typeof alias !== "string" || alias === "") continue;
369
+ const source = applyTokens(basePath, entry.namespace, alias);
370
+ if (source === destination) continue;
371
+ const existing = sourceOwners.get(source);
372
+ if (existing && existing !== entry.filePath) {
373
+ console.warn(
374
+ `[mdx-utils] collectRedirects: duplicate alias "${alias}" defined in both ${existing} and ${entry.filePath}. Next.js will pick the first match.`
375
+ );
376
+ } else {
377
+ sourceOwners.set(source, entry.filePath);
378
+ }
379
+ redirects.push({ source, destination, permanent });
380
+ }
381
+ }
382
+ return redirects;
383
+ }
384
+
63
385
  // src/node.ts
64
386
  var DEFAULT_EXTENSIONS = [".mdx"];
65
387
  async function getContentSlugs(dirPath, options) {
66
388
  const extensions = options?.extensions ?? DEFAULT_EXTENSIONS;
67
389
  const recursive = options?.recursive ?? false;
68
- const dirents = await (0, import_promises.readdir)(dirPath, {
390
+ const dirents = await (0, import_promises2.readdir)(dirPath, {
69
391
  withFileTypes: true,
70
392
  recursive
71
393
  });
@@ -74,16 +396,20 @@ async function getContentSlugs(dirPath, options) {
74
396
  ).map((dirent) => {
75
397
  const slug = dirent.name.substring(0, dirent.name.lastIndexOf("."));
76
398
  if (!recursive || !dirent.parentPath) return slug;
77
- const relativePath = import_path.default.relative(dirPath, dirent.parentPath);
78
- return relativePath ? import_path.default.join(relativePath, slug) : slug;
399
+ const relativePath = import_node_path2.default.relative(dirPath, dirent.parentPath);
400
+ return relativePath ? import_node_path2.default.join(relativePath, slug) : slug;
79
401
  });
80
402
  }
81
403
  async function readMdxFile(filePath) {
82
- return (0, import_promises.readFile)(filePath, "utf-8");
404
+ return (0, import_promises2.readFile)(filePath, "utf-8");
83
405
  }
84
406
  // Annotate the CommonJS export names for ESM import in node:
85
407
  0 && (module.exports = {
408
+ MdxValidationError,
86
409
  calculateReadTime,
410
+ collectRedirects,
411
+ createMdxCollection,
412
+ defaultParseFrontmatter,
87
413
  getContentSlugs,
88
414
  readMdxFile,
89
415
  sortByDateDescending,
package/dist/node.d.cts CHANGED
@@ -1,5 +1,78 @@
1
- import { GetContentSlugsOptions } from './index.cjs';
2
- export { ReadTimeOptions, ReadTimeResult, TableOfContents, TableOfContentsEntry, calculateReadTime, sortByDateDescending, stripMdxSyntax } from './index.cjs';
1
+ import { C as CreateMdxCollectionOptions, M as MdxCollection, a as CollectRedirectsOptions, N as NextRedirect, b as MdxValidationIssue, G as GetContentSlugsOptions } from './index-is67XHX5.cjs';
2
+ export { F as FrontmatterSchema, c as MdxCollectionGetArgs, d as MdxEntry, R as ReadTimeOptions, e as ReadTimeResult, T as TableOfContents, f as TableOfContentsEntry, g as calculateReadTime, s as sortByDateDescending, h as stripMdxSyntax } from './index-is67XHX5.cjs';
3
+
4
+ declare function createMdxCollection<TFrontmatter = Record<string, unknown>>(options: CreateMdxCollectionOptions<TFrontmatter>): MdxCollection<TFrontmatter>;
5
+
6
+ /**
7
+ * Walk a collection and emit Next.js-shaped redirects from each entry's
8
+ * `aliases` frontmatter field. Designed to plug straight into
9
+ * `next.config.ts`:
10
+ *
11
+ * ```ts
12
+ * export default {
13
+ * async redirects() {
14
+ * return collectRedirects({
15
+ * root: "content/games",
16
+ * namespaceDepth: 1,
17
+ * basePath: "/games/{namespace}/guides/{slug}",
18
+ * });
19
+ * },
20
+ * };
21
+ * ```
22
+ *
23
+ * Entries without an `aliases` field contribute nothing. Non-array
24
+ * values in the field are silently skipped — content-shape errors
25
+ * should never break `next.config`.
26
+ */
27
+ declare function collectRedirects(options: CollectRedirectsOptions): Promise<NextRedirect[]>;
28
+
29
+ /**
30
+ * Minimal built-in frontmatter parser.
31
+ *
32
+ * This is deliberately *not* a full YAML parser. It handles the subset
33
+ * used by blog and docs content and nothing more:
34
+ *
35
+ * - A `---`-delimited block at the very start of the file
36
+ * - `key: value` pairs with unquoted or quoted string values
37
+ * - Inline arrays: `tags: [a, b, c]`
38
+ * - Block arrays: `aliases:\n - old-slug\n - older-slug`
39
+ * - Blank lines and `# comments` inside the block
40
+ *
41
+ * All scalar values are returned as strings — no type coercion for
42
+ * numbers, booleans, or dates. Consumers who need coercion should run
43
+ * the result through a schema (`frontmatterSchema`) or swap in a real
44
+ * YAML parser via `parseFrontmatter`.
45
+ *
46
+ * Stability contract:
47
+ * - Expanding the supported subset (e.g. adding boolean coercion) is
48
+ * a non-breaking minor change.
49
+ * - Changing the *output shape* of already-supported inputs is a
50
+ * breaking change.
51
+ *
52
+ * Consumers who want to extend rather than replace the default can
53
+ * import it directly:
54
+ *
55
+ * ```ts
56
+ * import { defaultParseFrontmatter } from "@bliztek/mdx-utils/node";
57
+ * const wrapped = (raw: string) => {
58
+ * const base = defaultParseFrontmatter(raw);
59
+ * // ... post-process base ...
60
+ * return base;
61
+ * };
62
+ * ```
63
+ */
64
+ declare function defaultParseFrontmatter(raw: string): Record<string, unknown>;
65
+
66
+ /**
67
+ * Thrown by `MdxCollection.validate()` when one or more files fail
68
+ * schema validation. The full list of issues is preserved on `.issues`
69
+ * so consumers can render their own output instead of parsing the
70
+ * message string.
71
+ */
72
+ declare class MdxValidationError extends Error {
73
+ readonly issues: MdxValidationIssue[];
74
+ constructor(issues: MdxValidationIssue[]);
75
+ }
3
76
 
4
77
  /**
5
78
  * Get all content file slugs from a directory.
@@ -15,4 +88,4 @@ declare function getContentSlugs(dirPath: string, options?: GetContentSlugsOptio
15
88
  */
16
89
  declare function readMdxFile(filePath: string): Promise<string>;
17
90
 
18
- export { GetContentSlugsOptions, getContentSlugs, readMdxFile };
91
+ export { CollectRedirectsOptions, CreateMdxCollectionOptions, GetContentSlugsOptions, MdxCollection, MdxValidationError, MdxValidationIssue, NextRedirect, collectRedirects, createMdxCollection, defaultParseFrontmatter, getContentSlugs, readMdxFile };
package/dist/node.d.ts CHANGED
@@ -1,5 +1,78 @@
1
- import { GetContentSlugsOptions } from './index.js';
2
- export { ReadTimeOptions, ReadTimeResult, TableOfContents, TableOfContentsEntry, calculateReadTime, sortByDateDescending, stripMdxSyntax } from './index.js';
1
+ import { C as CreateMdxCollectionOptions, M as MdxCollection, a as CollectRedirectsOptions, N as NextRedirect, b as MdxValidationIssue, G as GetContentSlugsOptions } from './index-is67XHX5.js';
2
+ export { F as FrontmatterSchema, c as MdxCollectionGetArgs, d as MdxEntry, R as ReadTimeOptions, e as ReadTimeResult, T as TableOfContents, f as TableOfContentsEntry, g as calculateReadTime, s as sortByDateDescending, h as stripMdxSyntax } from './index-is67XHX5.js';
3
+
4
+ declare function createMdxCollection<TFrontmatter = Record<string, unknown>>(options: CreateMdxCollectionOptions<TFrontmatter>): MdxCollection<TFrontmatter>;
5
+
6
+ /**
7
+ * Walk a collection and emit Next.js-shaped redirects from each entry's
8
+ * `aliases` frontmatter field. Designed to plug straight into
9
+ * `next.config.ts`:
10
+ *
11
+ * ```ts
12
+ * export default {
13
+ * async redirects() {
14
+ * return collectRedirects({
15
+ * root: "content/games",
16
+ * namespaceDepth: 1,
17
+ * basePath: "/games/{namespace}/guides/{slug}",
18
+ * });
19
+ * },
20
+ * };
21
+ * ```
22
+ *
23
+ * Entries without an `aliases` field contribute nothing. Non-array
24
+ * values in the field are silently skipped — content-shape errors
25
+ * should never break `next.config`.
26
+ */
27
+ declare function collectRedirects(options: CollectRedirectsOptions): Promise<NextRedirect[]>;
28
+
29
+ /**
30
+ * Minimal built-in frontmatter parser.
31
+ *
32
+ * This is deliberately *not* a full YAML parser. It handles the subset
33
+ * used by blog and docs content and nothing more:
34
+ *
35
+ * - A `---`-delimited block at the very start of the file
36
+ * - `key: value` pairs with unquoted or quoted string values
37
+ * - Inline arrays: `tags: [a, b, c]`
38
+ * - Block arrays: `aliases:\n - old-slug\n - older-slug`
39
+ * - Blank lines and `# comments` inside the block
40
+ *
41
+ * All scalar values are returned as strings — no type coercion for
42
+ * numbers, booleans, or dates. Consumers who need coercion should run
43
+ * the result through a schema (`frontmatterSchema`) or swap in a real
44
+ * YAML parser via `parseFrontmatter`.
45
+ *
46
+ * Stability contract:
47
+ * - Expanding the supported subset (e.g. adding boolean coercion) is
48
+ * a non-breaking minor change.
49
+ * - Changing the *output shape* of already-supported inputs is a
50
+ * breaking change.
51
+ *
52
+ * Consumers who want to extend rather than replace the default can
53
+ * import it directly:
54
+ *
55
+ * ```ts
56
+ * import { defaultParseFrontmatter } from "@bliztek/mdx-utils/node";
57
+ * const wrapped = (raw: string) => {
58
+ * const base = defaultParseFrontmatter(raw);
59
+ * // ... post-process base ...
60
+ * return base;
61
+ * };
62
+ * ```
63
+ */
64
+ declare function defaultParseFrontmatter(raw: string): Record<string, unknown>;
65
+
66
+ /**
67
+ * Thrown by `MdxCollection.validate()` when one or more files fail
68
+ * schema validation. The full list of issues is preserved on `.issues`
69
+ * so consumers can render their own output instead of parsing the
70
+ * message string.
71
+ */
72
+ declare class MdxValidationError extends Error {
73
+ readonly issues: MdxValidationIssue[];
74
+ constructor(issues: MdxValidationIssue[]);
75
+ }
3
76
 
4
77
  /**
5
78
  * Get all content file slugs from a directory.
@@ -15,4 +88,4 @@ declare function getContentSlugs(dirPath: string, options?: GetContentSlugsOptio
15
88
  */
16
89
  declare function readMdxFile(filePath: string): Promise<string>;
17
90
 
18
- export { GetContentSlugsOptions, getContentSlugs, readMdxFile };
91
+ export { CollectRedirectsOptions, CreateMdxCollectionOptions, GetContentSlugsOptions, MdxCollection, MdxValidationError, MdxValidationIssue, NextRedirect, collectRedirects, createMdxCollection, defaultParseFrontmatter, getContentSlugs, readMdxFile };