@pitlane/content 0.1.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/mdx.mjs ADDED
@@ -0,0 +1,389 @@
1
+ //#region src/mdx.ts
2
+ /**
3
+ * The imports in one top-level ESM block, and what is left after removing them.
4
+ *
5
+ * The remainder matters twice over. A block holds whatever the author wrote, so
6
+ * an `export const` can sit beside an import and the document's body may use
7
+ * it; dropping the whole block would take the export with the import. And what
8
+ * is left is spliced back into the document, so it has to stay recognisable as
9
+ * ESM: a comment surviving on its own line starts a paragraph, which swallows
10
+ * the export that follows it.
11
+ *
12
+ * Statement boundaries come from `es-module-lexer`, the lexer Vite reads the
13
+ * same imports with. Deciding them here instead means reimplementing JavaScript
14
+ * tokenization: a quote inside a regex literal is not a string, a line starting
15
+ * with `import` inside a template is not a statement, `{ import: "x" }` is a
16
+ * property, and each of those was wrong before the lexer answered it.
17
+ *
18
+ * Anything import-shaped the lexer reports and this cannot read throws rather
19
+ * than being skipped: a skipped import is a component that silently renders as
20
+ * nothing, which is the failure this whole path exists to remove.
21
+ */
22
+ async function readEsm(block, where) {
23
+ let { init, parse } = await import("es-module-lexer");
24
+ await init;
25
+ let lexable = readable(block, parse, where);
26
+ let comments = commentSpans(lexable);
27
+ let statements = [];
28
+ let imports = [];
29
+ for (let found of parse(lexable)[0]) {
30
+ if (found.t === 3) throw importMeta(where);
31
+ if (found.t !== 1 || !block.startsWith("import", found.ss)) continue;
32
+ let tail = endOfStatement(block, found.e + 1, comments);
33
+ statements.push({
34
+ start: found.ss,
35
+ end: tail.end
36
+ });
37
+ let read = readImport(block, found, tail, comments, where);
38
+ if (read) imports.push(read);
39
+ }
40
+ let outside = comments.filter((comment) => !statements.some((held) => covers(held, comment)));
41
+ return {
42
+ imports,
43
+ remainder: blank(block, [...statements, ...outside])
44
+ };
45
+ }
46
+ function covers(outer, inner) {
47
+ return inner.start >= outer.start && inner.end <= outer.end;
48
+ }
49
+ /**
50
+ * The block in a form the lexer can read.
51
+ *
52
+ * An MDX document defines a component by writing JSX in its ESM block, and the
53
+ * lexer is a JavaScript lexer: `() => <em>n</em>` is a parse error to it. So
54
+ * each line is masked from the first JSX tag it holds, which leaves every
55
+ * import intact -- an import statement cannot contain a `<` -- and keeps every
56
+ * offset, because the mask is the same width as what it covers.
57
+ */
58
+ function readable(block, parse, where) {
59
+ if (lexes(block, parse)) return block;
60
+ let masked = block.split("\n").map((line) => {
61
+ let at = line.search(/<(?=[A-Za-z_$>/])/);
62
+ return at === -1 ? line : line.slice(0, at) + " ".repeat(line.length - at);
63
+ }).join("\n");
64
+ if (lexes(masked, parse)) return masked;
65
+ throw new Error(`Could not read the imports of "${where}": its \`import\` and \`export\` block is not valid JavaScript. Add contentLayer() from @pitlane/content/vite so the build compiles this collection.`);
66
+ }
67
+ function lexes(source, parse) {
68
+ try {
69
+ parse(source);
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ }
75
+ /**
76
+ * What follows the specifier: an optional attributes clause, then an optional
77
+ * semicolon, with comments allowed in both gaps.
78
+ *
79
+ * The lexer reports neither reliably. Its statement end stops at the specifier,
80
+ * leaving a `;` that becomes a paragraph reading `;` in the rendered page, and
81
+ * it declines to report a clause written with a trailing comma, with no
82
+ * attributes at all, or on the line below -- all of which are valid, and all of
83
+ * which the host still demands before it will load the module.
84
+ */
85
+ function endOfStatement(block, after, comments) {
86
+ let end = skipBlanks(block, after, comments);
87
+ let attributes;
88
+ let phrase = /^(?:with|assert)\b/.exec(block.slice(end));
89
+ if (phrase) {
90
+ let open = skipBlanks(block, end + phrase[0].length, comments);
91
+ if (block[open] === "{") {
92
+ let close = balanced(block, open);
93
+ attributes = {
94
+ start: open,
95
+ end: close
96
+ };
97
+ end = close;
98
+ }
99
+ }
100
+ let semicolon = skipBlanks(block, end, comments);
101
+ return {
102
+ end: block[semicolon] === ";" ? semicolon + 1 : end,
103
+ attributes
104
+ };
105
+ }
106
+ /** Past whitespace and comments, which may sit anywhere in a statement. */
107
+ function skipBlanks(block, from, comments) {
108
+ let index = from;
109
+ for (;;) {
110
+ while (index < block.length && /\s/.test(block[index])) index += 1;
111
+ let comment = comments.find((held) => held.start === index);
112
+ if (!comment) return index;
113
+ index = comment.end;
114
+ }
115
+ }
116
+ /** The index just past the `}` closing the `{` at `open`. */
117
+ function balanced(block, open) {
118
+ let depth = 0;
119
+ for (let index = open; index < block.length; index += 1) if (block[index] === "{") depth += 1;
120
+ else if (block[index] === "}") {
121
+ depth -= 1;
122
+ if (depth === 0) return index + 1;
123
+ }
124
+ return block.length;
125
+ }
126
+ /**
127
+ * Replaces each span with its own newlines.
128
+ *
129
+ * Blanked rather than deleted so every offset after it still lines up with the
130
+ * source the author wrote, which is what keeps a later error's line number
131
+ * pointing at the right line.
132
+ */
133
+ function blank(block, spans) {
134
+ let ordered = [...spans].sort((a, b) => b.start - a.start);
135
+ let out = block;
136
+ for (let { start, end } of ordered) {
137
+ let removed = out.slice(start, end);
138
+ out = out.slice(0, start) + "\n".repeat((removed.match(/\n/g) ?? []).length) + out.slice(end);
139
+ }
140
+ return out;
141
+ }
142
+ /** Reads one statement, or nothing when it is type-only. */
143
+ function readImport(block, found, tail, comments, where) {
144
+ let statement = block.slice(found.ss, tail.end);
145
+ let specifier = found.n;
146
+ if (specifier === void 0) throw unreadable(statement, where);
147
+ let clause = erase(block, {
148
+ start: found.ss + 6,
149
+ end: found.s - 1
150
+ }, comments).trim().replace(/\bfrom$/, "").trim();
151
+ if (/^type\b/.test(clause) && clause.slice(4).trim().length > 0) return void 0;
152
+ let attributes = tail.attributes && readAttributes(erase(block, tail.attributes, comments));
153
+ return {
154
+ specifier,
155
+ bindings: readBindings(clause, statement, where),
156
+ attributes
157
+ };
158
+ }
159
+ /** A slice of the block with any comment in it replaced by blanks. */
160
+ function erase(block, span, comments) {
161
+ let out = block.slice(span.start, span.end);
162
+ for (let comment of comments) {
163
+ if (!covers(span, comment)) continue;
164
+ let start = comment.start - span.start;
165
+ let width = comment.end - comment.start;
166
+ out = out.slice(0, start) + " ".repeat(width) + out.slice(start + width);
167
+ }
168
+ return out;
169
+ }
170
+ function readBindings(clause, statement, where) {
171
+ let bindings = /* @__PURE__ */ new Map();
172
+ let named = /\{([\s\S]*)\}/.exec(clause);
173
+ let head = (named ? clause.slice(0, named.index) : clause).replace(/,\s*$/, "").trim();
174
+ if (head.startsWith("* as ")) throw namespaceImport(head.slice(5).trim(), statement, where);
175
+ if (head.length > 0) bindings.set(identifier(head, statement, where), "default");
176
+ for (let part of named?.[1]?.split(",") ?? []) {
177
+ let entry = part.trim();
178
+ if (entry.length === 0) continue;
179
+ let words = entry.split(/\s+/);
180
+ if (words[0] === "type" && words.length > 1 && words[1] !== "as") continue;
181
+ let renamed = /^(\S+)\s+as\s+(\S+)$/.exec(entry);
182
+ if (renamed) bindings.set(identifier(renamed[2], statement, where), renamed[1]);
183
+ else bindings.set(identifier(entry, statement, where), entry);
184
+ }
185
+ return bindings;
186
+ }
187
+ const ATTRIBUTE_RE = /(?:([A-Za-z_$][\w$]*)|"([^"]*)"|'([^']*)')\s*:\s*(?:"([^"]*)"|'([^']*)')/g;
188
+ /**
189
+ * An attributes clause has to reach the `import()` this path performs. Dropping
190
+ * it leaves `import data from "./x.json" with { type: "json" }` importing the
191
+ * file with no attribute, which Node refuses outright.
192
+ */
193
+ function readAttributes(clause) {
194
+ let attributes = {};
195
+ for (let [, name, quoted, single, value, singleValue] of clause.matchAll(ATTRIBUTE_RE)) attributes[name ?? quoted ?? single] = value ?? singleValue;
196
+ return attributes;
197
+ }
198
+ /**
199
+ * The comments in a block of ESM source.
200
+ *
201
+ * Only comments: the lexer answers for imports, so a mistake here leaves a
202
+ * comment in the remainder rather than losing a component. Regex literals are
203
+ * tracked all the same, because `/"/` holds a quote that would otherwise open a
204
+ * string running to the end of the next real one.
205
+ */
206
+ function commentSpans(block) {
207
+ let comments = [];
208
+ let nesting = [];
209
+ let previous = "";
210
+ let index = 0;
211
+ while (index < block.length) {
212
+ let char = block[index];
213
+ if (nesting.at(-1) === "template") {
214
+ if (char === "\\") index += 2;
215
+ else if (char === "`") {
216
+ nesting.pop();
217
+ previous = "`";
218
+ index += 1;
219
+ } else if (char === "$" && block[index + 1] === "{") {
220
+ nesting.push("interpolation");
221
+ previous = "";
222
+ index += 2;
223
+ } else index += 1;
224
+ continue;
225
+ }
226
+ if (char === "/" && block[index + 1] === "/") {
227
+ let line = block.indexOf("\n", index);
228
+ let end = line === -1 ? block.length : line;
229
+ comments.push({
230
+ start: index,
231
+ end
232
+ });
233
+ index = end;
234
+ } else if (char === "/" && block[index + 1] === "*") {
235
+ let close = block.indexOf("*/", index + 2);
236
+ let end = close === -1 ? block.length : close + 2;
237
+ comments.push({
238
+ start: index,
239
+ end
240
+ });
241
+ index = end;
242
+ } else if (char === "/" && startsRegex(previous)) {
243
+ index = endOfRegex(block, index);
244
+ previous = "/";
245
+ } else if (char === "\"" || char === "'") {
246
+ index = endOfString(block, index, char);
247
+ previous = char;
248
+ } else if (char === "`") {
249
+ nesting.push("template");
250
+ index += 1;
251
+ } else if (char === "{") {
252
+ nesting.push("brace");
253
+ previous = char;
254
+ index += 1;
255
+ } else if (char === "}") {
256
+ if (nesting.length > 0) nesting.pop();
257
+ previous = char;
258
+ index += 1;
259
+ } else if (isWordStart(char)) {
260
+ let end = index;
261
+ while (end < block.length && isWordPart(block[end])) end += 1;
262
+ previous = block.slice(index, end);
263
+ index = end;
264
+ } else if (/\s/.test(char)) index += 1;
265
+ else {
266
+ previous = char;
267
+ index += 1;
268
+ }
269
+ }
270
+ return comments;
271
+ }
272
+ /** Where a `/` can only begin a regex, never divide. */
273
+ const BEFORE_REGEX = /* @__PURE__ */ new Set([
274
+ "",
275
+ "=",
276
+ "(",
277
+ ",",
278
+ ":",
279
+ "[",
280
+ "!",
281
+ "&",
282
+ "|",
283
+ "?",
284
+ ";",
285
+ "{",
286
+ "}",
287
+ "+",
288
+ "-",
289
+ "*",
290
+ "%",
291
+ "~",
292
+ "^",
293
+ "<",
294
+ ">",
295
+ "return",
296
+ "typeof",
297
+ "instanceof",
298
+ "in",
299
+ "of",
300
+ "new",
301
+ "delete",
302
+ "void",
303
+ "case",
304
+ "do",
305
+ "else",
306
+ "yield",
307
+ "await",
308
+ "throw"
309
+ ]);
310
+ function startsRegex(previous) {
311
+ return BEFORE_REGEX.has(previous);
312
+ }
313
+ /**
314
+ * The end of a regex literal, or the `/` itself when it turns out to divide.
315
+ *
316
+ * A literal never spans a line, so an unclosed one by the end of the line is
317
+ * division after all, whatever the token before it suggested.
318
+ */
319
+ function endOfRegex(block, start) {
320
+ let index = start + 1;
321
+ let inClass = false;
322
+ while (index < block.length) {
323
+ let char = block[index];
324
+ if (char === "\n") return start + 1;
325
+ if (char === "\\") index += 2;
326
+ else if (char === "[") {
327
+ inClass = true;
328
+ index += 1;
329
+ } else if (char === "]") {
330
+ inClass = false;
331
+ index += 1;
332
+ } else if (char === "/" && !inClass) {
333
+ index += 1;
334
+ while (index < block.length && /[a-z]/.test(block[index])) index += 1;
335
+ return index;
336
+ } else index += 1;
337
+ }
338
+ return start + 1;
339
+ }
340
+ function endOfString(block, start, quote) {
341
+ let index = start + 1;
342
+ while (index < block.length) {
343
+ let char = block[index];
344
+ if (char === "\\") index += 2;
345
+ else if (char === quote) return index + 1;
346
+ else index += 1;
347
+ }
348
+ return block.length;
349
+ }
350
+ function isWordStart(char) {
351
+ return /[A-Za-z_$]/.test(char);
352
+ }
353
+ function isWordPart(char) {
354
+ return /[\w$]/.test(char);
355
+ }
356
+ /**
357
+ * A bundler compiles the document to a module, where `import.meta` is
358
+ * ordinary. Here the body is a function body, so the engine refuses it with
359
+ * `SyntaxError: Cannot use 'import.meta' outside a module` thrown from source
360
+ * the author never wrote, naming neither the document nor the reason.
361
+ */
362
+ function importMeta(where) {
363
+ return /* @__PURE__ */ new Error(`"${where}" uses \`import.meta\`, which cannot be evaluated outside a bundler: the document is compiled to a function body rather than a module. Add contentLayer() from @pitlane/content/vite so the build compiles this collection.`);
364
+ }
365
+ /**
366
+ * A local name becomes a parameter of the function the compiled body is
367
+ * evaluated as, so anything that is not an identifier has to be refused here
368
+ * rather than producing a syntax error in generated source.
369
+ */
370
+ function identifier(name, statement, where) {
371
+ if (!/^[A-Za-z_$][\w$]*$/.test(name)) throw unreadable(statement, where);
372
+ return name;
373
+ }
374
+ /**
375
+ * Sätteri compiles `import * as ui from "./x.tsx"` to `const {} = arguments[0]`
376
+ * in `function-body` mode: the local name is never bound, so the document
377
+ * throws `ReferenceError: ui is not defined` from inside compiled source the
378
+ * author never wrote. Refused here, where the file and the statement are both
379
+ * still in hand. A bundler compiles the same document to a module, where the
380
+ * namespace import is ordinary and works.
381
+ */
382
+ function namespaceImport(local, statement, where) {
383
+ return /* @__PURE__ */ new Error(`"${where}" imports \`* as ${local}\` in \`${statement}\`, which cannot be resolved outside a bundler. Import the components by name instead, or add contentLayer() from @pitlane/content/vite so the build compiles this collection.`);
384
+ }
385
+ function unreadable(statement, where) {
386
+ return /* @__PURE__ */ new Error(`Could not read the import \`${statement}\` in "${where}". Rendering an MDX entry outside a bundler resolves its imports directly, which needs an ordinary import statement.`);
387
+ }
388
+ //#endregion
389
+ export { readEsm };
@@ -0,0 +1,57 @@
1
+ //#region src/parse.ts
2
+ /**
3
+ * An error already framed with the collection that produced it.
4
+ *
5
+ * The marker is a type rather than a substring of the message: matching on
6
+ * wording couples every thrower to `annotate`'s idea of what a framed message
7
+ * looks like, and rewording one of them would double-wrap or skip silently.
8
+ */
9
+ var ContentError = class extends Error {
10
+ collection;
11
+ constructor(collection, message, options) {
12
+ super(message, options);
13
+ this.name = "ContentError";
14
+ this.collection = collection;
15
+ }
16
+ };
17
+ /**
18
+ * Validates one entry's data against its collection's schema.
19
+ *
20
+ * The thrown message names everything needed to find the file and fix it: the
21
+ * entry, the collection, the path when there is one, and one line per issue.
22
+ * This is the earliest point the data exists, so it is the earliest point the
23
+ * mistake can be reported.
24
+ */
25
+ async function parseEntryData(schema, target, data) {
26
+ let result = await schema["~standard"].validate(data);
27
+ if (result.issues) throw new ContentError(target.collection, parseFailure(target, result.issues));
28
+ return result.value;
29
+ }
30
+ function parseFailure(target, issues) {
31
+ let where = target.filePath ? ` (${target.filePath})` : "";
32
+ let lines = issues.map((issue) => ` - ${issuePath(issue)}${issue.message}`);
33
+ return [`Failed to parse entry "${target.id}" in collection "${target.collection}"${where}:`, ...lines].join("\n");
34
+ }
35
+ function issuePath(issue) {
36
+ if (!issue.path || issue.path.length === 0) return "";
37
+ return `${issue.path.map((segment) => typeof segment === "object" ? String(segment.key) : String(segment)).join(".")}: `;
38
+ }
39
+ /**
40
+ * Frames the one import failure `render()` can produce on its own.
41
+ *
42
+ * The renderer is loaded on demand so that reading a collection needs no
43
+ * framework, which means an application that never renders never installs
44
+ * one. When it does render, Node reports a resolver path and a specifier and
45
+ * nothing about content. Anything else that fails while the renderer loads is
46
+ * left alone, so a real error inside it is not disguised as a missing install.
47
+ *
48
+ * @param where The entry being rendered, for the message.
49
+ * @param cause The failure the dynamic import threw.
50
+ * @returns A framed error, or `undefined` when this was some other failure.
51
+ */
52
+ function missingRenderer(where, cause) {
53
+ if (!(cause instanceof Error && cause.code === "ERR_MODULE_NOT_FOUND" && /'remix(?:\/[^']*)?'/.test(cause.message))) return void 0;
54
+ return new Error(`Rendering "${where}" needs the peer dependency "remix"; install it, or read the entry's data without calling render().`, { cause });
55
+ }
56
+ //#endregion
57
+ export { missingRenderer as n, parseEntryData as r, ContentError as t };
@@ -0,0 +1,65 @@
1
+ import { g as PrebuiltCollections } from "./types-xSR1WTBq.mjs";
2
+ //#region src/prebuild.d.ts
3
+ /**
4
+ * The channel between `contentLayer()` and `createContent`.
5
+ *
6
+ * The plugin runs the application's content module through Vite's module
7
+ * runner, which evaluates it in a realm of its own. A module-scoped variable
8
+ * would therefore be a different variable on each side, so the handshake lives
9
+ * on globals whose names `symbols.ts` owns.
10
+ */
11
+ interface Channel {
12
+ collections: Map<string, unknown[]>;
13
+ watched: Set<string>;
14
+ /** Collections whose loader configured runtime rendering options. */
15
+ configuredSatteri: Set<string>;
16
+ tasks: (() => Promise<void>)[];
17
+ root: string;
18
+ }
19
+ /** Opens prebuild mode. Called by `contentLayer()` before it runs the entry. */
20
+ declare function openPrebuild(root: string): Channel;
21
+ /** Closes prebuild mode, so a later `createContent` in this process is normal. */
22
+ declare function closePrebuild(): void;
23
+ /** Whether a build is collecting declarations and prebuilding their entries. */
24
+ declare function isPrebuilding(): boolean;
25
+ /**
26
+ * The root loaders resolve relative paths against.
27
+ *
28
+ * A prebuilt collection never asks, so the fallback only runs where a loader
29
+ * is about to read the filesystem anyway. `process` is reached defensively all
30
+ * the same: a host without it should hear about the missing filesystem from
31
+ * the loader, which names the collection, rather than about a missing global.
32
+ */
33
+ declare function contentRoot(): string;
34
+ /** Defers loading until the plugin finishes evaluating the declaration module. */
35
+ declare function registerPrebuild(populate: () => Promise<void>): void;
36
+ /** Records one collection's entries for `contentLayer()` to read back. */
37
+ declare function recordPrebuilt(collection: string, entries: unknown[]): void;
38
+ /**
39
+ * Records the paths a loader says it reads, so `contentLayer()` can watch them.
40
+ *
41
+ * This comes from the loader rather than from the entries it produced: a
42
+ * collection that currently matches nothing has no file paths to infer from,
43
+ * and it is exactly the collection whose first file needs to be noticed.
44
+ */
45
+ declare function recordWatched(paths: readonly string[]): void;
46
+ /**
47
+ * Records that a prebuilt collection's loader configured `options.satteri`.
48
+ *
49
+ * Those options only take effect when the collection renders at runtime, so a
50
+ * prebuilt collection carrying them has a plugin list that silently applies on
51
+ * some hosts and not others. `contentLayer()` warns rather than let that pass.
52
+ */
53
+ declare function recordConfiguredSatteri(collection: string): void;
54
+ /**
55
+ * The manifest `contentLayer()` emitted, or `null` when nothing prebuilt anything.
56
+ *
57
+ * A prebuild reads no manifest, because it is producing one. Without that rule
58
+ * a process that has already imported a prebuilt bundle — two builds in one
59
+ * test run, a build after a preview — would hand the next prebuild the previous
60
+ * manifest, the loaders would look as though they had already run, and the
61
+ * collection would be emitted empty.
62
+ */
63
+ declare function prebuiltManifest(): PrebuiltCollections | null;
64
+ //#endregion
65
+ export { closePrebuild, contentRoot, isPrebuilding, openPrebuild, prebuiltManifest, recordConfiguredSatteri, recordPrebuilt, recordWatched, registerPrebuild };
@@ -0,0 +1,81 @@
1
+ import { n as PREBUILD_CHANNEL, r as PREBUILT_MANIFEST } from "./symbols-DmXlrDbX.mjs";
2
+ import "./manifest.mjs";
3
+ //#region src/prebuild.ts
4
+ /** Opens prebuild mode. Called by `contentLayer()` before it runs the entry. */
5
+ function openPrebuild(root) {
6
+ let channel = {
7
+ collections: /* @__PURE__ */ new Map(),
8
+ watched: /* @__PURE__ */ new Set(),
9
+ configuredSatteri: /* @__PURE__ */ new Set(),
10
+ tasks: [],
11
+ root
12
+ };
13
+ globalThis[PREBUILD_CHANNEL] = channel;
14
+ return channel;
15
+ }
16
+ /** Closes prebuild mode, so a later `createContent` in this process is normal. */
17
+ function closePrebuild() {
18
+ delete globalThis[PREBUILD_CHANNEL];
19
+ }
20
+ /** Whether a build is collecting declarations and prebuilding their entries. */
21
+ function isPrebuilding() {
22
+ return globalThis[PREBUILD_CHANNEL] !== void 0;
23
+ }
24
+ /**
25
+ * The root loaders resolve relative paths against.
26
+ *
27
+ * A prebuilt collection never asks, so the fallback only runs where a loader
28
+ * is about to read the filesystem anyway. `process` is reached defensively all
29
+ * the same: a host without it should hear about the missing filesystem from
30
+ * the loader, which names the collection, rather than about a missing global.
31
+ */
32
+ function contentRoot() {
33
+ let root = globalThis[PREBUILD_CHANNEL]?.root;
34
+ if (root !== void 0) return root;
35
+ return typeof process === "undefined" ? "/" : process.cwd();
36
+ }
37
+ /** Defers loading until the plugin finishes evaluating the declaration module. */
38
+ function registerPrebuild(populate) {
39
+ globalThis[PREBUILD_CHANNEL]?.tasks.push(populate);
40
+ }
41
+ /** Records one collection's entries for `contentLayer()` to read back. */
42
+ function recordPrebuilt(collection, entries) {
43
+ globalThis[PREBUILD_CHANNEL]?.collections.set(collection, entries);
44
+ }
45
+ /**
46
+ * Records the paths a loader says it reads, so `contentLayer()` can watch them.
47
+ *
48
+ * This comes from the loader rather than from the entries it produced: a
49
+ * collection that currently matches nothing has no file paths to infer from,
50
+ * and it is exactly the collection whose first file needs to be noticed.
51
+ */
52
+ function recordWatched(paths) {
53
+ let channel = globalThis[PREBUILD_CHANNEL];
54
+ if (!channel) return;
55
+ for (let path of paths) channel.watched.add(path);
56
+ }
57
+ /**
58
+ * Records that a prebuilt collection's loader configured `options.satteri`.
59
+ *
60
+ * Those options only take effect when the collection renders at runtime, so a
61
+ * prebuilt collection carrying them has a plugin list that silently applies on
62
+ * some hosts and not others. `contentLayer()` warns rather than let that pass.
63
+ */
64
+ function recordConfiguredSatteri(collection) {
65
+ globalThis[PREBUILD_CHANNEL]?.configuredSatteri.add(collection);
66
+ }
67
+ /**
68
+ * The manifest `contentLayer()` emitted, or `null` when nothing prebuilt anything.
69
+ *
70
+ * A prebuild reads no manifest, because it is producing one. Without that rule
71
+ * a process that has already imported a prebuilt bundle — two builds in one
72
+ * test run, a build after a preview — would hand the next prebuild the previous
73
+ * manifest, the loaders would look as though they had already run, and the
74
+ * collection would be emitted empty.
75
+ */
76
+ function prebuiltManifest() {
77
+ if (isPrebuilding()) return null;
78
+ return globalThis[PREBUILT_MANIFEST] ?? null;
79
+ }
80
+ //#endregion
81
+ export { closePrebuild, contentRoot, isPrebuilding, openPrebuild, prebuiltManifest, recordConfiguredSatteri, recordPrebuilt, recordWatched, registerPrebuild };