@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.
- package/_vendor/schemas/contentCollection.d.ts +41 -15
- package/_vendor/schemas/contentCollection.mjs +1 -1
- package/_vendor/schemas/contentEntry.d.ts +12 -5
- package/_vendor/schemas/contentEntry.mjs +2 -2
- package/_vendor/schemas/contentManifest.d.ts +1 -2
- package/_vendor/schemas/contentManifest.mjs +7 -15
- package/{chunk-I23U2PUI.mjs → chunk-7OPH5UFQ.mjs} +20 -3
- package/{chunk-IQ5GRTXO.mjs → chunk-EUQVEPSD.mjs} +34 -11
- package/content.js +39 -24
- package/content.js.map +2 -2
- package/lib/buildMetadata.js +3 -3
- package/next/config.js +23 -0
- package/next/config.js.map +2 -2
- package/package.json +4 -4
|
@@ -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
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,
|
|
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-
|
|
2
|
-
import '../../chunk-
|
|
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,
|
|
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-
|
|
2
|
-
import { isValidContentSlug, CONTENT_COLLECTION_SCHEMA_FILENAME, CONTENT_ENTRY_FILE_EXTENSION, contentCollectionFileSchema,
|
|
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:
|
|
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:
|
|
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,
|
|
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
|
|
16
|
+
var LEGACY_CONTENT_ENTRY_STATUSES = [
|
|
16
17
|
"draft",
|
|
17
18
|
"published",
|
|
18
19
|
"archived"
|
|
19
20
|
];
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 (
|
|
27
|
-
return
|
|
40
|
+
if (hasDraft && hasPublishedCopy) {
|
|
41
|
+
return draftMatchesPublishedCopy ? "published" : "publishedWithChanges";
|
|
42
|
+
}
|
|
43
|
+
if (hasDraft) {
|
|
44
|
+
return "unpublished";
|
|
28
45
|
}
|
|
29
|
-
return
|
|
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
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
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:
|
|
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(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\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(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\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
|
}
|
package/lib/buildMetadata.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const BUILD_METADATA = {
|
|
2
2
|
"packageName": "@replohq/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-
|
|
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.
|
package/next/config.js.map
CHANGED
|
@@ -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;
|
|
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.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.2.0",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-
|
|
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",
|