@replohq/sdk 1.0.0 → 1.2.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.
@@ -13,6 +13,14 @@ export declare const CONTENT_DIRNAME = "content";
13
13
  export declare const CONTENT_COLLECTION_SCHEMA_FILENAME = "_collection.json";
14
14
  /** Extension of entry files inside `content/<collection>/`. */
15
15
  export declare const CONTENT_ENTRY_FILE_EXTENSION = ".mdx";
16
+ /**
17
+ * Extension of an entry's machine-owned published copy. `<slug>.mdx` is the
18
+ * editable draft; `<slug>.published.mdx` is written only by publishing and is
19
+ * what production builds serve — the same pair contract as site pages
20
+ * (`page.dev.tsx` / `page.published.tsx`). Slugs can't contain dots, so the
21
+ * suffix can never collide with a real entry slug.
22
+ */
23
+ export declare const CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION = ".published.mdx";
16
24
  /**
17
25
  * Frontmatter key that stores the `relatedContent` field's value (an array of
18
26
  * `<collection>/<entry>` paths). The storage key predates the field id; both
@@ -29,23 +37,41 @@ export declare const CONTENT_SLUG_PATTERN: RegExp;
29
37
  export declare const CONTENT_MAX_SLUG_LENGTH = 100;
30
38
  export declare const contentSlugSchema: z.ZodString;
31
39
  export declare function isValidContentSlug(slug: string): boolean;
32
- export declare const CONTENT_ENTRY_STATUSES: readonly ["draft", "published", "archived"];
33
- export type ContentEntryStatus = (typeof CONTENT_ENTRY_STATUSES)[number];
34
- export declare const contentEntryStatusSchema: z.ZodEnum<{
35
- draft: "draft";
36
- published: "published";
37
- archived: "archived";
38
- }>;
39
40
  /**
40
- * The one public-visibility rule: only published, non-archived entries render
41
- * on the published site. The sandbox preview (`next dev`) and the website
42
- * builder pass `allowDrafts: true` so drafts are previewable before publish.
41
+ * Legacy entry lifecycle values from the retired status-frontmatter model.
42
+ * Only conversion code reads these; visibility is now structural an entry
43
+ * renders publicly iff its `.published.mdx` copy exists.
43
44
  */
44
- export declare function isContentEntryPubliclyVisible({ status, archivedAt, allowDrafts, }: {
45
- status: ContentEntryStatus;
46
- archivedAt: unknown;
47
- allowDrafts: boolean;
48
- }): boolean;
45
+ export declare const LEGACY_CONTENT_ENTRY_STATUSES: readonly ["draft", "published", "archived"];
46
+ export type LegacyContentEntryStatus = (typeof LEGACY_CONTENT_ENTRY_STATUSES)[number];
47
+ /**
48
+ * Classify an entry filename inside `content/<collection>/` as the editable
49
+ * draft or the machine-owned published copy, with the entry slug it belongs
50
+ * to. Null for schema files and anything that isn't a valid entry file.
51
+ */
52
+ export declare function getContentEntryFileSlug(filename: string): {
53
+ slug: string;
54
+ kind: "draft" | "published";
55
+ } | null;
56
+ export type ContentEntryPublishState = "published" | "publishedWithChanges" | "unpublished" | "pendingDelete";
57
+ /**
58
+ * Derive an entry's publish state from which pair halves exist and whether
59
+ * they match — the same derivation site pages use, so the publish checklist
60
+ * and both editors agree on what "has unpublished changes" means.
61
+ */
62
+ export declare function getContentEntryPublishState({ hasDraft, hasPublishedCopy, draftMatchesPublishedCopy, }: {
63
+ hasDraft: boolean;
64
+ hasPublishedCopy: boolean;
65
+ draftMatchesPublishedCopy: boolean;
66
+ }): ContentEntryPublishState;
67
+ /**
68
+ * The public route an entry serves at, and the key selective publish uses to
69
+ * pick it in `promoteRoutes` alongside page routes.
70
+ */
71
+ export declare function getContentEntryRoute({ collectionSlug, entrySlug, }: {
72
+ collectionSlug: string;
73
+ entrySlug: string;
74
+ }): string;
49
75
  export declare const contentCollectionFieldTypeSchema: z.ZodEnum<{
50
76
  boolean: "boolean";
51
77
  status: "status";
@@ -1,2 +1,2 @@
1
- export { CONTENT_BUILTIN_FIELD_ROLES, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_COUPLED_VALUE_PARENTS, CONTENT_DIRNAME, CONTENT_ENTRY_FILE_EXTENSION, CONTENT_ENTRY_STATUSES, CONTENT_MANIFEST_FILENAME, CONTENT_MAX_SLUG_LENGTH, CONTENT_PRIVATE_VALUE_KEYS, CONTENT_RELATED_VALUE_STORAGE_KEY, CONTENT_RESERVED_COLLECTION_SLUGS, CONTENT_RESERVED_ENTRY_SLUGS, CONTENT_SEO_VALUE_KEYS, CONTENT_SLUG_PATTERN, contentCollectionColorSchema, contentCollectionFieldSchema, contentCollectionFieldTypeSchema, contentCollectionFileSchema, contentEntryStatusSchema, contentSlugSchema, formatContentEntryRef, getContentFieldRole, getContentFieldValueSchema, getOrderedContentFields, getPublishedContentFields, isContentEntryPubliclyVisible, isContentPrivateValueKey, isContentReservedCollectionSlug, isContentReservedEntrySlug, isContentSeoValueKey, isSafeContentUrl, isValidContentSlug, parseContentEntryRef } from '../../chunk-IQ5GRTXO.mjs';
1
+ export { CONTENT_BUILTIN_FIELD_ROLES, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_COUPLED_VALUE_PARENTS, CONTENT_DIRNAME, CONTENT_ENTRY_FILE_EXTENSION, CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION, CONTENT_MANIFEST_FILENAME, CONTENT_MAX_SLUG_LENGTH, CONTENT_PRIVATE_VALUE_KEYS, CONTENT_RELATED_VALUE_STORAGE_KEY, CONTENT_RESERVED_COLLECTION_SLUGS, CONTENT_RESERVED_ENTRY_SLUGS, CONTENT_SEO_VALUE_KEYS, CONTENT_SLUG_PATTERN, LEGACY_CONTENT_ENTRY_STATUSES, contentCollectionColorSchema, contentCollectionFieldSchema, contentCollectionFieldTypeSchema, contentCollectionFileSchema, contentSlugSchema, formatContentEntryRef, getContentEntryFileSlug, getContentEntryPublishState, getContentEntryRoute, getContentFieldRole, getContentFieldValueSchema, getOrderedContentFields, getPublishedContentFields, isContentPrivateValueKey, isContentReservedCollectionSlug, isContentReservedEntrySlug, isContentSeoValueKey, isSafeContentUrl, isValidContentSlug, parseContentEntryRef } from '../../chunk-EUQVEPSD.mjs';
2
2
  import '../../chunk-UJCSKKID.mjs';
@@ -1,14 +1,10 @@
1
+ import type { LegacyContentEntryStatus } from "./contentCollection";
1
2
  import { contentCollectionFieldSchema } from "./contentCollection";
2
3
  import { z } from "zod";
3
4
  export declare const CONTENT_MANAGED_ENTRY_VALUE_KEYS: readonly ["slug", "title", "status", "author", "authorInitials", "excerpt", "coverImage", "coverImageAlt", "tags", "updatedAt", "publishedAt", "archivedAt", "seoTitle", "seoDescription", "related", "scratchpad", "backlinks"];
4
5
  export declare const contentEntryFrontmatterSchema: z.ZodObject<{
5
6
  slug: z.ZodOptional<z.ZodUnknown>;
6
7
  title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
7
- status: z.ZodEnum<{
8
- draft: "draft";
9
- published: "published";
10
- archived: "archived";
11
- }>;
12
8
  author: z.ZodOptional<z.ZodNullable<z.ZodString>>;
13
9
  authorInitials: z.ZodOptional<z.ZodNullable<z.ZodString>>;
14
10
  excerpt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
@@ -42,6 +38,17 @@ export declare function getContentRenderableEntryValues({ document, slug, }: {
42
38
  document: ContentEntryDocument;
43
39
  slug: string;
44
40
  }): Record<string, unknown>;
41
+ /**
42
+ * Interpret a raw entry file under the retired status-frontmatter model and
43
+ * produce its pair-model draft: lifecycle keys (`status`, `archivedAt`)
44
+ * stripped, everything else preserved. `legacyStatus` is null when the file
45
+ * was already converted (no status key), and drives whether conversion also
46
+ * writes a published copy. Null when the file doesn't parse at all.
47
+ */
48
+ export declare function convertLegacyContentEntryDocument(raw: string): {
49
+ legacyStatus: LegacyContentEntryStatus | null;
50
+ content: string;
51
+ } | null;
45
52
  export interface ContentEntryFieldValidationIssue {
46
53
  fieldId: string;
47
54
  message: string;
@@ -1,3 +1,3 @@
1
- export { CONTENT_MANAGED_ENTRY_VALUE_KEYS, contentEntryFrontmatterSchema, getContentEntryFieldValidationIssues, getContentRenderableEntryValues, parseContentEntryDocument, serializeContentEntryDocument } from '../../chunk-I23U2PUI.mjs';
2
- import '../../chunk-IQ5GRTXO.mjs';
1
+ export { CONTENT_MANAGED_ENTRY_VALUE_KEYS, contentEntryFrontmatterSchema, convertLegacyContentEntryDocument, getContentEntryFieldValidationIssues, getContentRenderableEntryValues, parseContentEntryDocument, serializeContentEntryDocument } from '../../chunk-7OPH5UFQ.mjs';
2
+ import '../../chunk-EUQVEPSD.mjs';
3
3
  import '../../chunk-UJCSKKID.mjs';
@@ -37,8 +37,7 @@ export declare function getContentManifestEntrySlugs({ collection, contentManife
37
37
  collection: string;
38
38
  contentManifest: ContentManifest;
39
39
  }): string[];
40
- export declare function getContentIndex({ files, allowDrafts, }: {
40
+ export declare function getContentIndex({ files, }: {
41
41
  files: Record<string, string | null>;
42
- allowDrafts: boolean;
43
42
  }): ContentIndexCollection[];
44
43
  export declare function validateContentManifest(contentManifest: ContentManifest): ContentValidationIssue[];
@@ -1,5 +1,5 @@
1
- import { parseContentEntryDocument, getContentRenderableEntryValues, getContentEntryFieldValidationIssues } from '../../chunk-I23U2PUI.mjs';
2
- import { isValidContentSlug, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_ENTRY_FILE_EXTENSION, contentCollectionFileSchema, isContentEntryPubliclyVisible } from '../../chunk-IQ5GRTXO.mjs';
1
+ import { parseContentEntryDocument, getContentRenderableEntryValues, getContentEntryFieldValidationIssues } from '../../chunk-7OPH5UFQ.mjs';
2
+ import { isValidContentSlug, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_ENTRY_FILE_EXTENSION, contentCollectionFileSchema, CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION } from '../../chunk-EUQVEPSD.mjs';
3
3
  import { parseJsonOrNull } from '../../chunk-VKO7P3ED.mjs';
4
4
  import '../../chunk-GV5FY7ZZ.mjs';
5
5
  import '../../chunk-UJCSKKID.mjs';
@@ -52,8 +52,7 @@ function getContentManifestEntrySlugs({
52
52
  });
53
53
  }
54
54
  function getContentIndex({
55
- files,
56
- allowDrafts
55
+ files
57
56
  }) {
58
57
  const collectionsBySlug = /* @__PURE__ */ new Map();
59
58
  for (const [filePath, content] of Object.entries(files)) {
@@ -90,14 +89,6 @@ function getContentIndex({
90
89
  continue;
91
90
  }
92
91
  const document = parsedDocument.document;
93
- const status = document.values.status;
94
- if (status === "archived" || !isContentEntryPubliclyVisible({
95
- status,
96
- archivedAt: document.values.archivedAt,
97
- allowDrafts
98
- })) {
99
- continue;
100
- }
101
92
  const values = getContentRenderableEntryValues({
102
93
  document,
103
94
  slug: parsedPath.entry
@@ -105,16 +96,17 @@ function getContentIndex({
105
96
  const validationIssues = getContentEntryFieldValidationIssues({
106
97
  fields: collection.schema.fields,
107
98
  values,
108
- requireRequiredFields: document.values.status === "published"
99
+ requireRequiredFields: false
109
100
  });
110
101
  if (validationIssues.length > 0) {
111
102
  continue;
112
103
  }
104
+ const publishedCopy = files[filePath.slice(0, -CONTENT_ENTRY_FILE_EXTENSION.length) + CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION];
113
105
  const trimmedTitle = document.values.title?.trim();
114
106
  collection.collection.entries.push({
115
107
  slug: parsedPath.entry,
116
108
  title: trimmedTitle && trimmedTitle.length > 0 ? trimmedTitle : parsedPath.entry,
117
- status
109
+ status: publishedCopy === content ? "published" : "draft"
118
110
  });
119
111
  }
120
112
  const collections = [...collectionsBySlug.values()].map(({ collection }) => {
@@ -179,7 +171,7 @@ function validateContentManifest(contentManifest) {
179
171
  document,
180
172
  slug: parsedPath.entry
181
173
  }),
182
- requireRequiredFields: document.values.status === "published"
174
+ requireRequiredFields: true
183
175
  });
184
176
  issues.push(
185
177
  ...fieldIssues.map((issue) => ({
@@ -1,4 +1,4 @@
1
- import { CONTENT_SEO_VALUE_KEYS, CONTENT_RELATED_VALUE_STORAGE_KEY, CONTENT_PRIVATE_VALUE_KEYS, contentEntryStatusSchema, isContentPrivateValueKey, getContentFieldValueSchema } from './chunk-IQ5GRTXO.mjs';
1
+ import { CONTENT_SEO_VALUE_KEYS, CONTENT_RELATED_VALUE_STORAGE_KEY, CONTENT_PRIVATE_VALUE_KEYS, isContentPrivateValueKey, LEGACY_CONTENT_ENTRY_STATUSES, getContentFieldValueSchema } from './chunk-EUQVEPSD.mjs';
2
2
  import { stringify, parseDocument } from 'yaml';
3
3
  import { z } from 'zod';
4
4
 
@@ -57,7 +57,6 @@ var CONTENT_MANAGED_ENTRY_VALUE_KEYS = [
57
57
  var contentEntryFrontmatterSchema = z.object({
58
58
  slug: z.unknown().optional(),
59
59
  title: z.string().nullish(),
60
- status: contentEntryStatusSchema,
61
60
  author: z.string().nullish(),
62
61
  authorInitials: z.string().nullish(),
63
62
  excerpt: z.string().nullish(),
@@ -129,6 +128,24 @@ function getContentRenderableEntryValues({
129
128
  slug
130
129
  };
131
130
  }
131
+ function convertLegacyContentEntryDocument(raw) {
132
+ const parsed = parseContentEntryDocument(raw);
133
+ if (parsed.status === "invalid") {
134
+ return null;
135
+ }
136
+ const { status, archivedAt, ...values } = parsed.document.values;
137
+ if (status === void 0 && archivedAt === void 0) {
138
+ return { legacyStatus: null, content: raw };
139
+ }
140
+ return {
141
+ // An unrecognized status value publishes nothing rather than guessing.
142
+ legacyStatus: LEGACY_CONTENT_ENTRY_STATUSES.find((candidate) => candidate === status) ?? "draft",
143
+ content: serializeContentEntryDocument({
144
+ values,
145
+ body: parsed.document.body
146
+ })
147
+ };
148
+ }
132
149
  function getContentEntryFieldValidationIssues({
133
150
  fields,
134
151
  values,
@@ -148,4 +165,4 @@ function getContentEntryFieldValidationIssues({
148
165
  });
149
166
  }
150
167
 
151
- export { CONTENT_MANAGED_ENTRY_VALUE_KEYS, contentEntryFrontmatterSchema, getContentEntryFieldValidationIssues, getContentRenderableEntryValues, parseContentEntryDocument, serializeContentEntryDocument };
168
+ export { CONTENT_MANAGED_ENTRY_VALUE_KEYS, contentEntryFrontmatterSchema, convertLegacyContentEntryDocument, getContentEntryFieldValidationIssues, getContentRenderableEntryValues, parseContentEntryDocument, serializeContentEntryDocument };
@@ -5,6 +5,7 @@ var CONTENT_MANIFEST_FILENAME = "content-manifest.generated.json";
5
5
  var CONTENT_DIRNAME = "content";
6
6
  var CONTENT_COLLECTION_SCHEMA_FILENAME = "_collection.json";
7
7
  var CONTENT_ENTRY_FILE_EXTENSION = ".mdx";
8
+ var CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION = ".published.mdx";
8
9
  var CONTENT_RELATED_VALUE_STORAGE_KEY = "related";
9
10
  var CONTENT_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
10
11
  var CONTENT_MAX_SLUG_LENGTH = 100;
@@ -12,21 +13,43 @@ var contentSlugSchema = z.string().min(1).max(CONTENT_MAX_SLUG_LENGTH).regex(CON
12
13
  function isValidContentSlug(slug) {
13
14
  return contentSlugSchema.safeParse(slug).success;
14
15
  }
15
- var CONTENT_ENTRY_STATUSES = [
16
+ var LEGACY_CONTENT_ENTRY_STATUSES = [
16
17
  "draft",
17
18
  "published",
18
19
  "archived"
19
20
  ];
20
- var contentEntryStatusSchema = z.enum(CONTENT_ENTRY_STATUSES);
21
- function isContentEntryPubliclyVisible({
22
- status,
23
- archivedAt,
24
- allowDrafts
21
+ function getContentEntryFileSlug(filename) {
22
+ if (filename.endsWith(CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION)) {
23
+ const slug = filename.slice(
24
+ 0,
25
+ -CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION.length
26
+ );
27
+ return isValidContentSlug(slug) ? { slug, kind: "published" } : null;
28
+ }
29
+ if (filename.endsWith(CONTENT_ENTRY_FILE_EXTENSION)) {
30
+ const slug = filename.slice(0, -CONTENT_ENTRY_FILE_EXTENSION.length);
31
+ return isValidContentSlug(slug) ? { slug, kind: "draft" } : null;
32
+ }
33
+ return null;
34
+ }
35
+ function getContentEntryPublishState({
36
+ hasDraft,
37
+ hasPublishedCopy,
38
+ draftMatchesPublishedCopy
25
39
  }) {
26
- if (archivedAt !== null && archivedAt !== void 0) {
27
- return false;
40
+ if (hasDraft && hasPublishedCopy) {
41
+ return draftMatchesPublishedCopy ? "published" : "publishedWithChanges";
42
+ }
43
+ if (hasDraft) {
44
+ return "unpublished";
28
45
  }
29
- return status === "published" || allowDrafts && status === "draft";
46
+ return "pendingDelete";
47
+ }
48
+ function getContentEntryRoute({
49
+ collectionSlug,
50
+ entrySlug
51
+ }) {
52
+ return `/${collectionSlug}/${entrySlug}`;
30
53
  }
31
54
  var contentCollectionFieldTypeSchema = z.enum([
32
55
  "short-text",
@@ -94,7 +117,7 @@ function getContentFieldValueSchema(fieldType) {
94
117
  return z.string().refine((value) => isSafeContentUrl(value, { image: false }));
95
118
  }
96
119
  if (fieldType === "status") {
97
- return contentEntryStatusSchema;
120
+ return z.string();
98
121
  }
99
122
  return z.boolean();
100
123
  }
@@ -179,4 +202,4 @@ function getOrderedContentFields(fields) {
179
202
  });
180
203
  }
181
204
 
182
- export { CONTENT_BUILTIN_FIELD_ROLES, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_COUPLED_VALUE_PARENTS, CONTENT_DIRNAME, CONTENT_ENTRY_FILE_EXTENSION, CONTENT_ENTRY_STATUSES, CONTENT_MANIFEST_FILENAME, CONTENT_MAX_SLUG_LENGTH, CONTENT_PRIVATE_VALUE_KEYS, CONTENT_RELATED_VALUE_STORAGE_KEY, CONTENT_RESERVED_COLLECTION_SLUGS, CONTENT_RESERVED_ENTRY_SLUGS, CONTENT_SEO_VALUE_KEYS, CONTENT_SLUG_PATTERN, contentCollectionColorSchema, contentCollectionFieldSchema, contentCollectionFieldTypeSchema, contentCollectionFileSchema, contentEntryStatusSchema, contentSlugSchema, formatContentEntryRef, getContentFieldRole, getContentFieldValueSchema, getOrderedContentFields, getPublishedContentFields, isContentEntryPubliclyVisible, isContentPrivateValueKey, isContentReservedCollectionSlug, isContentReservedEntrySlug, isContentSeoValueKey, isSafeContentUrl, isValidContentSlug, parseContentEntryRef };
205
+ export { CONTENT_BUILTIN_FIELD_ROLES, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_COUPLED_VALUE_PARENTS, CONTENT_DIRNAME, CONTENT_ENTRY_FILE_EXTENSION, CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION, CONTENT_MANIFEST_FILENAME, CONTENT_MAX_SLUG_LENGTH, CONTENT_PRIVATE_VALUE_KEYS, CONTENT_RELATED_VALUE_STORAGE_KEY, CONTENT_RESERVED_COLLECTION_SLUGS, CONTENT_RESERVED_ENTRY_SLUGS, CONTENT_SEO_VALUE_KEYS, CONTENT_SLUG_PATTERN, LEGACY_CONTENT_ENTRY_STATUSES, contentCollectionColorSchema, contentCollectionFieldSchema, contentCollectionFieldTypeSchema, contentCollectionFileSchema, contentSlugSchema, formatContentEntryRef, getContentEntryFileSlug, getContentEntryPublishState, getContentEntryRoute, getContentFieldRole, getContentFieldValueSchema, getOrderedContentFields, getPublishedContentFields, isContentPrivateValueKey, isContentReservedCollectionSlug, isContentReservedEntrySlug, isContentSeoValueKey, isSafeContentUrl, isValidContentSlug, parseContentEntryRef };
package/content.js CHANGED
@@ -12,10 +12,11 @@ import {
12
12
  CONTENT_COLLECTION_SCHEMA_FILENAME,
13
13
  CONTENT_DIRNAME,
14
14
  CONTENT_ENTRY_FILE_EXTENSION,
15
+ CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION,
15
16
  CONTENT_RELATED_VALUE_STORAGE_KEY,
16
17
  contentCollectionFileSchema,
18
+ getContentEntryFileSlug,
17
19
  getPublishedContentFields,
18
- isContentEntryPubliclyVisible,
19
20
  isSafeContentUrl,
20
21
  isValidContentSlug,
21
22
  parseContentEntryRef
@@ -58,7 +59,8 @@ function createContentSource({
58
59
  },
59
60
  listEntries: async (collection) => {
60
61
  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
+ const parsed = entry.isFile() ? getContentEntryFileSlug(entry.name) : null;
63
+ return parsed?.kind === "draft" ? [parsed.slug] : [];
62
64
  });
63
65
  }
64
66
  };
@@ -292,17 +294,23 @@ async function findContentCollection({
292
294
  collection,
293
295
  contentSource
294
296
  });
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
- });
297
+ const entries = await Promise.all(
298
+ visibleEntries.map(async (entry) => {
299
+ return {
300
+ slug: entry.slug,
301
+ title: readNonEmptyString(entry.values.title) ?? entry.slug,
302
+ excerpt: readNonEmptyString(entry.values.excerpt),
303
+ coverImage: readSafeImageUrl(entry.values.coverImage),
304
+ coverImageAlt: readNonEmptyString(entry.values.coverImageAlt),
305
+ publishedAt: readIsoDate(entry.values.publishedAt),
306
+ status: await getEntryListingStatus({
307
+ collection,
308
+ slug: entry.slug,
309
+ contentSource
310
+ })
311
+ };
312
+ })
313
+ );
306
314
  entries.sort((left, right) => {
307
315
  const leftTime = left.publishedAt ? Date.parse(left.publishedAt) : 0;
308
316
  const rightTime = right.publishedAt ? Date.parse(right.publishedAt) : 0;
@@ -357,6 +365,22 @@ async function listVisibleEntries({
357
365
  );
358
366
  return entries.filter(isNotNullish);
359
367
  }
368
+ async function getEntryListingStatus({
369
+ collection,
370
+ slug,
371
+ contentSource
372
+ }) {
373
+ if (process.env.NODE_ENV !== "development") {
374
+ return "published";
375
+ }
376
+ const [draft, publishedCopy] = await Promise.all([
377
+ contentSource.read(`${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`),
378
+ contentSource.read(
379
+ `${collection}/${slug}${CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION}`
380
+ )
381
+ ]);
382
+ return draft !== null && draft === publishedCopy ? "published" : "draft";
383
+ }
360
384
  async function readActiveCollectionSchema({
361
385
  collection,
362
386
  contentSource
@@ -398,16 +422,7 @@ async function readVisibleEntryDocument({
398
422
  if (parsedDocument.status === "invalid") {
399
423
  return null;
400
424
  }
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;
425
+ return parsedDocument.document;
411
426
  }
412
427
  async function readVisibleEntry({
413
428
  collectionSchema,
@@ -430,7 +445,7 @@ async function readVisibleEntry({
430
445
  if (getContentEntryFieldValidationIssues({
431
446
  fields: collectionSchema.fields,
432
447
  values,
433
- requireRequiredFields: document.values.status !== "draft"
448
+ requireRequiredFields: process.env.NODE_ENV !== "development"
434
449
  }).length > 0) {
435
450
  return null;
436
451
  }
package/content.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../content.tsx"],
4
- "sourcesContent": ["/* oxlint-disable replo/consistent-component-exports -- NOTE (Ryan, 2026-07-30): The published content entrypoint exposes its server loaders and renderers together; HMR splitting doesn't apply to a published server-only package. */\nimport \"server-only\";\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Metadata } from \"next\";\nimport type { ReactNode } from \"react\";\nimport type { ContentManifest } from \"schemas/contentManifest\";\n\nimport { Fragment } from \"react\";\n\nimport { notFound } from \"next/navigation\";\n\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { parseJsonOrNull } from \"replo-utils/lib/json\";\nimport { isNotNullish } from \"replo-utils/lib/misc\";\nimport {\n CONTENT_COLLECTION_SCHEMA_FILENAME,\n CONTENT_DIRNAME,\n CONTENT_ENTRY_FILE_EXTENSION,\n CONTENT_RELATED_VALUE_STORAGE_KEY,\n contentCollectionFieldSchema,\n contentCollectionFileSchema,\n getPublishedContentFields,\n isContentEntryPubliclyVisible,\n isSafeContentUrl,\n isValidContentSlug,\n parseContentEntryRef,\n} from \"schemas/contentCollection\";\nimport {\n getContentEntryFieldValidationIssues,\n getContentRenderableEntryValues,\n parseContentEntryDocument,\n} from \"schemas/contentEntry\";\nimport {\n getContentManifestCollectionSlugs,\n getContentManifestEntrySlugs,\n} from \"schemas/contentManifest\";\nimport { z } from \"zod\";\n\ntype ContentCollectionFile = z.infer<typeof contentCollectionFileSchema>;\n\n/** Public name for the shared collection-field contract. */\nexport type ContentField = z.infer<typeof contentCollectionFieldSchema>;\n\nexport interface ContentEntry {\n collection: {\n name: string;\n description: string;\n slug: string;\n };\n fields: ContentField[];\n slug: string;\n body: string;\n values: Record<string, unknown>;\n}\n\n/** One entry's slot in a collection listing. */\nexport interface ContentCollectionEntry {\n slug: string;\n title: string;\n excerpt: string | null;\n coverImage: string | null;\n coverImageAlt: string | null;\n publishedAt: string | null;\n status: \"draft\" | \"published\";\n}\n\nexport interface ContentCollection {\n slug: string;\n name: string;\n description: string;\n entries: ContentCollectionEntry[];\n}\n\n/** An ordered content field paired with this entry's value for it. */\nexport interface ContentFieldWithValue {\n field: ContentField;\n value: unknown;\n}\n\nexport interface ContentSitemapEntry {\n url: string;\n lastModified?: Date;\n}\n\nexport interface ContentSource {\n read(relativePath: string): Promise<string | null>;\n listCollections(): Promise<string[]>;\n listEntries(collection: string): Promise<string[]>;\n}\n\ninterface ContentOptions {\n contentSource?: ContentSource;\n contentDirectory?: string;\n contentManifest?: ContentManifest;\n}\n\nexport function createContentSource({\n contentDirectory = getDefaultContentDirectory(),\n contentManifest,\n}: {\n contentDirectory?: string;\n contentManifest?: ContentManifest;\n} = {}): ContentSource {\n if (contentManifest) {\n return {\n read: async (relativePath) => {\n return contentManifest.files[relativePath] ?? null;\n },\n listCollections: async () => {\n return getContentManifestCollectionSlugs(contentManifest);\n },\n listEntries: async (collection) => {\n return getContentManifestEntrySlugs({ collection, contentManifest });\n },\n };\n }\n return {\n read: (relativePath) => {\n return readTextFileOrNull(join(contentDirectory, relativePath));\n },\n listCollections: async () => {\n return (await readDirectoryOrEmpty(contentDirectory)).flatMap((entry) => {\n return entry.isDirectory() ? [entry.name] : [];\n });\n },\n listEntries: async (collection) => {\n return (\n await readDirectoryOrEmpty(join(contentDirectory, collection))\n ).flatMap((entry) => {\n return entry.isFile() &&\n entry.name.endsWith(CONTENT_ENTRY_FILE_EXTENSION)\n ? [entry.name.slice(0, -CONTENT_ENTRY_FILE_EXTENSION.length)]\n : [];\n });\n },\n };\n}\n\nexport async function getContentEntry({\n collection,\n slug,\n ...contentOptions\n}: {\n collection: string;\n slug: string;\n} & ContentOptions): Promise<ContentEntry> {\n const entry = await findContentEntry({\n collection,\n slug,\n contentSource: getContentSource(contentOptions),\n });\n if (!entry) {\n notFound();\n }\n return entry;\n}\n\n/** Static params for one collection's `[slug]` detail route. */\nexport async function getContentCollectionStaticParams({\n collection,\n ...contentOptions\n}: { collection: string } & ContentOptions): Promise<{ slug: string }[]> {\n const contentSource = getContentSource(contentOptions);\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return [];\n }\n const entries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n return entries\n .map((entry) => entry.slug)\n .sort()\n .map((slug) => ({ slug }));\n}\n\n/**\n * A collection and its renderable entries for a listing page. Uses the\n * not-found contract when the collection is missing or archived.\n */\nexport async function getContentCollection({\n collection,\n ...contentOptions\n}: { collection: string } & ContentOptions): Promise<ContentCollection> {\n const loaded = await findContentCollection({\n collection,\n contentSource: getContentSource(contentOptions),\n });\n if (!loaded) {\n notFound();\n }\n return loaded;\n}\n\n/**\n * Per-entry head metadata (title/description/OpenGraph/Twitter) from the\n * entry's SEO fields with content fallbacks. Returns an empty object when the\n * entry is not renderable, so `generateMetadata` can call it unconditionally.\n */\nexport async function getContentEntryMetadata({\n collection,\n slug,\n ...contentOptions\n}: {\n collection: string;\n slug: string;\n} & ContentOptions): Promise<Metadata> {\n const entry = await findContentEntry({\n collection,\n slug,\n contentSource: getContentSource(contentOptions),\n });\n if (!entry) {\n return {};\n }\n\n const title =\n readNonEmptyString(entry.values.seoTitle) ??\n readNonEmptyString(entry.values.title) ??\n entry.collection.name;\n const description =\n readNonEmptyString(entry.values.seoDescription) ??\n readNonEmptyString(entry.values.excerpt);\n const image = readSafeImageUrl(entry.values.coverImage);\n\n return {\n title,\n ...(description === null ? {} : { description }),\n openGraph: {\n title,\n ...(description === null ? {} : { description }),\n type: \"article\",\n ...(image === null ? {} : { images: [image] }),\n },\n twitter: {\n card: image === null ? \"summary\" : \"summary_large_image\",\n title,\n ...(description === null ? {} : { description }),\n ...(image === null ? {} : { images: [image] }),\n },\n };\n}\n\n/**\n * Structured data for an entry as a JSON-LD script tag. Serialization is\n * script-safe (`<` escaped) so entry content can never break out of the tag.\n * The type is a deliberately neutral `WebPage`; collections have no schema\n * type of their own.\n */\nexport function ContentEntryJsonLd({ entry }: { entry: ContentEntry }) {\n const title =\n readNonEmptyString(entry.values.seoTitle) ??\n readNonEmptyString(entry.values.title) ??\n entry.collection.name;\n const description =\n readNonEmptyString(entry.values.seoDescription) ??\n readNonEmptyString(entry.values.excerpt);\n const image = readSafeImageUrl(entry.values.coverImage);\n const datePublished = readIsoDate(entry.values.publishedAt);\n const dateModified = readIsoDate(entry.values.updatedAt);\n\n const jsonLd = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebPage\",\n name: title,\n ...(description === null ? {} : { description }),\n ...(image === null ? {} : { image }),\n ...(datePublished === null ? {} : { datePublished }),\n ...(dateModified === null ? {} : { dateModified }),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger -- NOTE (Ryan, 2026-07-30): JSON-LD must be raw script text; serializeJsonLd escapes `<` so values cannot close the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(jsonLd) }}\n />\n );\n}\n\n/**\n * Absolute sitemap entries for every collection listing and renderable entry.\n * `origin` comes from the caller (e.g. the request's platform-validated Host)\n * because site origins are not fixed at build time.\n */\nexport async function getContentSitemapEntries({\n origin,\n ...contentOptions\n}: { origin: string } & ContentOptions): Promise<ContentSitemapEntry[]> {\n const base = origin.replace(/\\/+$/u, \"\");\n const entries: ContentSitemapEntry[] = [];\n const contentSource = getContentSource(contentOptions);\n\n for (const collection of await listCollectionSlugs({ contentSource })) {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n continue;\n }\n\n // Collections can opt out of a public listing page; its URL would 404.\n if (collectionSchema.hasListingPage !== false) {\n entries.push({ url: `${base}/${collection}` });\n }\n const visibleEntries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n visibleEntries.sort((left, right) => left.slug.localeCompare(right.slug));\n for (const entry of visibleEntries) {\n const lastModified = readValidDate(entry.values.updatedAt);\n entries.push({\n url: `${base}/${collection}/${entry.slug}`,\n ...(lastModified === null ? {} : { lastModified }),\n });\n }\n }\n\n return entries;\n}\n\n/**\n * The full sitemap XML document for a site's content (home, collection\n * listings, entries). Kept here so the generated sitemap route stays a tiny,\n * restylable-free shell around one call.\n */\nexport async function getContentSitemapXml({\n origin,\n ...contentOptions\n}: { origin: string } & ContentOptions): Promise<string> {\n const base = origin.replace(/\\/+$/u, \"\");\n const entries: ContentSitemapEntry[] = [\n { url: base },\n ...(await getContentSitemapEntries({\n origin: base,\n contentSource: getContentSource(contentOptions),\n })),\n ];\n const items = entries.map(({ url, lastModified }) => {\n const lastmod = lastModified\n ? `<lastmod>${lastModified.toISOString()}</lastmod>`\n : \"\";\n return `<url><loc>${escapeXml(url)}</loc>${lastmod}</url>`;\n });\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">${items.join(\n \"\",\n )}</urlset>\\n`;\n}\n\n/**\n * The entry's renderable content fields in collection-settings order, paired\n * with values. This is the published half of the shared ordering contract in\n * `schemas/contentCollection`; empty values are omitted so templates never render\n * bare labels.\n */\nexport function getContentFields(entry: ContentEntry): ContentFieldWithValue[] {\n return getPublishedContentFields(entry.fields)\n .map((field) => ({ field, value: entry.values[field.id] }))\n .filter(({ value }) => {\n return value !== undefined && value !== null && value !== \"\";\n });\n}\n\nexport function ContentFieldValue({\n field,\n value,\n}: {\n field: ContentField;\n value: unknown;\n}): ReactNode {\n if (field.type === \"rich-text\" && typeof value === \"string\") {\n return <SafeMarkdown source={value} />;\n }\n if (field.type === \"image\" && typeof value === \"string\") {\n return (\n <img\n alt={field.label}\n src={value}\n style={{ display: \"block\", height: \"auto\", maxWidth: \"100%\" }}\n />\n );\n }\n if (field.type === \"url\" && typeof value === \"string\") {\n return (\n <a href={value} style={CONTENT_LINK_STYLE}>\n {value}\n </a>\n );\n }\n if (field.type === \"date\" && typeof value === \"string\") {\n const parsed = new Date(value);\n // Explicit locale so the server render matches the client's hydration.\n const label = Number.isNaN(parsed.getTime())\n ? value\n : parsed.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n return <time dateTime={value}>{label}</time>;\n }\n if (field.type === \"boolean\" && typeof value === \"boolean\") {\n return value ? \"Yes\" : \"No\";\n }\n if (field.type === \"related\" && Array.isArray(value)) {\n const items = value.filter(\n (item) => isContentRelatedLink(item) || typeof item === \"string\",\n );\n if (items.length === 0) {\n return null;\n }\n return items.map((item, index) => (\n <Fragment key={isContentRelatedLink(item) ? item.href : String(item)}>\n {index > 0 && \", \"}\n {isContentRelatedLink(item) ? (\n <a href={item.href} style={CONTENT_LINK_STYLE}>\n {item.title}\n </a>\n ) : (\n String(item)\n )}\n </Fragment>\n ));\n }\n if (field.type === \"tags\" && Array.isArray(value)) {\n return value.join(\", \");\n }\n return typeof value === \"string\" ? value : null;\n}\n\nasync function findContentCollection({\n collection,\n contentSource,\n}: {\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentCollection | null> {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return null;\n }\n\n const visibleEntries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n const entries = visibleEntries.map((entry): ContentCollectionEntry => {\n return {\n slug: entry.slug,\n title: readNonEmptyString(entry.values.title) ?? entry.slug,\n excerpt: readNonEmptyString(entry.values.excerpt),\n coverImage: readSafeImageUrl(entry.values.coverImage),\n coverImageAlt: readNonEmptyString(entry.values.coverImageAlt),\n publishedAt: readIsoDate(entry.values.publishedAt),\n status: entry.values.status === \"draft\" ? \"draft\" : \"published\",\n };\n });\n entries.sort((left, right) => {\n const leftTime = left.publishedAt ? Date.parse(left.publishedAt) : 0;\n const rightTime = right.publishedAt ? Date.parse(right.publishedAt) : 0;\n return rightTime - leftTime || left.title.localeCompare(right.title);\n });\n\n return {\n slug: collection,\n name: collectionSchema.name,\n description: collectionSchema.description,\n entries,\n };\n}\n\nasync function findContentEntry({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry | null> {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return null;\n }\n return readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n });\n}\n\nasync function listCollectionSlugs({\n contentSource,\n}: {\n contentSource: ContentSource;\n}): Promise<string[]> {\n return (await contentSource.listCollections())\n .filter(isValidContentSlug)\n .sort();\n}\n\n/**\n * Every renderable entry of a collection, each file read and parsed exactly\n * once. Callers pass the already-read collection schema so no helper in this\n * chain ever re-reads it.\n */\nasync function listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n}: {\n collectionSchema: ContentCollectionFile;\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry[]> {\n const slugs = await contentSource.listEntries(collection);\n const entries = await Promise.all(\n slugs.map((slug) => {\n return readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n });\n }),\n );\n return entries.filter(isNotNullish);\n}\n\n/** The collection's parsed schema, or null when missing, invalid, or archived. */\nasync function readActiveCollectionSchema({\n collection,\n contentSource,\n}: {\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentCollectionFile | null> {\n if (!isValidContentSlug(collection)) {\n return null;\n }\n const rawSchema = await contentSource.read(\n `${collection}/${CONTENT_COLLECTION_SCHEMA_FILENAME}`,\n );\n if (!rawSchema) {\n return null;\n }\n const parsedJson = parseJsonOrNull(rawSchema);\n if (parsedJson === null) {\n return null;\n }\n const result = contentCollectionFileSchema.safeParse(parsedJson);\n if (!result.success || isNotNullish(result.data.archivedAt)) {\n return null;\n }\n return result.data;\n}\n\n// The parsed document when the entry exists and is publicly visible, else\n// null. The one visibility gate for full entry loads and shallow reads alike.\nasync function readVisibleEntryDocument({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}) {\n if (!isValidContentSlug(slug)) {\n return null;\n }\n const rawEntry = await contentSource.read(\n `${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`,\n );\n if (!rawEntry) {\n return null;\n }\n const parsedDocument = parseContentEntryDocument(rawEntry);\n if (parsedDocument.status === \"invalid\") {\n return null;\n }\n const document = parsedDocument.document;\n // NOTE (Ryan, 2026-07-28): The sandbox preview runs `next dev`, so drafts\n // render there for Content previewing; production builds stay published-only.\n const allowDrafts = process.env.NODE_ENV === \"development\";\n if (\n !isContentEntryPubliclyVisible({\n status: document.values.status,\n archivedAt: document.values.archivedAt,\n allowDrafts,\n })\n ) {\n return null;\n }\n return document;\n}\n\nasync function readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n}: {\n collectionSchema: ContentCollectionFile;\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry | null> {\n const document = await readVisibleEntryDocument({\n collection,\n slug,\n contentSource,\n });\n if (!document) {\n return null;\n }\n\n const values = getContentRenderableEntryValues({\n document,\n slug,\n });\n // NOTE (Ryan, 2026-07-28): Drafts are work-in-progress, so the dev preview\n // renders them even when required fields are still incomplete.\n if (\n getContentEntryFieldValidationIssues({\n fields: collectionSchema.fields,\n values,\n requireRequiredFields: document.values.status !== \"draft\",\n }).length > 0\n ) {\n return null;\n }\n\n // The editor's built-in Related Content section stores refs under `related`\n // without a schema field; surface them through a synthetic trailing field so\n // related links render for every collection, not only ones whose schema\n // declares a related field.\n const fields: ContentField[] = [...collectionSchema.fields];\n const hasRelatedField = fields.some((field) => field.type === \"related\");\n const storedRelated = values[CONTENT_RELATED_VALUE_STORAGE_KEY];\n if (\n !hasRelatedField &&\n Array.isArray(storedRelated) &&\n storedRelated.length > 0\n ) {\n fields.push({\n id: CONTENT_RELATED_VALUE_STORAGE_KEY,\n label: \"Related content\",\n type: \"related\",\n required: false,\n enabled: true,\n builtin: true,\n });\n }\n return {\n collection: {\n name: collectionSchema.name,\n description: collectionSchema.description,\n slug: collection,\n },\n fields,\n slug,\n body: document.body,\n values: await resolveRelatedFieldValues({\n fields,\n values,\n contentSource,\n }),\n };\n}\n\n/** A related entry resolved to a public link; unresolvable refs stay plain text. */\nexport interface ContentRelatedLink {\n title: string;\n href: string;\n}\n\n// Resolves each `related` field's stored content paths into public links with\n// the target entry's title. Targets that aren't publicly visible (drafts in\n// production, archived, deleted) stay out entirely rather than rendering a\n// dead link.\nasync function resolveRelatedFieldValues({\n fields,\n values,\n contentSource,\n}: {\n fields: ContentField[];\n values: Record<string, unknown>;\n contentSource: ContentSource;\n}): Promise<Record<string, unknown>> {\n const relatedFields = fields.filter((field) => field.type === \"related\");\n if (relatedFields.length === 0) {\n return values;\n }\n const resolved = { ...values };\n for (const field of relatedFields) {\n const raw = resolved[field.id];\n if (!Array.isArray(raw)) {\n continue;\n }\n const links: ContentRelatedLink[] = [];\n for (const item of raw) {\n if (typeof item !== \"string\") {\n continue;\n }\n const ref = parseContentEntryRef(item);\n if (!ref) {\n continue;\n }\n const { collectionSlug: targetCollection, entrySlug: targetSlug } = ref;\n const collectionSchema = await readActiveCollectionSchema({\n collection: targetCollection,\n contentSource,\n });\n if (!collectionSchema) {\n continue;\n }\n // A shallow document read on purpose: resolving the target through the\n // full entry loader would recurse through ITS related fields, looping\n // on mutually-related entries. Only visibility and the title matter.\n const target = await readVisibleEntryDocument({\n collection: targetCollection,\n slug: targetSlug,\n contentSource,\n });\n if (!target) {\n continue;\n }\n links.push({\n title: readNonEmptyString(target.values.title) ?? targetSlug,\n href: `/${targetCollection}/${targetSlug}`,\n });\n }\n resolved[field.id] = links;\n }\n return resolved;\n}\n\nconst contentRelatedLinkSchema = z.object({\n title: z.string(),\n href: z.string(),\n});\n\nfunction isContentRelatedLink(value: unknown): value is ContentRelatedLink {\n return contentRelatedLinkSchema.safeParse(value).success;\n}\n\n// Sites commonly ship a CSS reset (Tailwind's preflight sets\n// `text-decoration: inherit`) that strips link underlines, leaving body links\n// visually indistinguishable from text. State it inline so a link reads as a\n// link on any site, the same way images carry their own layout styles.\nconst CONTENT_LINK_STYLE = { textDecoration: \"underline\" } as const;\n\nfunction SafeMarkdown({ source }: { source: string }) {\n return (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n skipHtml\n components={{\n a: ({ children, ...props }) => (\n <a {...props} style={CONTENT_LINK_STYLE}>\n {children}\n </a>\n ),\n }}\n >\n {source}\n </ReactMarkdown>\n );\n}\n\nfunction readNonEmptyString(value: unknown): string | null {\n const parsed = z.string().safeParse(value);\n return parsed.success && parsed.data.trim().length > 0 ? parsed.data : null;\n}\n\nfunction readSafeImageUrl(value: unknown): string | null {\n const url = readNonEmptyString(value);\n return url !== null && isSafeContentUrl(url, { image: true }) ? url : null;\n}\n\nfunction readIsoDate(value: unknown): string | null {\n const raw = readNonEmptyString(value);\n return raw !== null && !Number.isNaN(Date.parse(raw)) ? raw : null;\n}\n\nfunction readValidDate(value: unknown): Date | null {\n const iso = readIsoDate(value);\n return iso === null ? null : new Date(iso);\n}\n\nfunction serializeJsonLd(value: Record<string, unknown>): string {\n return JSON.stringify(value)\n .replaceAll(\"<\", \"\\\\u003c\")\n .replaceAll(\"\\u2028\", \"\\\\u2028\")\n .replaceAll(\"\\u2029\", \"\\\\u2029\");\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&apos;\");\n}\n\nasync function readTextFileOrNull(path: string) {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n\nasync function readDirectoryOrEmpty(path: string) {\n try {\n return await readdir(path, { withFileTypes: true });\n } catch {\n return [];\n }\n}\n\nfunction getContentSource({\n contentSource,\n contentDirectory,\n contentManifest,\n}: ContentOptions): ContentSource {\n return (\n contentSource ?? createContentSource({ contentDirectory, contentManifest })\n );\n}\n\nfunction getDefaultContentDirectory() {\n return join(process.cwd(), CONTENT_DIRNAME);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* oxlint-disable replo/consistent-component-exports -- NOTE (Ryan, 2026-07-30): The published content entrypoint exposes its server loaders and renderers together; HMR splitting doesn't apply to a published server-only package. */\nimport \"server-only\";\n\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { Metadata } from \"next\";\nimport type { ReactNode } from \"react\";\nimport type { ContentManifest } from \"schemas/contentManifest\";\n\nimport { Fragment } from \"react\";\n\nimport { notFound } from \"next/navigation\";\n\nimport ReactMarkdown from \"react-markdown\";\nimport remarkGfm from \"remark-gfm\";\nimport { parseJsonOrNull } from \"replo-utils/lib/json\";\nimport { isNotNullish } from \"replo-utils/lib/misc\";\nimport {\n CONTENT_COLLECTION_SCHEMA_FILENAME,\n CONTENT_DIRNAME,\n CONTENT_ENTRY_FILE_EXTENSION,\n CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION,\n CONTENT_RELATED_VALUE_STORAGE_KEY,\n contentCollectionFieldSchema,\n contentCollectionFileSchema,\n getContentEntryFileSlug,\n getPublishedContentFields,\n isSafeContentUrl,\n isValidContentSlug,\n parseContentEntryRef,\n} from \"schemas/contentCollection\";\nimport {\n getContentEntryFieldValidationIssues,\n getContentRenderableEntryValues,\n parseContentEntryDocument,\n} from \"schemas/contentEntry\";\nimport {\n getContentManifestCollectionSlugs,\n getContentManifestEntrySlugs,\n} from \"schemas/contentManifest\";\nimport { z } from \"zod\";\n\ntype ContentCollectionFile = z.infer<typeof contentCollectionFileSchema>;\n\n/** Public name for the shared collection-field contract. */\nexport type ContentField = z.infer<typeof contentCollectionFieldSchema>;\n\nexport interface ContentEntry {\n collection: {\n name: string;\n description: string;\n slug: string;\n };\n fields: ContentField[];\n slug: string;\n body: string;\n values: Record<string, unknown>;\n}\n\n/** One entry's slot in a collection listing. */\nexport interface ContentCollectionEntry {\n slug: string;\n title: string;\n excerpt: string | null;\n coverImage: string | null;\n coverImageAlt: string | null;\n publishedAt: string | null;\n status: \"draft\" | \"published\";\n}\n\nexport interface ContentCollection {\n slug: string;\n name: string;\n description: string;\n entries: ContentCollectionEntry[];\n}\n\n/** An ordered content field paired with this entry's value for it. */\nexport interface ContentFieldWithValue {\n field: ContentField;\n value: unknown;\n}\n\nexport interface ContentSitemapEntry {\n url: string;\n lastModified?: Date;\n}\n\nexport interface ContentSource {\n read(relativePath: string): Promise<string | null>;\n listCollections(): Promise<string[]>;\n listEntries(collection: string): Promise<string[]>;\n}\n\ninterface ContentOptions {\n contentSource?: ContentSource;\n contentDirectory?: string;\n contentManifest?: ContentManifest;\n}\n\nexport function createContentSource({\n contentDirectory = getDefaultContentDirectory(),\n contentManifest,\n}: {\n contentDirectory?: string;\n contentManifest?: ContentManifest;\n} = {}): ContentSource {\n if (contentManifest) {\n return {\n read: async (relativePath) => {\n return contentManifest.files[relativePath] ?? null;\n },\n listCollections: async () => {\n return getContentManifestCollectionSlugs(contentManifest);\n },\n listEntries: async (collection) => {\n return getContentManifestEntrySlugs({ collection, contentManifest });\n },\n };\n }\n return {\n read: (relativePath) => {\n return readTextFileOrNull(join(contentDirectory, relativePath));\n },\n listCollections: async () => {\n return (await readDirectoryOrEmpty(contentDirectory)).flatMap((entry) => {\n return entry.isDirectory() ? [entry.name] : [];\n });\n },\n listEntries: async (collection) => {\n return (\n await readDirectoryOrEmpty(join(contentDirectory, collection))\n ).flatMap((entry) => {\n const parsed = entry.isFile()\n ? getContentEntryFileSlug(entry.name)\n : null;\n return parsed?.kind === \"draft\" ? [parsed.slug] : [];\n });\n },\n };\n}\n\nexport async function getContentEntry({\n collection,\n slug,\n ...contentOptions\n}: {\n collection: string;\n slug: string;\n} & ContentOptions): Promise<ContentEntry> {\n const entry = await findContentEntry({\n collection,\n slug,\n contentSource: getContentSource(contentOptions),\n });\n if (!entry) {\n notFound();\n }\n return entry;\n}\n\n/** Static params for one collection's `[slug]` detail route. */\nexport async function getContentCollectionStaticParams({\n collection,\n ...contentOptions\n}: { collection: string } & ContentOptions): Promise<{ slug: string }[]> {\n const contentSource = getContentSource(contentOptions);\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return [];\n }\n const entries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n return entries\n .map((entry) => entry.slug)\n .sort()\n .map((slug) => ({ slug }));\n}\n\n/**\n * A collection and its renderable entries for a listing page. Uses the\n * not-found contract when the collection is missing or archived.\n */\nexport async function getContentCollection({\n collection,\n ...contentOptions\n}: { collection: string } & ContentOptions): Promise<ContentCollection> {\n const loaded = await findContentCollection({\n collection,\n contentSource: getContentSource(contentOptions),\n });\n if (!loaded) {\n notFound();\n }\n return loaded;\n}\n\n/**\n * Per-entry head metadata (title/description/OpenGraph/Twitter) from the\n * entry's SEO fields with content fallbacks. Returns an empty object when the\n * entry is not renderable, so `generateMetadata` can call it unconditionally.\n */\nexport async function getContentEntryMetadata({\n collection,\n slug,\n ...contentOptions\n}: {\n collection: string;\n slug: string;\n} & ContentOptions): Promise<Metadata> {\n const entry = await findContentEntry({\n collection,\n slug,\n contentSource: getContentSource(contentOptions),\n });\n if (!entry) {\n return {};\n }\n\n const title =\n readNonEmptyString(entry.values.seoTitle) ??\n readNonEmptyString(entry.values.title) ??\n entry.collection.name;\n const description =\n readNonEmptyString(entry.values.seoDescription) ??\n readNonEmptyString(entry.values.excerpt);\n const image = readSafeImageUrl(entry.values.coverImage);\n\n return {\n title,\n ...(description === null ? {} : { description }),\n openGraph: {\n title,\n ...(description === null ? {} : { description }),\n type: \"article\",\n ...(image === null ? {} : { images: [image] }),\n },\n twitter: {\n card: image === null ? \"summary\" : \"summary_large_image\",\n title,\n ...(description === null ? {} : { description }),\n ...(image === null ? {} : { images: [image] }),\n },\n };\n}\n\n/**\n * Structured data for an entry as a JSON-LD script tag. Serialization is\n * script-safe (`<` escaped) so entry content can never break out of the tag.\n * The type is a deliberately neutral `WebPage`; collections have no schema\n * type of their own.\n */\nexport function ContentEntryJsonLd({ entry }: { entry: ContentEntry }) {\n const title =\n readNonEmptyString(entry.values.seoTitle) ??\n readNonEmptyString(entry.values.title) ??\n entry.collection.name;\n const description =\n readNonEmptyString(entry.values.seoDescription) ??\n readNonEmptyString(entry.values.excerpt);\n const image = readSafeImageUrl(entry.values.coverImage);\n const datePublished = readIsoDate(entry.values.publishedAt);\n const dateModified = readIsoDate(entry.values.updatedAt);\n\n const jsonLd = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebPage\",\n name: title,\n ...(description === null ? {} : { description }),\n ...(image === null ? {} : { image }),\n ...(datePublished === null ? {} : { datePublished }),\n ...(dateModified === null ? {} : { dateModified }),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger -- NOTE (Ryan, 2026-07-30): JSON-LD must be raw script text; serializeJsonLd escapes `<` so values cannot close the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(jsonLd) }}\n />\n );\n}\n\n/**\n * Absolute sitemap entries for every collection listing and renderable entry.\n * `origin` comes from the caller (e.g. the request's platform-validated Host)\n * because site origins are not fixed at build time.\n */\nexport async function getContentSitemapEntries({\n origin,\n ...contentOptions\n}: { origin: string } & ContentOptions): Promise<ContentSitemapEntry[]> {\n const base = origin.replace(/\\/+$/u, \"\");\n const entries: ContentSitemapEntry[] = [];\n const contentSource = getContentSource(contentOptions);\n\n for (const collection of await listCollectionSlugs({ contentSource })) {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n continue;\n }\n\n // Collections can opt out of a public listing page; its URL would 404.\n if (collectionSchema.hasListingPage !== false) {\n entries.push({ url: `${base}/${collection}` });\n }\n const visibleEntries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n visibleEntries.sort((left, right) => left.slug.localeCompare(right.slug));\n for (const entry of visibleEntries) {\n const lastModified = readValidDate(entry.values.updatedAt);\n entries.push({\n url: `${base}/${collection}/${entry.slug}`,\n ...(lastModified === null ? {} : { lastModified }),\n });\n }\n }\n\n return entries;\n}\n\n/**\n * The full sitemap XML document for a site's content (home, collection\n * listings, entries). Kept here so the generated sitemap route stays a tiny,\n * restylable-free shell around one call.\n */\nexport async function getContentSitemapXml({\n origin,\n ...contentOptions\n}: { origin: string } & ContentOptions): Promise<string> {\n const base = origin.replace(/\\/+$/u, \"\");\n const entries: ContentSitemapEntry[] = [\n { url: base },\n ...(await getContentSitemapEntries({\n origin: base,\n contentSource: getContentSource(contentOptions),\n })),\n ];\n const items = entries.map(({ url, lastModified }) => {\n const lastmod = lastModified\n ? `<lastmod>${lastModified.toISOString()}</lastmod>`\n : \"\";\n return `<url><loc>${escapeXml(url)}</loc>${lastmod}</url>`;\n });\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">${items.join(\n \"\",\n )}</urlset>\\n`;\n}\n\n/**\n * The entry's renderable content fields in collection-settings order, paired\n * with values. This is the published half of the shared ordering contract in\n * `schemas/contentCollection`; empty values are omitted so templates never render\n * bare labels.\n */\nexport function getContentFields(entry: ContentEntry): ContentFieldWithValue[] {\n return getPublishedContentFields(entry.fields)\n .map((field) => ({ field, value: entry.values[field.id] }))\n .filter(({ value }) => {\n return value !== undefined && value !== null && value !== \"\";\n });\n}\n\nexport function ContentFieldValue({\n field,\n value,\n}: {\n field: ContentField;\n value: unknown;\n}): ReactNode {\n if (field.type === \"rich-text\" && typeof value === \"string\") {\n return <SafeMarkdown source={value} />;\n }\n if (field.type === \"image\" && typeof value === \"string\") {\n return (\n <img\n alt={field.label}\n src={value}\n style={{ display: \"block\", height: \"auto\", maxWidth: \"100%\" }}\n />\n );\n }\n if (field.type === \"url\" && typeof value === \"string\") {\n return (\n <a href={value} style={CONTENT_LINK_STYLE}>\n {value}\n </a>\n );\n }\n if (field.type === \"date\" && typeof value === \"string\") {\n const parsed = new Date(value);\n // Explicit locale so the server render matches the client's hydration.\n const label = Number.isNaN(parsed.getTime())\n ? value\n : parsed.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n return <time dateTime={value}>{label}</time>;\n }\n if (field.type === \"boolean\" && typeof value === \"boolean\") {\n return value ? \"Yes\" : \"No\";\n }\n if (field.type === \"related\" && Array.isArray(value)) {\n const items = value.filter(\n (item) => isContentRelatedLink(item) || typeof item === \"string\",\n );\n if (items.length === 0) {\n return null;\n }\n return items.map((item, index) => (\n <Fragment key={isContentRelatedLink(item) ? item.href : String(item)}>\n {index > 0 && \", \"}\n {isContentRelatedLink(item) ? (\n <a href={item.href} style={CONTENT_LINK_STYLE}>\n {item.title}\n </a>\n ) : (\n String(item)\n )}\n </Fragment>\n ));\n }\n if (field.type === \"tags\" && Array.isArray(value)) {\n return value.join(\", \");\n }\n return typeof value === \"string\" ? value : null;\n}\n\nasync function findContentCollection({\n collection,\n contentSource,\n}: {\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentCollection | null> {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return null;\n }\n\n const visibleEntries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n const entries = await Promise.all(\n visibleEntries.map(async (entry): Promise<ContentCollectionEntry> => {\n return {\n slug: entry.slug,\n title: readNonEmptyString(entry.values.title) ?? entry.slug,\n excerpt: readNonEmptyString(entry.values.excerpt),\n coverImage: readSafeImageUrl(entry.values.coverImage),\n coverImageAlt: readNonEmptyString(entry.values.coverImageAlt),\n publishedAt: readIsoDate(entry.values.publishedAt),\n status: await getEntryListingStatus({\n collection,\n slug: entry.slug,\n contentSource,\n }),\n };\n }),\n );\n entries.sort((left, right) => {\n const leftTime = left.publishedAt ? Date.parse(left.publishedAt) : 0;\n const rightTime = right.publishedAt ? Date.parse(right.publishedAt) : 0;\n return rightTime - leftTime || left.title.localeCompare(right.title);\n });\n\n return {\n slug: collection,\n name: collectionSchema.name,\n description: collectionSchema.description,\n entries,\n };\n}\n\nasync function findContentEntry({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry | null> {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return null;\n }\n return readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n });\n}\n\nasync function listCollectionSlugs({\n contentSource,\n}: {\n contentSource: ContentSource;\n}): Promise<string[]> {\n return (await contentSource.listCollections())\n .filter(isValidContentSlug)\n .sort();\n}\n\n/**\n * Every renderable entry of a collection, each file read and parsed exactly\n * once. Callers pass the already-read collection schema so no helper in this\n * chain ever re-reads it.\n */\nasync function listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n}: {\n collectionSchema: ContentCollectionFile;\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry[]> {\n const slugs = await contentSource.listEntries(collection);\n const entries = await Promise.all(\n slugs.map((slug) => {\n return readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n });\n }),\n );\n return entries.filter(isNotNullish);\n}\n\n// In the dev preview an entry is \"published\" when its draft matches its\n// published copy; production sources hold only published copies.\nasync function getEntryListingStatus({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<\"draft\" | \"published\"> {\n if (process.env.NODE_ENV !== \"development\") {\n return \"published\";\n }\n const [draft, publishedCopy] = await Promise.all([\n contentSource.read(`${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`),\n contentSource.read(\n `${collection}/${slug}${CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION}`,\n ),\n ]);\n return draft !== null && draft === publishedCopy ? \"published\" : \"draft\";\n}\n\n/** The collection's parsed schema, or null when missing, invalid, or archived. */\nasync function readActiveCollectionSchema({\n collection,\n contentSource,\n}: {\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentCollectionFile | null> {\n if (!isValidContentSlug(collection)) {\n return null;\n }\n const rawSchema = await contentSource.read(\n `${collection}/${CONTENT_COLLECTION_SCHEMA_FILENAME}`,\n );\n if (!rawSchema) {\n return null;\n }\n const parsedJson = parseJsonOrNull(rawSchema);\n if (parsedJson === null) {\n return null;\n }\n const result = contentCollectionFileSchema.safeParse(parsedJson);\n if (!result.success || isNotNullish(result.data.archivedAt)) {\n return null;\n }\n return result.data;\n}\n\n// The parsed document when the entry exists and is publicly visible, else\n// null. The one visibility gate for full entry loads and shallow reads alike.\nasync function readVisibleEntryDocument({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}) {\n if (!isValidContentSlug(slug)) {\n return null;\n }\n const rawEntry = await contentSource.read(\n `${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`,\n );\n if (!rawEntry) {\n return null;\n }\n const parsedDocument = parseContentEntryDocument(rawEntry);\n if (parsedDocument.status === \"invalid\") {\n return null;\n }\n // Visibility is structural: the sandbox preview (`next dev`) reads draft\n // files so everything renders there, while production reads the manifest,\n // which the publish tool builds from published copies only.\n return parsedDocument.document;\n}\n\nasync function readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n}: {\n collectionSchema: ContentCollectionFile;\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry | null> {\n const document = await readVisibleEntryDocument({\n collection,\n slug,\n contentSource,\n });\n if (!document) {\n return null;\n }\n\n const values = getContentRenderableEntryValues({\n document,\n slug,\n });\n // NOTE (Ryan, 2026-07-28): Drafts are work-in-progress, so the dev preview\n // renders them even when required fields are still incomplete.\n if (\n getContentEntryFieldValidationIssues({\n fields: collectionSchema.fields,\n values,\n requireRequiredFields: process.env.NODE_ENV !== \"development\",\n }).length > 0\n ) {\n return null;\n }\n\n // The editor's built-in Related Content section stores refs under `related`\n // without a schema field; surface them through a synthetic trailing field so\n // related links render for every collection, not only ones whose schema\n // declares a related field.\n const fields: ContentField[] = [...collectionSchema.fields];\n const hasRelatedField = fields.some((field) => field.type === \"related\");\n const storedRelated = values[CONTENT_RELATED_VALUE_STORAGE_KEY];\n if (\n !hasRelatedField &&\n Array.isArray(storedRelated) &&\n storedRelated.length > 0\n ) {\n fields.push({\n id: CONTENT_RELATED_VALUE_STORAGE_KEY,\n label: \"Related content\",\n type: \"related\",\n required: false,\n enabled: true,\n builtin: true,\n });\n }\n return {\n collection: {\n name: collectionSchema.name,\n description: collectionSchema.description,\n slug: collection,\n },\n fields,\n slug,\n body: document.body,\n values: await resolveRelatedFieldValues({\n fields,\n values,\n contentSource,\n }),\n };\n}\n\n/** A related entry resolved to a public link; unresolvable refs stay plain text. */\nexport interface ContentRelatedLink {\n title: string;\n href: string;\n}\n\n// Resolves each `related` field's stored content paths into public links with\n// the target entry's title. Targets that aren't publicly visible (drafts in\n// production, archived, deleted) stay out entirely rather than rendering a\n// dead link.\nasync function resolveRelatedFieldValues({\n fields,\n values,\n contentSource,\n}: {\n fields: ContentField[];\n values: Record<string, unknown>;\n contentSource: ContentSource;\n}): Promise<Record<string, unknown>> {\n const relatedFields = fields.filter((field) => field.type === \"related\");\n if (relatedFields.length === 0) {\n return values;\n }\n const resolved = { ...values };\n for (const field of relatedFields) {\n const raw = resolved[field.id];\n if (!Array.isArray(raw)) {\n continue;\n }\n const links: ContentRelatedLink[] = [];\n for (const item of raw) {\n if (typeof item !== \"string\") {\n continue;\n }\n const ref = parseContentEntryRef(item);\n if (!ref) {\n continue;\n }\n const { collectionSlug: targetCollection, entrySlug: targetSlug } = ref;\n const collectionSchema = await readActiveCollectionSchema({\n collection: targetCollection,\n contentSource,\n });\n if (!collectionSchema) {\n continue;\n }\n // A shallow document read on purpose: resolving the target through the\n // full entry loader would recurse through ITS related fields, looping\n // on mutually-related entries. Only visibility and the title matter.\n const target = await readVisibleEntryDocument({\n collection: targetCollection,\n slug: targetSlug,\n contentSource,\n });\n if (!target) {\n continue;\n }\n links.push({\n title: readNonEmptyString(target.values.title) ?? targetSlug,\n href: `/${targetCollection}/${targetSlug}`,\n });\n }\n resolved[field.id] = links;\n }\n return resolved;\n}\n\nconst contentRelatedLinkSchema = z.object({\n title: z.string(),\n href: z.string(),\n});\n\nfunction isContentRelatedLink(value: unknown): value is ContentRelatedLink {\n return contentRelatedLinkSchema.safeParse(value).success;\n}\n\n// Sites commonly ship a CSS reset (Tailwind's preflight sets\n// `text-decoration: inherit`) that strips link underlines, leaving body links\n// visually indistinguishable from text. State it inline so a link reads as a\n// link on any site, the same way images carry their own layout styles.\nconst CONTENT_LINK_STYLE = { textDecoration: \"underline\" } as const;\n\nfunction SafeMarkdown({ source }: { source: string }) {\n return (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n skipHtml\n components={{\n a: ({ children, ...props }) => (\n <a {...props} style={CONTENT_LINK_STYLE}>\n {children}\n </a>\n ),\n }}\n >\n {source}\n </ReactMarkdown>\n );\n}\n\nfunction readNonEmptyString(value: unknown): string | null {\n const parsed = z.string().safeParse(value);\n return parsed.success && parsed.data.trim().length > 0 ? parsed.data : null;\n}\n\nfunction readSafeImageUrl(value: unknown): string | null {\n const url = readNonEmptyString(value);\n return url !== null && isSafeContentUrl(url, { image: true }) ? url : null;\n}\n\nfunction readIsoDate(value: unknown): string | null {\n const raw = readNonEmptyString(value);\n return raw !== null && !Number.isNaN(Date.parse(raw)) ? raw : null;\n}\n\nfunction readValidDate(value: unknown): Date | null {\n const iso = readIsoDate(value);\n return iso === null ? null : new Date(iso);\n}\n\nfunction serializeJsonLd(value: Record<string, unknown>): string {\n return JSON.stringify(value)\n .replaceAll(\"<\", \"\\\\u003c\")\n .replaceAll(\"\\u2028\", \"\\\\u2028\")\n .replaceAll(\"\\u2029\", \"\\\\u2029\");\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&apos;\");\n}\n\nasync function readTextFileOrNull(path: string) {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n\nasync function readDirectoryOrEmpty(path: string) {\n try {\n return await readdir(path, { withFileTypes: true });\n } catch {\n return [];\n }\n}\n\nfunction getContentSource({\n contentSource,\n contentDirectory,\n contentManifest,\n}: ContentOptions): ContentSource {\n return (\n contentSource ?? createContentSource({ contentDirectory, contentManifest })\n );\n}\n\nfunction getDefaultContentDirectory() {\n return join(process.cwd(), CONTENT_DIRNAME);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  export const BUILD_METADATA = {
2
2
  "packageName": "@replohq/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "branch": "HEAD",
5
- "commit": "ac7b59160587bd6c8937d220fd31468a604b4a14",
6
- "builtAt": "2026-08-30T20:30:37.919Z"
5
+ "commit": "f5a23580e6a5cb8d580a67999fee0cdb795a026d",
6
+ "builtAt": "2026-09-01T23:19:53.856Z"
7
7
  };
package/next/config.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { existsSync } from "fs";
1
2
  import os from "os";
3
+ import path from "path";
2
4
  const PLATFORM_DEV_ORIGINS = [
3
5
  "localhost:*",
4
6
  "*.localhost:*",
@@ -15,6 +17,23 @@ const PLATFORM_SERVER_ACTION_ORIGINS = [
15
17
  "*.preview.reploapi.com",
16
18
  "*.preview-staging.reploapi.com"
17
19
  ];
20
+ const DEFAULT_PAGE_EXTENSIONS = ["tsx", "ts", "jsx", "js"];
21
+ const DEVELOPMENT_ONLY_PAGE_EXTENSIONS = ["dev.jsx", "dev.tsx"];
22
+ const BUILD_ONLY_PAGE_EXTENSIONS = ["published.tsx"];
23
+ const PUBLISH_TSCONFIG_FILENAME = "tsconfig.publish.json";
24
+ function resolvePageExtensions(configuredExtensions) {
25
+ const baseExtensions = (configuredExtensions ?? DEFAULT_PAGE_EXTENSIONS).filter((extension) => {
26
+ return !DEVELOPMENT_ONLY_PAGE_EXTENSIONS.includes(extension) && !BUILD_ONLY_PAGE_EXTENSIONS.includes(extension);
27
+ });
28
+ return process.env.BUILD_ENV === "build" ? [...BUILD_ONLY_PAGE_EXTENSIONS, ...baseExtensions] : [...DEVELOPMENT_ONLY_PAGE_EXTENSIONS, ...baseExtensions];
29
+ }
30
+ function resolvePublishTypescriptConfig(configured) {
31
+ if (process.env.BUILD_ENV !== "build") {
32
+ return configured;
33
+ }
34
+ const tsconfigPath = path.join(process.cwd(), PUBLISH_TSCONFIG_FILENAME);
35
+ return existsSync(tsconfigPath) ? { ...configured, tsconfigPath: PUBLISH_TSCONFIG_FILENAME } : configured;
36
+ }
18
37
  function siteConfig(overrides = {}) {
19
38
  const isBuild = process.env.BUILD_ENV === "build";
20
39
  const devRoot = process.env.IS_REPLO_SANDBOX ? "/workspace/dev" : process.cwd();
@@ -23,6 +42,8 @@ function siteConfig(overrides = {}) {
23
42
  images: overrideImages,
24
43
  experimental: overrideExperimental,
25
44
  allowedDevOrigins: overrideDevOrigins,
45
+ pageExtensions: overridePageExtensions,
46
+ typescript: overrideTypescript,
26
47
  ...restOverrides
27
48
  } = overrides;
28
49
  const { serverActions: overrideServerActions, ...restOverrideExperimental } = overrideExperimental ?? {};
@@ -35,6 +56,8 @@ function siteConfig(overrides = {}) {
35
56
  // can never break sandbox builds, publish, or the dev server.
36
57
  output: "standalone",
37
58
  distDir: isBuild ? ".next" : ".next-dev",
59
+ pageExtensions: resolvePageExtensions(overridePageExtensions),
60
+ typescript: resolvePublishTypescriptConfig(overrideTypescript),
38
61
  // NOTE (Cole, 2026-04-30, REPL-26637): The publish tool patches
39
62
  // required-server-files.json to clear this value before OpenNext bundles,
40
63
  // since "/" makes esbuild emit absolute-path requires that break in Workers.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../next/config.ts"],
4
- "sourcesContent": ["import os from \"os\";\nimport type { NextConfig } from \"next\";\n\n// Origins browsers load sandbox previews from \u2014 PREVIEW_HOSTNAME in the\n// coordinator's wrangler.jsonc plus Daytona's native preview domain. Next 16\n// hard-blocks cross-origin dev requests (HMR WebSocket, Server Actions) from\n// origins missing here.\nconst PLATFORM_DEV_ORIGINS = [\n \"localhost:*\",\n \"*.localhost:*\",\n \"*.lvh.me:*\",\n \"*.daytonaproxy01.net\",\n \"*.preview.reploapi.com\",\n \"*.preview-staging.reploapi.com\",\n];\n\nconst PLATFORM_SERVER_ACTION_ORIGINS = [\n \"localhost:*\",\n \"*.localhost:*\",\n \"*.lvh.me:*\",\n \"*.cloudflare-agent-coordinator.replo.workers.dev\",\n \"*.preview.reploapi.com\",\n \"*.preview-staging.reploapi.com\",\n];\n\n// The canonical Next.js runtime contract shared by every agent-built site.\n// Sites re-export `default` from here through a one-line `next.config.ts` shim,\n// so the platform wiring (sandbox dist dirs, Turbopack root, preview origins,\n// Cloudflare image loader) lives in exactly one place instead of being copied\n// into each template.\n//\n// `overrides` is a per-site customization surface, but it must not be able to\n// silently amputate platform wiring. A plain `...overrides` spread is a shallow\n// merge: a site that sets any nested key (e.g. `experimental.typedRoutes`)\n// would replace the whole `experimental`/`images` block and drop platform\n// defaults like the build-worker OOM guard or the Cloudflare image loader. So\n// load-bearing keys are re-asserted after the spread and the nested blocks are\n// deep-merged: a site can add keys, but platform invariants always win and\n// platform origin lists are preserved (site entries are appended, not swapped).\nexport function siteConfig(overrides: NextConfig = {}): NextConfig {\n const isBuild = process.env.BUILD_ENV === \"build\";\n const devRoot = process.env.IS_REPLO_SANDBOX\n ? \"/workspace/dev\"\n : process.cwd();\n const {\n turbopack: overrideTurbopack,\n images: overrideImages,\n experimental: overrideExperimental,\n allowedDevOrigins: overrideDevOrigins,\n ...restOverrides\n } = overrides;\n\n const { serverActions: overrideServerActions, ...restOverrideExperimental } =\n overrideExperimental ?? {};\n\n return {\n // Site-overridable defaults.\n reactStrictMode: true,\n devIndicators: false,\n ...restOverrides,\n\n // Platform invariants \u2014 re-asserted after the spread so a site override\n // can never break sandbox builds, publish, or the dev server.\n output: \"standalone\",\n distDir: isBuild ? \".next\" : \".next-dev\",\n // NOTE (Cole, 2026-04-30, REPL-26637): The publish tool patches\n // required-server-files.json to clear this value before OpenNext bundles,\n // since \"/\" makes esbuild emit absolute-path requires that break in Workers.\n // NOTE (Patrick Lu, 2026-07-06, REPL-28775): Leave outputFileTracingRoot\n // unset outside builds because Next 15.5+ otherwise forces turbopack.root to\n // \"/\" and makes the dev watcher retain whole-filesystem state.\n outputFileTracingRoot: isBuild ? \"/\" : undefined,\n // NOTE (Patrick Lu, 2026-07-06, REPL-28775): Match build tracing at publish\n // time; keep dev's watcher on the user app tree.\n turbopack: {\n ...overrideTurbopack,\n root: isBuild ? \"/\" : devRoot,\n },\n // @replohq/sdk ships as raw source with zero workspace deps, so we can't pull\n // in lodash-es `uniq` here \u2014 dedupe inline via a Set instead.\n allowedDevOrigins: Array.from(\n new Set([...(overrideDevOrigins ?? []), ...PLATFORM_DEV_ORIGINS]),\n ),\n images: {\n ...overrideImages,\n loader: \"custom\",\n loaderFile: \"./image-loader.ts\",\n remotePatterns: overrideImages?.remotePatterns ?? [\n {\n protocol: \"https\",\n hostname: \"**\",\n },\n ],\n },\n experimental: {\n ...restOverrideExperimental,\n // NOTE (Cole, 2026-06-18, REPL-28180): Next sizes its build worker pool\n // off os.cpus().length = host cores (~64) inside a cgroup-limited sandbox,\n // not the 2 the container actually has \u2014 a worker swarm that OOMs the\n // 4 GiB box. availableParallelism() is cgroup-aware. Gated to prod build.\n cpus: isBuild ? os.availableParallelism() : undefined,\n serverActions: {\n ...overrideServerActions,\n allowedOrigins: Array.from(\n new Set([\n ...(overrideServerActions?.allowedOrigins ?? []),\n ...PLATFORM_SERVER_ACTION_ORIGINS,\n ]),\n ),\n },\n },\n };\n}\n\nconst nextConfig: NextConfig = siteConfig();\n\nexport default nextConfig;\n"],
5
- "mappings": "AAAA,OAAO,QAAQ;AAOf,MAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,iCAAiC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAgBO,SAAS,WAAW,YAAwB,CAAC,GAAe;AACjE,QAAM,UAAU,QAAQ,IAAI,cAAc;AAC1C,QAAM,UAAU,QAAQ,IAAI,mBACxB,mBACA,QAAQ,IAAI;AAChB,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,EAAE,eAAe,uBAAuB,GAAG,yBAAyB,IACxE,wBAAwB,CAAC;AAE3B,SAAO;AAAA;AAAA,IAEL,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,GAAG;AAAA;AAAA;AAAA,IAIH,QAAQ;AAAA,IACR,SAAS,UAAU,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAO7B,uBAAuB,UAAU,MAAM;AAAA;AAAA;AAAA,IAGvC,WAAW;AAAA,MACT,GAAG;AAAA,MACH,MAAM,UAAU,MAAM;AAAA,IACxB;AAAA;AAAA;AAAA,IAGA,mBAAmB,MAAM;AAAA,MACvB,oBAAI,IAAI,CAAC,GAAI,sBAAsB,CAAC,GAAI,GAAG,oBAAoB,CAAC;AAAA,IAClE;AAAA,IACA,QAAQ;AAAA,MACN,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,gBAAgB,gBAAgB,kBAAkB;AAAA,QAChD;AAAA,UACE,UAAU;AAAA,UACV,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IACA,cAAc;AAAA,MACZ,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA,MAKH,MAAM,UAAU,GAAG,qBAAqB,IAAI;AAAA,MAC5C,eAAe;AAAA,QACb,GAAG;AAAA,QACH,gBAAgB,MAAM;AAAA,UACpB,oBAAI,IAAI;AAAA,YACN,GAAI,uBAAuB,kBAAkB,CAAC;AAAA,YAC9C,GAAG;AAAA,UACL,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,aAAyB,WAAW;AAE1C,IAAO,iBAAQ;",
4
+ "sourcesContent": ["import { existsSync } from \"fs\";\nimport os from \"os\";\nimport path from \"path\";\nimport type { NextConfig } from \"next\";\n\n// Origins browsers load sandbox previews from \u2014 PREVIEW_HOSTNAME in the\n// coordinator's wrangler.jsonc plus Daytona's native preview domain. Next 16\n// hard-blocks cross-origin dev requests (HMR WebSocket, Server Actions) from\n// origins missing here.\nconst PLATFORM_DEV_ORIGINS = [\n \"localhost:*\",\n \"*.localhost:*\",\n \"*.lvh.me:*\",\n \"*.daytonaproxy01.net\",\n \"*.preview.reploapi.com\",\n \"*.preview-staging.reploapi.com\",\n];\n\nconst PLATFORM_SERVER_ACTION_ORIGINS = [\n \"localhost:*\",\n \"*.localhost:*\",\n \"*.lvh.me:*\",\n \"*.cloudflare-agent-coordinator.replo.workers.dev\",\n \"*.preview.reploapi.com\",\n \"*.preview-staging.reploapi.com\",\n];\n\nconst DEFAULT_PAGE_EXTENSIONS = [\"tsx\", \"ts\", \"jsx\", \"js\"];\n// Routed only by the sandbox dev server: site-component previews and page drafts.\nconst DEVELOPMENT_ONLY_PAGE_EXTENSIONS = [\"dev.jsx\", \"dev.tsx\"];\n// Routed only by publish builds: the live copy of every page.\nconst BUILD_ONLY_PAGE_EXTENSIONS = [\"published.tsx\"];\nconst PUBLISH_TSCONFIG_FILENAME = \"tsconfig.publish.json\";\n\n// Pair routing is a platform invariant, not an editor nicety: without it a\n// build silently drops every page.published.tsx, so the site ships missing\n// routes. A site's own extensions are kept, but platform ones are filtered out\n// of the input and re-prepended so a site can never drop pair routing.\n// Behaviorally identical to @replohq/sandbox-runtime's wrapper, which\n// starter-scaffolded sites use instead of this function.\nfunction resolvePageExtensions(configuredExtensions: string[] | undefined) {\n const baseExtensions = (\n configuredExtensions ?? DEFAULT_PAGE_EXTENSIONS\n ).filter((extension) => {\n return (\n !DEVELOPMENT_ONLY_PAGE_EXTENSIONS.includes(extension) &&\n !BUILD_ONLY_PAGE_EXTENSIONS.includes(extension)\n );\n });\n\n return process.env.BUILD_ENV === \"build\"\n ? [...BUILD_ONLY_PAGE_EXTENSIONS, ...baseExtensions]\n : [...DEVELOPMENT_ONLY_PAGE_EXTENSIONS, ...baseExtensions];\n}\n\n// Drafts are excluded from the publish typecheck so unfinished pages never\n// block a publish; live files stay checked. Overrides the site's own\n// tsconfigPath during publish builds when the publish tsconfig is present.\nfunction resolvePublishTypescriptConfig(configured: NextConfig[\"typescript\"]) {\n if (process.env.BUILD_ENV !== \"build\") {\n return configured;\n }\n\n const tsconfigPath = path.join(process.cwd(), PUBLISH_TSCONFIG_FILENAME);\n return existsSync(tsconfigPath)\n ? { ...configured, tsconfigPath: PUBLISH_TSCONFIG_FILENAME }\n : configured;\n}\n\n// The canonical Next.js runtime contract shared by every agent-built site.\n// Sites re-export `default` from here through a one-line `next.config.ts` shim,\n// so the platform wiring (sandbox dist dirs, Turbopack root, preview origins,\n// Cloudflare image loader) lives in exactly one place instead of being copied\n// into each template.\n//\n// `overrides` is a per-site customization surface, but it must not be able to\n// silently amputate platform wiring. A plain `...overrides` spread is a shallow\n// merge: a site that sets any nested key (e.g. `experimental.typedRoutes`)\n// would replace the whole `experimental`/`images` block and drop platform\n// defaults like the build-worker OOM guard or the Cloudflare image loader. So\n// load-bearing keys are re-asserted after the spread and the nested blocks are\n// deep-merged: a site can add keys, but platform invariants always win and\n// platform origin lists are preserved (site entries are appended, not swapped).\nexport function siteConfig(overrides: NextConfig = {}): NextConfig {\n const isBuild = process.env.BUILD_ENV === \"build\";\n const devRoot = process.env.IS_REPLO_SANDBOX\n ? \"/workspace/dev\"\n : process.cwd();\n const {\n turbopack: overrideTurbopack,\n images: overrideImages,\n experimental: overrideExperimental,\n allowedDevOrigins: overrideDevOrigins,\n pageExtensions: overridePageExtensions,\n typescript: overrideTypescript,\n ...restOverrides\n } = overrides;\n\n const { serverActions: overrideServerActions, ...restOverrideExperimental } =\n overrideExperimental ?? {};\n\n return {\n // Site-overridable defaults.\n reactStrictMode: true,\n devIndicators: false,\n ...restOverrides,\n\n // Platform invariants \u2014 re-asserted after the spread so a site override\n // can never break sandbox builds, publish, or the dev server.\n output: \"standalone\",\n distDir: isBuild ? \".next\" : \".next-dev\",\n pageExtensions: resolvePageExtensions(overridePageExtensions),\n typescript: resolvePublishTypescriptConfig(overrideTypescript),\n // NOTE (Cole, 2026-04-30, REPL-26637): The publish tool patches\n // required-server-files.json to clear this value before OpenNext bundles,\n // since \"/\" makes esbuild emit absolute-path requires that break in Workers.\n // NOTE (Patrick Lu, 2026-07-06, REPL-28775): Leave outputFileTracingRoot\n // unset outside builds because Next 15.5+ otherwise forces turbopack.root to\n // \"/\" and makes the dev watcher retain whole-filesystem state.\n outputFileTracingRoot: isBuild ? \"/\" : undefined,\n // NOTE (Patrick Lu, 2026-07-06, REPL-28775): Match build tracing at publish\n // time; keep dev's watcher on the user app tree.\n turbopack: {\n ...overrideTurbopack,\n root: isBuild ? \"/\" : devRoot,\n },\n // @replohq/sdk ships as raw source with zero workspace deps, so we can't pull\n // in lodash-es `uniq` here \u2014 dedupe inline via a Set instead.\n allowedDevOrigins: Array.from(\n new Set([...(overrideDevOrigins ?? []), ...PLATFORM_DEV_ORIGINS]),\n ),\n images: {\n ...overrideImages,\n loader: \"custom\",\n loaderFile: \"./image-loader.ts\",\n remotePatterns: overrideImages?.remotePatterns ?? [\n {\n protocol: \"https\",\n hostname: \"**\",\n },\n ],\n },\n experimental: {\n ...restOverrideExperimental,\n // NOTE (Cole, 2026-06-18, REPL-28180): Next sizes its build worker pool\n // off os.cpus().length = host cores (~64) inside a cgroup-limited sandbox,\n // not the 2 the container actually has \u2014 a worker swarm that OOMs the\n // 4 GiB box. availableParallelism() is cgroup-aware. Gated to prod build.\n cpus: isBuild ? os.availableParallelism() : undefined,\n serverActions: {\n ...overrideServerActions,\n allowedOrigins: Array.from(\n new Set([\n ...(overrideServerActions?.allowedOrigins ?? []),\n ...PLATFORM_SERVER_ACTION_ORIGINS,\n ]),\n ),\n },\n },\n };\n}\n\nconst nextConfig: NextConfig = siteConfig();\n\nexport default nextConfig;\n"],
5
+ "mappings": "AAAA,SAAS,kBAAkB;AAC3B,OAAO,QAAQ;AACf,OAAO,UAAU;AAOjB,MAAM,uBAAuB;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,iCAAiC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,0BAA0B,CAAC,OAAO,MAAM,OAAO,IAAI;AAEzD,MAAM,mCAAmC,CAAC,WAAW,SAAS;AAE9D,MAAM,6BAA6B,CAAC,eAAe;AACnD,MAAM,4BAA4B;AAQlC,SAAS,sBAAsB,sBAA4C;AACzE,QAAM,kBACJ,wBAAwB,yBACxB,OAAO,CAAC,cAAc;AACtB,WACE,CAAC,iCAAiC,SAAS,SAAS,KACpD,CAAC,2BAA2B,SAAS,SAAS;AAAA,EAElD,CAAC;AAED,SAAO,QAAQ,IAAI,cAAc,UAC7B,CAAC,GAAG,4BAA4B,GAAG,cAAc,IACjD,CAAC,GAAG,kCAAkC,GAAG,cAAc;AAC7D;AAKA,SAAS,+BAA+B,YAAsC;AAC5E,MAAI,QAAQ,IAAI,cAAc,SAAS;AACrC,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,KAAK,KAAK,QAAQ,IAAI,GAAG,yBAAyB;AACvE,SAAO,WAAW,YAAY,IAC1B,EAAE,GAAG,YAAY,cAAc,0BAA0B,IACzD;AACN;AAgBO,SAAS,WAAW,YAAwB,CAAC,GAAe;AACjE,QAAM,UAAU,QAAQ,IAAI,cAAc;AAC1C,QAAM,UAAU,QAAQ,IAAI,mBACxB,mBACA,QAAQ,IAAI;AAChB,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,gBAAgB;AAAA,IAChB,YAAY;AAAA,IACZ,GAAG;AAAA,EACL,IAAI;AAEJ,QAAM,EAAE,eAAe,uBAAuB,GAAG,yBAAyB,IACxE,wBAAwB,CAAC;AAE3B,SAAO;AAAA;AAAA,IAEL,iBAAiB;AAAA,IACjB,eAAe;AAAA,IACf,GAAG;AAAA;AAAA;AAAA,IAIH,QAAQ;AAAA,IACR,SAAS,UAAU,UAAU;AAAA,IAC7B,gBAAgB,sBAAsB,sBAAsB;AAAA,IAC5D,YAAY,+BAA+B,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAO7D,uBAAuB,UAAU,MAAM;AAAA;AAAA;AAAA,IAGvC,WAAW;AAAA,MACT,GAAG;AAAA,MACH,MAAM,UAAU,MAAM;AAAA,IACxB;AAAA;AAAA;AAAA,IAGA,mBAAmB,MAAM;AAAA,MACvB,oBAAI,IAAI,CAAC,GAAI,sBAAsB,CAAC,GAAI,GAAG,oBAAoB,CAAC;AAAA,IAClE;AAAA,IACA,QAAQ;AAAA,MACN,GAAG;AAAA,MACH,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,gBAAgB,gBAAgB,kBAAkB;AAAA,QAChD;AAAA,UACE,UAAU;AAAA,UACV,UAAU;AAAA,QACZ;AAAA,MACF;AAAA,IACF;AAAA,IACA,cAAc;AAAA,MACZ,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA,MAKH,MAAM,UAAU,GAAG,qBAAqB,IAAI;AAAA,MAC5C,eAAe;AAAA,QACb,GAAG;AAAA,QACH,gBAAgB,MAAM;AAAA,UACpB,oBAAI,IAAI;AAAA,YACN,GAAI,uBAAuB,kBAAkB,CAAC;AAAA,YAC9C,GAAG;AAAA,UACL,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,aAAyB,WAAW;AAE1C,IAAO,iBAAQ;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@replohq/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "reploBuild": {
5
5
  "packageName": "@replohq/sdk",
6
- "version": "1.0.0",
6
+ "version": "1.2.0",
7
7
  "branch": "HEAD",
8
- "commit": "ac7b59160587bd6c8937d220fd31468a604b4a14",
9
- "builtAt": "2026-08-30T20:30:37.919Z"
8
+ "commit": "f5a23580e6a5cb8d580a67999fee0cdb795a026d",
9
+ "builtAt": "2026-09-01T23:19:53.856Z"
10
10
  },
11
11
  "description": "Replo SDK — cart, analytics, and data loaders for agent-built Next.js sites.",
12
12
  "license": "SEE LICENSE IN LICENSE",