@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/README.md +318 -2
- package/dist/index-is67XHX5.d.cts +178 -0
- package/dist/index-is67XHX5.d.ts +178 -0
- package/dist/index.d.cts +1 -42
- package/dist/index.d.ts +1 -42
- package/dist/node.cjs +332 -6
- package/dist/node.d.cts +76 -3
- package/dist/node.d.ts +76 -3
- package/dist/node.js +328 -4
- package/package.json +7 -5
package/dist/index.d.cts
CHANGED
|
@@ -1,42 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
|
41
|
-
var
|
|
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,
|
|
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 =
|
|
78
|
-
return relativePath ?
|
|
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,
|
|
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 };
|