@replohq/sdk 0.11.0 → 0.14.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.
Files changed (46) hide show
  1. package/_vendor/replo-utils/lib/json.mjs +2 -42
  2. package/_vendor/replo-utils/lib/misc.mjs +3 -0
  3. package/_vendor/schemas/contentCollection.d.ts +231 -0
  4. package/_vendor/schemas/contentCollection.mjs +2 -0
  5. package/_vendor/schemas/contentEntry.d.ts +53 -0
  6. package/_vendor/schemas/contentEntry.mjs +3 -0
  7. package/_vendor/schemas/contentFrontmatter.d.ts +18 -0
  8. package/_vendor/schemas/contentManifest.d.ts +44 -0
  9. package/_vendor/schemas/contentManifest.mjs +197 -0
  10. package/_vendor/schemas/generated/consent.d.ts +2 -2
  11. package/_vendor/schemas/money.mjs +2 -0
  12. package/_vendor/schemas/routing/locale.mjs +2 -0
  13. package/_vendor/schemas/routing/rules.mjs +2 -0
  14. package/analytics/get-analytics-sinks.js +5 -1
  15. package/analytics/get-analytics-sinks.js.map +2 -2
  16. package/analytics/sinks/converge-sink.d.ts +21 -0
  17. package/analytics/sinks/converge-sink.js +51 -0
  18. package/analytics/sinks/converge-sink.js.map +7 -0
  19. package/analytics/sinks/northbeam-sink.d.ts +20 -0
  20. package/analytics/sinks/northbeam-sink.js +41 -0
  21. package/analytics/sinks/northbeam-sink.js.map +7 -0
  22. package/cart/cart-actions.js +13 -10
  23. package/cart/cart-actions.js.map +2 -2
  24. package/cart/cart-provider.js +5 -4
  25. package/cart/cart-provider.js.map +2 -2
  26. package/cart/gateways/cart-constants.d.ts +14 -0
  27. package/cart/gateways/cart-constants.js +23 -1
  28. package/cart/gateways/cart-constants.js.map +2 -2
  29. package/cart/utils/cart-utils.d.ts +8 -0
  30. package/cart/utils/cart-utils.js +13 -1
  31. package/cart/utils/cart-utils.js.map +2 -2
  32. package/cart/utils/cookie-cart-persistence.js +4 -21
  33. package/cart/utils/cookie-cart-persistence.js.map +2 -2
  34. package/chunk-AL3F36PH.mjs +1514 -0
  35. package/chunk-GV5FY7ZZ.mjs +89 -0
  36. package/chunk-I23U2PUI.mjs +151 -0
  37. package/chunk-IQ5GRTXO.mjs +182 -0
  38. package/chunk-VKO7P3ED.mjs +41 -0
  39. package/consent/script-snippets.js +19 -1
  40. package/consent/script-snippets.js.map +2 -2
  41. package/consent/types.d.ts +2 -2
  42. package/content.d.ts +126 -0
  43. package/content.js +594 -0
  44. package/content.js.map +7 -0
  45. package/lib/buildMetadata.js +3 -3
  46. package/package.json +12 -4
package/content.d.ts ADDED
@@ -0,0 +1,126 @@
1
+ import "server-only";
2
+ import type { Metadata } from "next";
3
+ import type { ReactNode } from "react";
4
+ import type { ContentManifest } from "./_vendor/schemas/contentManifest";
5
+ import { contentCollectionFieldSchema } from "./_vendor/schemas/contentCollection";
6
+ import { z } from "zod";
7
+ /** Public name for the shared collection-field contract. */
8
+ export type ContentField = z.infer<typeof contentCollectionFieldSchema>;
9
+ export interface ContentEntry {
10
+ collection: {
11
+ name: string;
12
+ description: string;
13
+ slug: string;
14
+ };
15
+ fields: ContentField[];
16
+ slug: string;
17
+ body: string;
18
+ values: Record<string, unknown>;
19
+ }
20
+ /** One entry's slot in a collection listing. */
21
+ export interface ContentCollectionEntry {
22
+ slug: string;
23
+ title: string;
24
+ excerpt: string | null;
25
+ coverImage: string | null;
26
+ coverImageAlt: string | null;
27
+ publishedAt: string | null;
28
+ status: "draft" | "published";
29
+ }
30
+ export interface ContentCollection {
31
+ slug: string;
32
+ name: string;
33
+ description: string;
34
+ entries: ContentCollectionEntry[];
35
+ }
36
+ /** An ordered content field paired with this entry's value for it. */
37
+ export interface ContentFieldWithValue {
38
+ field: ContentField;
39
+ value: unknown;
40
+ }
41
+ export interface ContentSitemapEntry {
42
+ url: string;
43
+ lastModified?: Date;
44
+ }
45
+ export interface ContentSource {
46
+ read(relativePath: string): Promise<string | null>;
47
+ listCollections(): Promise<string[]>;
48
+ listEntries(collection: string): Promise<string[]>;
49
+ }
50
+ interface ContentOptions {
51
+ contentSource?: ContentSource;
52
+ contentDirectory?: string;
53
+ contentManifest?: ContentManifest;
54
+ }
55
+ export declare function createContentSource({ contentDirectory, contentManifest, }?: {
56
+ contentDirectory?: string;
57
+ contentManifest?: ContentManifest;
58
+ }): ContentSource;
59
+ export declare function getContentEntry({ collection, slug, ...contentOptions }: {
60
+ collection: string;
61
+ slug: string;
62
+ } & ContentOptions): Promise<ContentEntry>;
63
+ /** Static params for one collection's `[slug]` detail route. */
64
+ export declare function getContentCollectionStaticParams({ collection, ...contentOptions }: {
65
+ collection: string;
66
+ } & ContentOptions): Promise<{
67
+ slug: string;
68
+ }[]>;
69
+ /**
70
+ * A collection and its renderable entries for a listing page. Uses the
71
+ * not-found contract when the collection is missing or archived.
72
+ */
73
+ export declare function getContentCollection({ collection, ...contentOptions }: {
74
+ collection: string;
75
+ } & ContentOptions): Promise<ContentCollection>;
76
+ /**
77
+ * Per-entry head metadata (title/description/OpenGraph/Twitter) from the
78
+ * entry's SEO fields with content fallbacks. Returns an empty object when the
79
+ * entry is not renderable, so `generateMetadata` can call it unconditionally.
80
+ */
81
+ export declare function getContentEntryMetadata({ collection, slug, ...contentOptions }: {
82
+ collection: string;
83
+ slug: string;
84
+ } & ContentOptions): Promise<Metadata>;
85
+ /**
86
+ * Structured data for an entry as a JSON-LD script tag. Serialization is
87
+ * script-safe (`<` escaped) so entry content can never break out of the tag.
88
+ * The type is a deliberately neutral `WebPage`; collections have no schema
89
+ * type of their own.
90
+ */
91
+ export declare function ContentEntryJsonLd({ entry }: {
92
+ entry: ContentEntry;
93
+ }): import("react/jsx-runtime").JSX.Element;
94
+ /**
95
+ * Absolute sitemap entries for every collection listing and renderable entry.
96
+ * `origin` comes from the caller (e.g. the request's platform-validated Host)
97
+ * because site origins are not fixed at build time.
98
+ */
99
+ export declare function getContentSitemapEntries({ origin, ...contentOptions }: {
100
+ origin: string;
101
+ } & ContentOptions): Promise<ContentSitemapEntry[]>;
102
+ /**
103
+ * The full sitemap XML document for a site's content (home, collection
104
+ * listings, entries). Kept here so the generated sitemap route stays a tiny,
105
+ * restylable-free shell around one call.
106
+ */
107
+ export declare function getContentSitemapXml({ origin, ...contentOptions }: {
108
+ origin: string;
109
+ } & ContentOptions): Promise<string>;
110
+ /**
111
+ * The entry's renderable content fields in collection-settings order, paired
112
+ * with values. This is the published half of the shared ordering contract in
113
+ * `schemas/contentCollection`; empty values are omitted so templates never render
114
+ * bare labels.
115
+ */
116
+ export declare function getContentFields(entry: ContentEntry): ContentFieldWithValue[];
117
+ export declare function ContentFieldValue({ field, value, }: {
118
+ field: ContentField;
119
+ value: unknown;
120
+ }): ReactNode;
121
+ /** A related entry resolved to a public link; unresolvable refs stay plain text. */
122
+ export interface ContentRelatedLink {
123
+ title: string;
124
+ href: string;
125
+ }
126
+ export {};
package/content.js ADDED
@@ -0,0 +1,594 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import "server-only";
3
+ import { readdir, readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { Fragment } from "react";
6
+ import { notFound } from "next/navigation";
7
+ import ReactMarkdown from "react-markdown";
8
+ import remarkGfm from "remark-gfm";
9
+ import { parseJsonOrNull } from "./_vendor/replo-utils/lib/json.mjs";
10
+ import { isNotNullish } from "./_vendor/replo-utils/lib/misc.mjs";
11
+ import {
12
+ CONTENT_COLLECTION_SCHEMA_FILENAME,
13
+ CONTENT_DIRNAME,
14
+ CONTENT_ENTRY_FILE_EXTENSION,
15
+ CONTENT_RELATED_VALUE_STORAGE_KEY,
16
+ contentCollectionFileSchema,
17
+ getPublishedContentFields,
18
+ isContentEntryPubliclyVisible,
19
+ isSafeContentUrl,
20
+ isValidContentSlug,
21
+ parseContentEntryRef
22
+ } from "./_vendor/schemas/contentCollection.mjs";
23
+ import {
24
+ getContentEntryFieldValidationIssues,
25
+ getContentRenderableEntryValues,
26
+ parseContentEntryDocument
27
+ } from "./_vendor/schemas/contentEntry.mjs";
28
+ import {
29
+ getContentManifestCollectionSlugs,
30
+ getContentManifestEntrySlugs
31
+ } from "./_vendor/schemas/contentManifest.mjs";
32
+ import { z } from "zod";
33
+ function createContentSource({
34
+ contentDirectory = getDefaultContentDirectory(),
35
+ contentManifest
36
+ } = {}) {
37
+ if (contentManifest) {
38
+ return {
39
+ read: async (relativePath) => {
40
+ return contentManifest.files[relativePath] ?? null;
41
+ },
42
+ listCollections: async () => {
43
+ return getContentManifestCollectionSlugs(contentManifest);
44
+ },
45
+ listEntries: async (collection) => {
46
+ return getContentManifestEntrySlugs({ collection, contentManifest });
47
+ }
48
+ };
49
+ }
50
+ return {
51
+ read: (relativePath) => {
52
+ return readTextFileOrNull(join(contentDirectory, relativePath));
53
+ },
54
+ listCollections: async () => {
55
+ return (await readDirectoryOrEmpty(contentDirectory)).flatMap((entry) => {
56
+ return entry.isDirectory() ? [entry.name] : [];
57
+ });
58
+ },
59
+ listEntries: async (collection) => {
60
+ return (await readDirectoryOrEmpty(join(contentDirectory, collection))).flatMap((entry) => {
61
+ return entry.isFile() && entry.name.endsWith(CONTENT_ENTRY_FILE_EXTENSION) ? [entry.name.slice(0, -CONTENT_ENTRY_FILE_EXTENSION.length)] : [];
62
+ });
63
+ }
64
+ };
65
+ }
66
+ async function getContentEntry({
67
+ collection,
68
+ slug,
69
+ ...contentOptions
70
+ }) {
71
+ const entry = await findContentEntry({
72
+ collection,
73
+ slug,
74
+ contentSource: getContentSource(contentOptions)
75
+ });
76
+ if (!entry) {
77
+ notFound();
78
+ }
79
+ return entry;
80
+ }
81
+ async function getContentCollectionStaticParams({
82
+ collection,
83
+ ...contentOptions
84
+ }) {
85
+ const contentSource = getContentSource(contentOptions);
86
+ const collectionSchema = await readActiveCollectionSchema({
87
+ collection,
88
+ contentSource
89
+ });
90
+ if (!collectionSchema) {
91
+ return [];
92
+ }
93
+ const entries = await listVisibleEntries({
94
+ collectionSchema,
95
+ collection,
96
+ contentSource
97
+ });
98
+ return entries.map((entry) => entry.slug).sort().map((slug) => ({ slug }));
99
+ }
100
+ async function getContentCollection({
101
+ collection,
102
+ ...contentOptions
103
+ }) {
104
+ const loaded = await findContentCollection({
105
+ collection,
106
+ contentSource: getContentSource(contentOptions)
107
+ });
108
+ if (!loaded) {
109
+ notFound();
110
+ }
111
+ return loaded;
112
+ }
113
+ async function getContentEntryMetadata({
114
+ collection,
115
+ slug,
116
+ ...contentOptions
117
+ }) {
118
+ const entry = await findContentEntry({
119
+ collection,
120
+ slug,
121
+ contentSource: getContentSource(contentOptions)
122
+ });
123
+ if (!entry) {
124
+ return {};
125
+ }
126
+ const title = readNonEmptyString(entry.values.seoTitle) ?? readNonEmptyString(entry.values.title) ?? entry.collection.name;
127
+ const description = readNonEmptyString(entry.values.seoDescription) ?? readNonEmptyString(entry.values.excerpt);
128
+ const image = readSafeImageUrl(entry.values.coverImage);
129
+ return {
130
+ title,
131
+ ...description === null ? {} : { description },
132
+ openGraph: {
133
+ title,
134
+ ...description === null ? {} : { description },
135
+ type: "article",
136
+ ...image === null ? {} : { images: [image] }
137
+ },
138
+ twitter: {
139
+ card: image === null ? "summary" : "summary_large_image",
140
+ title,
141
+ ...description === null ? {} : { description },
142
+ ...image === null ? {} : { images: [image] }
143
+ }
144
+ };
145
+ }
146
+ function ContentEntryJsonLd({ entry }) {
147
+ const title = readNonEmptyString(entry.values.seoTitle) ?? readNonEmptyString(entry.values.title) ?? entry.collection.name;
148
+ const description = readNonEmptyString(entry.values.seoDescription) ?? readNonEmptyString(entry.values.excerpt);
149
+ const image = readSafeImageUrl(entry.values.coverImage);
150
+ const datePublished = readIsoDate(entry.values.publishedAt);
151
+ const dateModified = readIsoDate(entry.values.updatedAt);
152
+ const jsonLd = {
153
+ "@context": "https://schema.org",
154
+ "@type": "WebPage",
155
+ name: title,
156
+ ...description === null ? {} : { description },
157
+ ...image === null ? {} : { image },
158
+ ...datePublished === null ? {} : { datePublished },
159
+ ...dateModified === null ? {} : { dateModified }
160
+ };
161
+ return /* @__PURE__ */ jsx(
162
+ "script",
163
+ {
164
+ type: "application/ld+json",
165
+ dangerouslySetInnerHTML: { __html: serializeJsonLd(jsonLd) }
166
+ }
167
+ );
168
+ }
169
+ async function getContentSitemapEntries({
170
+ origin,
171
+ ...contentOptions
172
+ }) {
173
+ const base = origin.replace(/\/+$/u, "");
174
+ const entries = [];
175
+ const contentSource = getContentSource(contentOptions);
176
+ for (const collection of await listCollectionSlugs({ contentSource })) {
177
+ const collectionSchema = await readActiveCollectionSchema({
178
+ collection,
179
+ contentSource
180
+ });
181
+ if (!collectionSchema) {
182
+ continue;
183
+ }
184
+ if (collectionSchema.hasListingPage !== false) {
185
+ entries.push({ url: `${base}/${collection}` });
186
+ }
187
+ const visibleEntries = await listVisibleEntries({
188
+ collectionSchema,
189
+ collection,
190
+ contentSource
191
+ });
192
+ visibleEntries.sort((left, right) => left.slug.localeCompare(right.slug));
193
+ for (const entry of visibleEntries) {
194
+ const lastModified = readValidDate(entry.values.updatedAt);
195
+ entries.push({
196
+ url: `${base}/${collection}/${entry.slug}`,
197
+ ...lastModified === null ? {} : { lastModified }
198
+ });
199
+ }
200
+ }
201
+ return entries;
202
+ }
203
+ async function getContentSitemapXml({
204
+ origin,
205
+ ...contentOptions
206
+ }) {
207
+ const base = origin.replace(/\/+$/u, "");
208
+ const entries = [
209
+ { url: base },
210
+ ...await getContentSitemapEntries({
211
+ origin: base,
212
+ contentSource: getContentSource(contentOptions)
213
+ })
214
+ ];
215
+ const items = entries.map(({ url, lastModified }) => {
216
+ const lastmod = lastModified ? `<lastmod>${lastModified.toISOString()}</lastmod>` : "";
217
+ return `<url><loc>${escapeXml(url)}</loc>${lastmod}</url>`;
218
+ });
219
+ return `<?xml version="1.0" encoding="UTF-8"?>
220
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">${items.join(
221
+ ""
222
+ )}</urlset>
223
+ `;
224
+ }
225
+ function getContentFields(entry) {
226
+ return getPublishedContentFields(entry.fields).map((field) => ({ field, value: entry.values[field.id] })).filter(({ value }) => {
227
+ return value !== void 0 && value !== null && value !== "";
228
+ });
229
+ }
230
+ function ContentFieldValue({
231
+ field,
232
+ value
233
+ }) {
234
+ if (field.type === "rich-text" && typeof value === "string") {
235
+ return /* @__PURE__ */ jsx(SafeMarkdown, { source: value });
236
+ }
237
+ if (field.type === "image" && typeof value === "string") {
238
+ return /* @__PURE__ */ jsx(
239
+ "img",
240
+ {
241
+ alt: field.label,
242
+ src: value,
243
+ style: { display: "block", height: "auto", maxWidth: "100%" }
244
+ }
245
+ );
246
+ }
247
+ if (field.type === "url" && typeof value === "string") {
248
+ return /* @__PURE__ */ jsx("a", { href: value, style: CONTENT_LINK_STYLE, children: value });
249
+ }
250
+ if (field.type === "date" && typeof value === "string") {
251
+ const parsed = new Date(value);
252
+ const label = Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString("en-US", {
253
+ year: "numeric",
254
+ month: "long",
255
+ day: "numeric"
256
+ });
257
+ return /* @__PURE__ */ jsx("time", { dateTime: value, children: label });
258
+ }
259
+ if (field.type === "boolean" && typeof value === "boolean") {
260
+ return value ? "Yes" : "No";
261
+ }
262
+ if (field.type === "related" && Array.isArray(value)) {
263
+ const items = value.filter(
264
+ (item) => isContentRelatedLink(item) || typeof item === "string"
265
+ );
266
+ if (items.length === 0) {
267
+ return null;
268
+ }
269
+ return items.map((item, index) => /* @__PURE__ */ jsxs(Fragment, { children: [
270
+ index > 0 && ", ",
271
+ isContentRelatedLink(item) ? /* @__PURE__ */ jsx("a", { href: item.href, style: CONTENT_LINK_STYLE, children: item.title }) : String(item)
272
+ ] }, isContentRelatedLink(item) ? item.href : String(item)));
273
+ }
274
+ if (field.type === "tags" && Array.isArray(value)) {
275
+ return value.join(", ");
276
+ }
277
+ return typeof value === "string" ? value : null;
278
+ }
279
+ async function findContentCollection({
280
+ collection,
281
+ contentSource
282
+ }) {
283
+ const collectionSchema = await readActiveCollectionSchema({
284
+ collection,
285
+ contentSource
286
+ });
287
+ if (!collectionSchema) {
288
+ return null;
289
+ }
290
+ const visibleEntries = await listVisibleEntries({
291
+ collectionSchema,
292
+ collection,
293
+ contentSource
294
+ });
295
+ const entries = visibleEntries.map((entry) => {
296
+ return {
297
+ slug: entry.slug,
298
+ title: readNonEmptyString(entry.values.title) ?? entry.slug,
299
+ excerpt: readNonEmptyString(entry.values.excerpt),
300
+ coverImage: readSafeImageUrl(entry.values.coverImage),
301
+ coverImageAlt: readNonEmptyString(entry.values.coverImageAlt),
302
+ publishedAt: readIsoDate(entry.values.publishedAt),
303
+ status: entry.values.status === "draft" ? "draft" : "published"
304
+ };
305
+ });
306
+ entries.sort((left, right) => {
307
+ const leftTime = left.publishedAt ? Date.parse(left.publishedAt) : 0;
308
+ const rightTime = right.publishedAt ? Date.parse(right.publishedAt) : 0;
309
+ return rightTime - leftTime || left.title.localeCompare(right.title);
310
+ });
311
+ return {
312
+ slug: collection,
313
+ name: collectionSchema.name,
314
+ description: collectionSchema.description,
315
+ entries
316
+ };
317
+ }
318
+ async function findContentEntry({
319
+ collection,
320
+ slug,
321
+ contentSource
322
+ }) {
323
+ const collectionSchema = await readActiveCollectionSchema({
324
+ collection,
325
+ contentSource
326
+ });
327
+ if (!collectionSchema) {
328
+ return null;
329
+ }
330
+ return readVisibleEntry({
331
+ collectionSchema,
332
+ collection,
333
+ slug,
334
+ contentSource
335
+ });
336
+ }
337
+ async function listCollectionSlugs({
338
+ contentSource
339
+ }) {
340
+ return (await contentSource.listCollections()).filter(isValidContentSlug).sort();
341
+ }
342
+ async function listVisibleEntries({
343
+ collectionSchema,
344
+ collection,
345
+ contentSource
346
+ }) {
347
+ const slugs = await contentSource.listEntries(collection);
348
+ const entries = await Promise.all(
349
+ slugs.map((slug) => {
350
+ return readVisibleEntry({
351
+ collectionSchema,
352
+ collection,
353
+ slug,
354
+ contentSource
355
+ });
356
+ })
357
+ );
358
+ return entries.filter(isNotNullish);
359
+ }
360
+ async function readActiveCollectionSchema({
361
+ collection,
362
+ contentSource
363
+ }) {
364
+ if (!isValidContentSlug(collection)) {
365
+ return null;
366
+ }
367
+ const rawSchema = await contentSource.read(
368
+ `${collection}/${CONTENT_COLLECTION_SCHEMA_FILENAME}`
369
+ );
370
+ if (!rawSchema) {
371
+ return null;
372
+ }
373
+ const parsedJson = parseJsonOrNull(rawSchema);
374
+ if (parsedJson === null) {
375
+ return null;
376
+ }
377
+ const result = contentCollectionFileSchema.safeParse(parsedJson);
378
+ if (!result.success || isNotNullish(result.data.archivedAt)) {
379
+ return null;
380
+ }
381
+ return result.data;
382
+ }
383
+ async function readVisibleEntryDocument({
384
+ collection,
385
+ slug,
386
+ contentSource
387
+ }) {
388
+ if (!isValidContentSlug(slug)) {
389
+ return null;
390
+ }
391
+ const rawEntry = await contentSource.read(
392
+ `${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`
393
+ );
394
+ if (!rawEntry) {
395
+ return null;
396
+ }
397
+ const parsedDocument = parseContentEntryDocument(rawEntry);
398
+ if (parsedDocument.status === "invalid") {
399
+ return null;
400
+ }
401
+ const document = parsedDocument.document;
402
+ const allowDrafts = process.env.NODE_ENV === "development";
403
+ if (!isContentEntryPubliclyVisible({
404
+ status: document.values.status,
405
+ archivedAt: document.values.archivedAt,
406
+ allowDrafts
407
+ })) {
408
+ return null;
409
+ }
410
+ return document;
411
+ }
412
+ async function readVisibleEntry({
413
+ collectionSchema,
414
+ collection,
415
+ slug,
416
+ contentSource
417
+ }) {
418
+ const document = await readVisibleEntryDocument({
419
+ collection,
420
+ slug,
421
+ contentSource
422
+ });
423
+ if (!document) {
424
+ return null;
425
+ }
426
+ const values = getContentRenderableEntryValues({
427
+ document,
428
+ slug
429
+ });
430
+ if (getContentEntryFieldValidationIssues({
431
+ fields: collectionSchema.fields,
432
+ values,
433
+ requireRequiredFields: document.values.status !== "draft"
434
+ }).length > 0) {
435
+ return null;
436
+ }
437
+ const fields = [...collectionSchema.fields];
438
+ const hasRelatedField = fields.some((field) => field.type === "related");
439
+ const storedRelated = values[CONTENT_RELATED_VALUE_STORAGE_KEY];
440
+ if (!hasRelatedField && Array.isArray(storedRelated) && storedRelated.length > 0) {
441
+ fields.push({
442
+ id: CONTENT_RELATED_VALUE_STORAGE_KEY,
443
+ label: "Related content",
444
+ type: "related",
445
+ required: false,
446
+ enabled: true,
447
+ builtin: true
448
+ });
449
+ }
450
+ return {
451
+ collection: {
452
+ name: collectionSchema.name,
453
+ description: collectionSchema.description,
454
+ slug: collection
455
+ },
456
+ fields,
457
+ slug,
458
+ body: document.body,
459
+ values: await resolveRelatedFieldValues({
460
+ fields,
461
+ values,
462
+ contentSource
463
+ })
464
+ };
465
+ }
466
+ async function resolveRelatedFieldValues({
467
+ fields,
468
+ values,
469
+ contentSource
470
+ }) {
471
+ const relatedFields = fields.filter((field) => field.type === "related");
472
+ if (relatedFields.length === 0) {
473
+ return values;
474
+ }
475
+ const resolved = { ...values };
476
+ for (const field of relatedFields) {
477
+ const raw = resolved[field.id];
478
+ if (!Array.isArray(raw)) {
479
+ continue;
480
+ }
481
+ const links = [];
482
+ for (const item of raw) {
483
+ if (typeof item !== "string") {
484
+ continue;
485
+ }
486
+ const ref = parseContentEntryRef(item);
487
+ if (!ref) {
488
+ continue;
489
+ }
490
+ const { collectionSlug: targetCollection, entrySlug: targetSlug } = ref;
491
+ const collectionSchema = await readActiveCollectionSchema({
492
+ collection: targetCollection,
493
+ contentSource
494
+ });
495
+ if (!collectionSchema) {
496
+ continue;
497
+ }
498
+ const target = await readVisibleEntryDocument({
499
+ collection: targetCollection,
500
+ slug: targetSlug,
501
+ contentSource
502
+ });
503
+ if (!target) {
504
+ continue;
505
+ }
506
+ links.push({
507
+ title: readNonEmptyString(target.values.title) ?? targetSlug,
508
+ href: `/${targetCollection}/${targetSlug}`
509
+ });
510
+ }
511
+ resolved[field.id] = links;
512
+ }
513
+ return resolved;
514
+ }
515
+ const contentRelatedLinkSchema = z.object({
516
+ title: z.string(),
517
+ href: z.string()
518
+ });
519
+ function isContentRelatedLink(value) {
520
+ return contentRelatedLinkSchema.safeParse(value).success;
521
+ }
522
+ const CONTENT_LINK_STYLE = { textDecoration: "underline" };
523
+ function SafeMarkdown({ source }) {
524
+ return /* @__PURE__ */ jsx(
525
+ ReactMarkdown,
526
+ {
527
+ remarkPlugins: [remarkGfm],
528
+ skipHtml: true,
529
+ components: {
530
+ a: ({ children, ...props }) => /* @__PURE__ */ jsx("a", { ...props, style: CONTENT_LINK_STYLE, children })
531
+ },
532
+ children: source
533
+ }
534
+ );
535
+ }
536
+ function readNonEmptyString(value) {
537
+ const parsed = z.string().safeParse(value);
538
+ return parsed.success && parsed.data.trim().length > 0 ? parsed.data : null;
539
+ }
540
+ function readSafeImageUrl(value) {
541
+ const url = readNonEmptyString(value);
542
+ return url !== null && isSafeContentUrl(url, { image: true }) ? url : null;
543
+ }
544
+ function readIsoDate(value) {
545
+ const raw = readNonEmptyString(value);
546
+ return raw !== null && !Number.isNaN(Date.parse(raw)) ? raw : null;
547
+ }
548
+ function readValidDate(value) {
549
+ const iso = readIsoDate(value);
550
+ return iso === null ? null : new Date(iso);
551
+ }
552
+ function serializeJsonLd(value) {
553
+ return JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
554
+ }
555
+ function escapeXml(value) {
556
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
557
+ }
558
+ async function readTextFileOrNull(path) {
559
+ try {
560
+ return await readFile(path, "utf8");
561
+ } catch {
562
+ return null;
563
+ }
564
+ }
565
+ async function readDirectoryOrEmpty(path) {
566
+ try {
567
+ return await readdir(path, { withFileTypes: true });
568
+ } catch {
569
+ return [];
570
+ }
571
+ }
572
+ function getContentSource({
573
+ contentSource,
574
+ contentDirectory,
575
+ contentManifest
576
+ }) {
577
+ return contentSource ?? createContentSource({ contentDirectory, contentManifest });
578
+ }
579
+ function getDefaultContentDirectory() {
580
+ return join(process.cwd(), CONTENT_DIRNAME);
581
+ }
582
+ export {
583
+ ContentEntryJsonLd,
584
+ ContentFieldValue,
585
+ createContentSource,
586
+ getContentCollection,
587
+ getContentCollectionStaticParams,
588
+ getContentEntry,
589
+ getContentEntryMetadata,
590
+ getContentFields,
591
+ getContentSitemapEntries,
592
+ getContentSitemapXml
593
+ };
594
+ //# sourceMappingURL=content.js.map