@replohq/sdk 0.15.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_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/cart/buy-now-action.d.ts +7 -0
- package/cart/buy-now-action.js +20 -7
- package/cart/buy-now-action.js.map +2 -2
- package/cart/hooks/use-buy-now.d.ts +23 -4
- package/cart/hooks/use-buy-now.js +6 -2
- package/cart/hooks/use-buy-now.js.map +2 -2
- package/cart/return-urls.d.ts +10 -0
- package/cart/return-urls.js +44 -0
- package/cart/return-urls.js.map +7 -0
- package/cart/storefront-urls.d.ts +1 -10
- package/cart/storefront-urls.js +2 -41
- package/cart/storefront-urls.js.map +2 -2
- 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/package.json +12 -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) => ({
|
package/cart/buy-now-action.d.ts
CHANGED
|
@@ -17,5 +17,12 @@ export declare function buyNowAction(input: {
|
|
|
17
17
|
discountCodes: string[];
|
|
18
18
|
pageUrl: string;
|
|
19
19
|
}): Promise<{
|
|
20
|
+
result: "created";
|
|
20
21
|
redirectUrl: string;
|
|
22
|
+
} | {
|
|
23
|
+
result: "outOfStock";
|
|
24
|
+
variants: {
|
|
25
|
+
variantId: string;
|
|
26
|
+
available: number;
|
|
27
|
+
}[];
|
|
21
28
|
}>;
|
package/cart/buy-now-action.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getCheckoutProvider } from "../lib/integration-utils";
|
|
|
8
8
|
import { buildBaseUrl } from "../lib/url-utils";
|
|
9
9
|
import { createCartAction } from "./cart-actions";
|
|
10
10
|
import { REPLO_ATTRIBUTION_PROPERTY } from "./cart-types";
|
|
11
|
-
import {
|
|
11
|
+
import { getReturnUrls } from "./return-urls";
|
|
12
12
|
class BuyNowError extends CanopyError {
|
|
13
13
|
}
|
|
14
14
|
class BuyNowCartCreationError extends CanopyError {
|
|
@@ -20,7 +20,7 @@ async function buyNowAction(input) {
|
|
|
20
20
|
if (!host) {
|
|
21
21
|
throw new BuyNowError({ message: "Missing host header" });
|
|
22
22
|
}
|
|
23
|
-
const { origin, cancelUrl } =
|
|
23
|
+
const { origin, cancelUrl } = getReturnUrls({
|
|
24
24
|
host,
|
|
25
25
|
forwardedHost: headersList.get("x-forwarded-host"),
|
|
26
26
|
forwardedProto: headersList.get("x-forwarded-proto"),
|
|
@@ -71,14 +71,27 @@ async function buyNowAction(input) {
|
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
74
|
+
const payload = await response.json();
|
|
75
|
+
const outOfStock = z.object({
|
|
76
|
+
result: z.literal("outOfStock"),
|
|
77
|
+
variants: z.array(
|
|
78
|
+
z.object({
|
|
79
|
+
variantId: z.string(),
|
|
80
|
+
available: z.number().int().min(0)
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
}).safeParse(payload);
|
|
84
|
+
if (outOfStock.success) {
|
|
85
|
+
return { result: "outOfStock", variants: outOfStock.data.variants };
|
|
86
|
+
}
|
|
87
|
+
const created = z.object({ url: z.string() }).safeParse(payload);
|
|
88
|
+
if (!created.success) {
|
|
76
89
|
throw new BuyNowCartCreationError({
|
|
77
90
|
message: "Stripe checkout session response missing redirect URL",
|
|
78
|
-
cause:
|
|
91
|
+
cause: created.error
|
|
79
92
|
});
|
|
80
93
|
}
|
|
81
|
-
return { redirectUrl:
|
|
94
|
+
return { result: "created", redirectUrl: created.data.url };
|
|
82
95
|
}
|
|
83
96
|
const cart = await createCartAction({
|
|
84
97
|
lines: cartLines,
|
|
@@ -92,7 +105,7 @@ async function buyNowAction(input) {
|
|
|
92
105
|
});
|
|
93
106
|
}
|
|
94
107
|
const checkoutUrl = new URL(cart.checkoutUrl, origin);
|
|
95
|
-
return { redirectUrl: checkoutUrl.toString() };
|
|
108
|
+
return { result: "created", redirectUrl: checkoutUrl.toString() };
|
|
96
109
|
}
|
|
97
110
|
export {
|
|
98
111
|
buyNowAction
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/buy-now-action.ts"],
|
|
4
|
-
"sourcesContent": ["\"use server\";\n\nimport type { CartLineAttribute } from \"./cart-types\";\n\nimport { headers } from \"next/headers\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { z } from \"zod\";\n\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\nimport { createCartAction } from \"./cart-actions\";\nimport { REPLO_ATTRIBUTION_PROPERTY } from \"./cart-types\";\nimport {
|
|
5
|
-
"mappings": ";AAIA,SAAS,eAAe;AAExB,SAAS,MAAM,cAAc;AAC7B,SAAS,SAAS;AAElB,SAAS,cAAc;AACvB,SAAS,mBAAmB;AAC5B,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,kCAAkC;AAC3C,SAAS,
|
|
4
|
+
"sourcesContent": ["\"use server\";\n\nimport type { CartLineAttribute } from \"./cart-types\";\n\nimport { headers } from \"next/headers\";\n\nimport { v4 as uuidv4 } from \"uuid\";\nimport { z } from \"zod\";\n\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { getCheckoutProvider } from \"../lib/integration-utils\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\nimport { createCartAction } from \"./cart-actions\";\nimport { REPLO_ATTRIBUTION_PROPERTY } from \"./cart-types\";\nimport { getReturnUrls } from \"./return-urls\";\n\nclass BuyNowError extends CanopyError {}\nclass BuyNowCartCreationError extends CanopyError {}\n\n/**\n * Server action that handles the full buy-now flow:\n * 1. For Shopify: creates a cart via the canopy-api-backed gateway, returns checkoutUrl.\n * 2. For Stripe: creates a Stripe checkout session via canopy-api, returns session URL.\n *\n * Variant IDs are expected to already be Storefront GIDs (from data loader output).\n * No internal\u2192external ID mapping is needed.\n */\nexport async function buyNowAction(input: {\n lineItems: {\n variantId: string;\n quantity: number;\n sellingPlanId: string | null;\n properties: CartLineAttribute[];\n }[];\n discountCodes: string[];\n pageUrl: string;\n}): Promise<\n | { result: \"created\"; redirectUrl: string }\n | {\n result: \"outOfStock\";\n // Each short variant with the purchasable remainder, so the cart can\n // clamp quantities to what's in stock instead of dropping whole lines.\n variants: { variantId: string; available: number }[];\n }\n> {\n const { lineItems, discountCodes, pageUrl } = input;\n\n const headersList = await headers();\n const host = headersList.get(\"host\");\n if (!host) {\n throw new BuyNowError({ message: \"Missing host header\" });\n }\n const { origin, cancelUrl } = getReturnUrls({\n host,\n forwardedHost: headersList.get(\"x-forwarded-host\"),\n forwardedProto: headersList.get(\"x-forwarded-proto\"),\n requestOrigin: headersList.get(\"origin\"),\n pageUrl,\n });\n\n const checkoutProvider = await getCheckoutProvider();\n\n const cartLines = lineItems.map((item) => {\n const hasReploAttribution = item.properties.some(\n (prop) => prop.key === REPLO_ATTRIBUTION_PROPERTY.key,\n );\n const properties = hasReploAttribution\n ? item.properties\n : [REPLO_ATTRIBUTION_PROPERTY, ...item.properties];\n\n return {\n id: item.variantId,\n quantity: item.quantity,\n sellingPlanId: item.sellingPlanId,\n properties,\n };\n });\n\n if (checkoutProvider === \"stripe\") {\n const env = await getEnv();\n const temporaryCartId = uuidv4();\n\n const body = {\n cartId: temporaryCartId,\n lines: cartLines.map((line) => ({\n variantId: line.id,\n quantity: line.quantity,\n properties: line.properties,\n })),\n projectId: env.PROJECT_ID,\n // canopy appends session_id={CHECKOUT_SESSION_ID} at session creation;\n // the success route resolves it into the order status page.\n successUrl: `${origin}/checkout/success`,\n cancelUrl,\n };\n\n const canopyApiUrl = `${buildBaseUrl(env.CANOPY_API_HOST)}/api/v1/stripe-checkout/create-session`;\n const response = await fetch(canopyApiUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n\n if (!response.ok) {\n throw new BuyNowCartCreationError({\n message: \"Failed to create Stripe checkout session for buy now\",\n additionalData: {\n status: response.status,\n statusText: response.statusText,\n },\n });\n }\n\n const payload: unknown = await response.json();\n const outOfStock = z\n .object({\n result: z.literal(\"outOfStock\"),\n variants: z.array(\n z.object({\n variantId: z.string(),\n available: z.number().int().min(0),\n }),\n ),\n })\n .safeParse(payload);\n if (outOfStock.success) {\n return { result: \"outOfStock\", variants: outOfStock.data.variants };\n }\n const created = z.object({ url: z.string() }).safeParse(payload);\n if (!created.success) {\n throw new BuyNowCartCreationError({\n message: \"Stripe checkout session response missing redirect URL\",\n cause: created.error,\n });\n }\n return { result: \"created\", redirectUrl: created.data.url };\n }\n\n // Shopify and other integrations: create cart via canopy-api-backed gateway\n const cart = await createCartAction({\n lines: cartLines,\n skipStoringCart: true,\n discountCodes: discountCodes.length > 0 ? discountCodes : undefined,\n });\n\n if (!cart?.checkoutUrl) {\n throw new BuyNowCartCreationError({\n message: \"Failed to create cart or get checkout URL\",\n additionalData: { lineItems, discountCodes },\n });\n }\n\n const checkoutUrl = new URL(cart.checkoutUrl, origin);\n return { result: \"created\", redirectUrl: checkoutUrl.toString() };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a `buyNow` call did. Out of stock is an expected storefront outcome
|
|
3
|
+
* (a buyer can race the last unit), so it rides the return value rather than
|
|
4
|
+
* a thrown error; `variants` carries each short variant with the purchasable
|
|
5
|
+
* remainder so the button can render a sold-out state.
|
|
6
|
+
*/
|
|
7
|
+
export type BuyNowOutcome = {
|
|
8
|
+
result: "redirected";
|
|
9
|
+
} | {
|
|
10
|
+
result: "outOfStock";
|
|
11
|
+
variants: {
|
|
12
|
+
variantId: string;
|
|
13
|
+
available: number;
|
|
14
|
+
}[];
|
|
15
|
+
};
|
|
1
16
|
export interface VariantBuyNowInfo {
|
|
2
17
|
variantId: string;
|
|
3
18
|
quantity?: number;
|
|
@@ -15,13 +30,14 @@ export interface VariantBuyNowInfo {
|
|
|
15
30
|
*
|
|
16
31
|
* function MyComponent({ selectedVariant, selectedSellingPlan, quantity }) {
|
|
17
32
|
* const { buyNow } = useBuyNow();
|
|
33
|
+
* const [isSoldOut, setIsSoldOut] = React.useState(false);
|
|
18
34
|
*
|
|
19
35
|
* return (
|
|
20
36
|
* <Button
|
|
21
|
-
* disabled={!selectedVariant?.availableForSale}
|
|
37
|
+
* disabled={!selectedVariant?.availableForSale || isSoldOut}
|
|
22
38
|
* onClick={async () => {
|
|
23
39
|
* if (selectedVariant) {
|
|
24
|
-
* await buyNow(
|
|
40
|
+
* const outcome = await buyNow(
|
|
25
41
|
* [
|
|
26
42
|
* {
|
|
27
43
|
* variantId: selectedVariant.id,
|
|
@@ -31,10 +47,13 @@ export interface VariantBuyNowInfo {
|
|
|
31
47
|
* ],
|
|
32
48
|
* { discountCodes: ["SUMMER2024"] }
|
|
33
49
|
* );
|
|
50
|
+
* if (outcome.result === "outOfStock") {
|
|
51
|
+
* setIsSoldOut(true);
|
|
52
|
+
* }
|
|
34
53
|
* }
|
|
35
54
|
* }}
|
|
36
55
|
* >
|
|
37
|
-
* Shop Now
|
|
56
|
+
* {isSoldOut ? "Sold out" : "Shop Now"}
|
|
38
57
|
* </Button>
|
|
39
58
|
* );
|
|
40
59
|
* }
|
|
@@ -45,5 +64,5 @@ export interface VariantBuyNowInfo {
|
|
|
45
64
|
export declare function useBuyNow(): {
|
|
46
65
|
buyNow: (variants: VariantBuyNowInfo[], options?: {
|
|
47
66
|
discountCodes?: string[];
|
|
48
|
-
}) => Promise<
|
|
67
|
+
}) => Promise<BuyNowOutcome>;
|
|
49
68
|
};
|
|
@@ -21,7 +21,7 @@ function useBuyNow() {
|
|
|
21
21
|
message: "No variants provided"
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
-
const
|
|
24
|
+
const buyNowResult = await buyNowAction({
|
|
25
25
|
lineItems: variants.map((variant) => ({
|
|
26
26
|
variantId: variant.variantId,
|
|
27
27
|
quantity: variant.quantity ?? 1,
|
|
@@ -31,7 +31,10 @@ function useBuyNow() {
|
|
|
31
31
|
discountCodes: options?.discountCodes ?? [],
|
|
32
32
|
pageUrl: window.location.href
|
|
33
33
|
});
|
|
34
|
-
|
|
34
|
+
if (buyNowResult.result === "outOfStock") {
|
|
35
|
+
return { result: "outOfStock", variants: buyNowResult.variants };
|
|
36
|
+
}
|
|
37
|
+
const url = new URL(buyNowResult.redirectUrl);
|
|
35
38
|
const isStripeCheckout = url.hostname.endsWith("stripe.com");
|
|
36
39
|
if (!isStripeCheckout) {
|
|
37
40
|
const currentParams = new URLSearchParams(window.location.search);
|
|
@@ -52,6 +55,7 @@ function useBuyNow() {
|
|
|
52
55
|
}
|
|
53
56
|
}
|
|
54
57
|
navigateExternal(url.toString());
|
|
58
|
+
return { result: "redirected" };
|
|
55
59
|
},
|
|
56
60
|
[reploSessionString]
|
|
57
61
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../cart/hooks/use-buy-now.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Use useBuyNow() hook to create \"Buy Now\" buttons that skip the cart and go directly to checkout.\n * Calls a server action to create the cart/checkout session, then redirects the browser.\n * @module\n */\n\"use client\";\n\nimport React from \"react\";\n\nimport { useClientValue } from \"../../hooks/use-client-value\";\nimport { CanopyError } from \"../../lib/canopy-error\";\nimport { navigateExternal } from \"../../lib/external-navigation\";\nimport { buyNowAction } from \"../buy-now-action\";\n\n// Analytics constants - defined locally to avoid dependency on analytics-client package\nconst REPLO_LOCAL_STORAGE_SESSION_KEY = \"_replo_sid\";\nconst REPLO_SESSION_ID_KEY = \"rsid\";\nconst REPLO_CLIENT_ID_KEY = \"rclid\";\n\n// Analytics session data type - defined locally to avoid dependency\ninterface AnalyticsSessionData {\n sessionId: string;\n reploId: string;\n expiresAt: number;\n createdAt: number;\n conversionsCounter: number;\n isEntryPage: boolean;\n}\n\nclass ProductHasNoVariantsError extends CanopyError {}\n\nexport interface VariantBuyNowInfo {\n variantId: string;\n quantity?: number;\n sellingPlanId?: string | null;\n}\n\n/**\n * React hook that returns a `buyNow` function for express checkout flows.\n * Handles creating the cart/checkout session server-side and redirecting\n * the browser to checkout.\n *\n * @example\n *\n * ```tsx\n * import { useBuyNow } from \"@replohq/sdk/cart/hooks/use-buy-now\";\n *\n * function MyComponent({ selectedVariant, selectedSellingPlan, quantity }) {\n * const { buyNow } = useBuyNow();\n *\n * return (\n * <Button\n * disabled={!selectedVariant?.availableForSale}\n * onClick={async () => {\n * if (selectedVariant) {\n * await buyNow(\n * [\n * {\n * variantId: selectedVariant.id,\n * quantity,\n * sellingPlanId: selectedSellingPlan?.id,\n * },\n * ],\n * { discountCodes: [\"SUMMER2024\"] }\n * );\n * }\n * }}\n * >\n * Shop Now\n * </Button>\n * );\n * }\n * ```\n *\n * @returns Object containing the buyNow function\n */\nexport function useBuyNow() {\n const reploSessionString = useClientValue(\n () => localStorage.getItem(REPLO_LOCAL_STORAGE_SESSION_KEY),\n null,\n );\n\n const buyNow = React.useCallback(\n async (\n variants: VariantBuyNowInfo[],\n options?: { discountCodes?: string[] },\n ) => {\n if (!variants || variants.length === 0) {\n throw new ProductHasNoVariantsError({\n message: \"No variants provided\",\n });\n }\n\n const
|
|
5
|
-
"mappings": ";AAOA,OAAO,WAAW;AAElB,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAG7B,MAAM,kCAAkC;AACxC,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAY5B,MAAM,kCAAkC,YAAY;AAAC;
|
|
4
|
+
"sourcesContent": ["/**\n * Use useBuyNow() hook to create \"Buy Now\" buttons that skip the cart and go directly to checkout.\n * Calls a server action to create the cart/checkout session, then redirects the browser.\n * @module\n */\n\"use client\";\n\nimport React from \"react\";\n\nimport { useClientValue } from \"../../hooks/use-client-value\";\nimport { CanopyError } from \"../../lib/canopy-error\";\nimport { navigateExternal } from \"../../lib/external-navigation\";\nimport { buyNowAction } from \"../buy-now-action\";\n\n// Analytics constants - defined locally to avoid dependency on analytics-client package\nconst REPLO_LOCAL_STORAGE_SESSION_KEY = \"_replo_sid\";\nconst REPLO_SESSION_ID_KEY = \"rsid\";\nconst REPLO_CLIENT_ID_KEY = \"rclid\";\n\n// Analytics session data type - defined locally to avoid dependency\ninterface AnalyticsSessionData {\n sessionId: string;\n reploId: string;\n expiresAt: number;\n createdAt: number;\n conversionsCounter: number;\n isEntryPage: boolean;\n}\n\nclass ProductHasNoVariantsError extends CanopyError {}\n\n/**\n * What a `buyNow` call did. Out of stock is an expected storefront outcome\n * (a buyer can race the last unit), so it rides the return value rather than\n * a thrown error; `variants` carries each short variant with the purchasable\n * remainder so the button can render a sold-out state.\n */\nexport type BuyNowOutcome =\n | { result: \"redirected\" }\n | {\n result: \"outOfStock\";\n variants: { variantId: string; available: number }[];\n };\n\nexport interface VariantBuyNowInfo {\n variantId: string;\n quantity?: number;\n sellingPlanId?: string | null;\n}\n\n/**\n * React hook that returns a `buyNow` function for express checkout flows.\n * Handles creating the cart/checkout session server-side and redirecting\n * the browser to checkout.\n *\n * @example\n *\n * ```tsx\n * import { useBuyNow } from \"@replohq/sdk/cart/hooks/use-buy-now\";\n *\n * function MyComponent({ selectedVariant, selectedSellingPlan, quantity }) {\n * const { buyNow } = useBuyNow();\n * const [isSoldOut, setIsSoldOut] = React.useState(false);\n *\n * return (\n * <Button\n * disabled={!selectedVariant?.availableForSale || isSoldOut}\n * onClick={async () => {\n * if (selectedVariant) {\n * const outcome = await buyNow(\n * [\n * {\n * variantId: selectedVariant.id,\n * quantity,\n * sellingPlanId: selectedSellingPlan?.id,\n * },\n * ],\n * { discountCodes: [\"SUMMER2024\"] }\n * );\n * if (outcome.result === \"outOfStock\") {\n * setIsSoldOut(true);\n * }\n * }\n * }}\n * >\n * {isSoldOut ? \"Sold out\" : \"Shop Now\"}\n * </Button>\n * );\n * }\n * ```\n *\n * @returns Object containing the buyNow function\n */\nexport function useBuyNow() {\n const reploSessionString = useClientValue(\n () => localStorage.getItem(REPLO_LOCAL_STORAGE_SESSION_KEY),\n null,\n );\n\n const buyNow = React.useCallback(\n async (\n variants: VariantBuyNowInfo[],\n options?: { discountCodes?: string[] },\n ): Promise<BuyNowOutcome> => {\n if (!variants || variants.length === 0) {\n throw new ProductHasNoVariantsError({\n message: \"No variants provided\",\n });\n }\n\n const buyNowResult = await buyNowAction({\n lineItems: variants.map((variant) => ({\n variantId: variant.variantId,\n quantity: variant.quantity ?? 1,\n sellingPlanId: variant.sellingPlanId ?? null,\n properties: [],\n })),\n discountCodes: options?.discountCodes ?? [],\n pageUrl: window.location.href,\n });\n if (buyNowResult.result === \"outOfStock\") {\n return { result: \"outOfStock\", variants: buyNowResult.variants };\n }\n\n const url = new URL(buyNowResult.redirectUrl);\n\n // Only append tracking params for non-Stripe checkout URLs (e.g. Shopify).\n // Stripe checkout URLs should not be modified with extra query params.\n const isStripeCheckout = url.hostname.endsWith(\"stripe.com\");\n if (!isStripeCheckout) {\n const currentParams = new URLSearchParams(window.location.search);\n for (const [key, value] of currentParams.entries()) {\n if (!url.searchParams.has(key)) {\n url.searchParams.set(key, value);\n }\n }\n\n if (reploSessionString) {\n try {\n const sessionData: AnalyticsSessionData =\n JSON.parse(reploSessionString);\n if (sessionData.sessionId && sessionData.reploId) {\n url.searchParams.set(REPLO_SESSION_ID_KEY, sessionData.sessionId);\n url.searchParams.set(REPLO_CLIENT_ID_KEY, sessionData.reploId);\n }\n } catch {\n // Ignore invalid session data\n }\n }\n }\n\n navigateExternal(url.toString());\n return { result: \"redirected\" };\n },\n [reploSessionString],\n );\n\n return { buyNow };\n}\n"],
|
|
5
|
+
"mappings": ";AAOA,OAAO,WAAW;AAElB,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,oBAAoB;AAG7B,MAAM,kCAAkC;AACxC,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAY5B,MAAM,kCAAkC,YAAY;AAAC;AAgE9C,SAAS,YAAY;AAC1B,QAAM,qBAAqB;AAAA,IACzB,MAAM,aAAa,QAAQ,+BAA+B;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,SAAS,MAAM;AAAA,IACnB,OACE,UACA,YAC2B;AAC3B,UAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,cAAM,IAAI,0BAA0B;AAAA,UAClC,SAAS;AAAA,QACX,CAAC;AAAA,MACH;AAEA,YAAM,eAAe,MAAM,aAAa;AAAA,QACtC,WAAW,SAAS,IAAI,CAAC,aAAa;AAAA,UACpC,WAAW,QAAQ;AAAA,UACnB,UAAU,QAAQ,YAAY;AAAA,UAC9B,eAAe,QAAQ,iBAAiB;AAAA,UACxC,YAAY,CAAC;AAAA,QACf,EAAE;AAAA,QACF,eAAe,SAAS,iBAAiB,CAAC;AAAA,QAC1C,SAAS,OAAO,SAAS;AAAA,MAC3B,CAAC;AACD,UAAI,aAAa,WAAW,cAAc;AACxC,eAAO,EAAE,QAAQ,cAAc,UAAU,aAAa,SAAS;AAAA,MACjE;AAEA,YAAM,MAAM,IAAI,IAAI,aAAa,WAAW;AAI5C,YAAM,mBAAmB,IAAI,SAAS,SAAS,YAAY;AAC3D,UAAI,CAAC,kBAAkB;AACrB,cAAM,gBAAgB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AAChE,mBAAW,CAAC,KAAK,KAAK,KAAK,cAAc,QAAQ,GAAG;AAClD,cAAI,CAAC,IAAI,aAAa,IAAI,GAAG,GAAG;AAC9B,gBAAI,aAAa,IAAI,KAAK,KAAK;AAAA,UACjC;AAAA,QACF;AAEA,YAAI,oBAAoB;AACtB,cAAI;AACF,kBAAM,cACJ,KAAK,MAAM,kBAAkB;AAC/B,gBAAI,YAAY,aAAa,YAAY,SAAS;AAChD,kBAAI,aAAa,IAAI,sBAAsB,YAAY,SAAS;AAChE,kBAAI,aAAa,IAAI,qBAAqB,YAAY,OAAO;AAAA,YAC/D;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAEA,uBAAiB,IAAI,SAAS,CAAC;AAC/B,aAAO,EAAE,QAAQ,aAAa;AAAA,IAChC;AAAA,IACA,CAAC,kBAAkB;AAAA,EACrB;AAEA,SAAO,EAAE,OAAO;AAClB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function getReturnUrls({ host, forwardedHost, forwardedProto, requestOrigin, pageUrl, }: {
|
|
2
|
+
host: string;
|
|
3
|
+
forwardedHost: string | null;
|
|
4
|
+
forwardedProto: string | null;
|
|
5
|
+
requestOrigin: string | null;
|
|
6
|
+
pageUrl: string;
|
|
7
|
+
}): {
|
|
8
|
+
origin: string;
|
|
9
|
+
cancelUrl: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function firstHeaderValue(value) {
|
|
2
|
+
const firstValue = value?.split(",")[0]?.trim();
|
|
3
|
+
return firstValue ? firstValue : null;
|
|
4
|
+
}
|
|
5
|
+
function parseOrigin(value) {
|
|
6
|
+
if (!value) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
try {
|
|
10
|
+
return new URL(value).origin;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function getReturnUrls({
|
|
16
|
+
host,
|
|
17
|
+
forwardedHost,
|
|
18
|
+
forwardedProto,
|
|
19
|
+
requestOrigin,
|
|
20
|
+
pageUrl
|
|
21
|
+
}) {
|
|
22
|
+
const protocol = firstHeaderValue(forwardedProto) === "http" ? "http" : "https";
|
|
23
|
+
const proxyHost = firstHeaderValue(forwardedHost) ?? host;
|
|
24
|
+
const fallbackOrigin = `${protocol}://${proxyHost}`;
|
|
25
|
+
const trustedOrigins = /* @__PURE__ */ new Set([
|
|
26
|
+
fallbackOrigin,
|
|
27
|
+
parseOrigin(firstHeaderValue(requestOrigin))
|
|
28
|
+
]);
|
|
29
|
+
try {
|
|
30
|
+
const parsedPageUrl = new URL(pageUrl);
|
|
31
|
+
if (trustedOrigins.has(parsedPageUrl.origin)) {
|
|
32
|
+
return {
|
|
33
|
+
origin: parsedPageUrl.origin,
|
|
34
|
+
cancelUrl: parsedPageUrl.toString()
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return { origin: fallbackOrigin, cancelUrl: fallbackOrigin };
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
getReturnUrls
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=return-urls.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../cart/return-urls.ts"],
|
|
4
|
+
"sourcesContent": ["function firstHeaderValue(value: string | null) {\n const firstValue = value?.split(\",\")[0]?.trim();\n return firstValue ? firstValue : null;\n}\n\nfunction parseOrigin(value: string | null) {\n if (!value) {\n return null;\n }\n\n try {\n return new URL(value).origin;\n } catch {\n return null;\n }\n}\n\nexport function getReturnUrls({\n host,\n forwardedHost,\n forwardedProto,\n requestOrigin,\n pageUrl,\n}: {\n host: string;\n forwardedHost: string | null;\n forwardedProto: string | null;\n requestOrigin: string | null;\n pageUrl: string;\n}) {\n const protocol =\n firstHeaderValue(forwardedProto) === \"http\" ? \"http\" : \"https\";\n const proxyHost = firstHeaderValue(forwardedHost) ?? host;\n const fallbackOrigin = `${protocol}://${proxyHost}`;\n const trustedOrigins = new Set([\n fallbackOrigin,\n parseOrigin(firstHeaderValue(requestOrigin)),\n ]);\n\n try {\n const parsedPageUrl = new URL(pageUrl);\n if (trustedOrigins.has(parsedPageUrl.origin)) {\n return {\n origin: parsedPageUrl.origin,\n cancelUrl: parsedPageUrl.toString(),\n };\n }\n } catch {\n // The server-derived fallback below remains the only trusted destination.\n }\n\n return { origin: fallbackOrigin, cancelUrl: fallbackOrigin };\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB,OAAsB;AAC9C,QAAM,aAAa,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AAC9C,SAAO,aAAa,aAAa;AACnC;AAEA,SAAS,YAAY,OAAsB;AACzC,MAAI,CAAC,OAAO;AACV,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,IAAI,IAAI,KAAK,EAAE;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,WACJ,iBAAiB,cAAc,MAAM,SAAS,SAAS;AACzD,QAAM,YAAY,iBAAiB,aAAa,KAAK;AACrD,QAAM,iBAAiB,GAAG,QAAQ,MAAM,SAAS;AACjD,QAAM,iBAAiB,oBAAI,IAAI;AAAA,IAC7B;AAAA,IACA,YAAY,iBAAiB,aAAa,CAAC;AAAA,EAC7C,CAAC;AAED,MAAI;AACF,UAAM,gBAAgB,IAAI,IAAI,OAAO;AACrC,QAAI,eAAe,IAAI,cAAc,MAAM,GAAG;AAC5C,aAAO;AAAA,QACL,QAAQ,cAAc;AAAA,QACtB,WAAW,cAAc,SAAS;AAAA,MACpC;AAAA,IACF;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,SAAO,EAAE,QAAQ,gBAAgB,WAAW,eAAe;AAC7D;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,10 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
host: string;
|
|
3
|
-
forwardedHost: string | null;
|
|
4
|
-
forwardedProto: string | null;
|
|
5
|
-
requestOrigin: string | null;
|
|
6
|
-
pageUrl: string;
|
|
7
|
-
}): {
|
|
8
|
-
origin: string;
|
|
9
|
-
cancelUrl: string;
|
|
10
|
-
};
|
|
1
|
+
export { getReturnUrls as getStorefrontUrls } from "./return-urls";
|
package/cart/storefront-urls.js
CHANGED
|
@@ -1,44 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
const firstValue = value?.split(",")[0]?.trim();
|
|
3
|
-
return firstValue ? firstValue : null;
|
|
4
|
-
}
|
|
5
|
-
function parseOrigin(value) {
|
|
6
|
-
if (!value) {
|
|
7
|
-
return null;
|
|
8
|
-
}
|
|
9
|
-
try {
|
|
10
|
-
return new URL(value).origin;
|
|
11
|
-
} catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function getStorefrontUrls({
|
|
16
|
-
host,
|
|
17
|
-
forwardedHost,
|
|
18
|
-
forwardedProto,
|
|
19
|
-
requestOrigin,
|
|
20
|
-
pageUrl
|
|
21
|
-
}) {
|
|
22
|
-
const protocol = firstHeaderValue(forwardedProto) === "http" ? "http" : "https";
|
|
23
|
-
const proxyHost = firstHeaderValue(forwardedHost) ?? host;
|
|
24
|
-
const fallbackOrigin = `${protocol}://${proxyHost}`;
|
|
25
|
-
const trustedOrigins = /* @__PURE__ */ new Set([
|
|
26
|
-
fallbackOrigin,
|
|
27
|
-
parseOrigin(firstHeaderValue(requestOrigin))
|
|
28
|
-
]);
|
|
29
|
-
try {
|
|
30
|
-
const parsedPageUrl = new URL(pageUrl);
|
|
31
|
-
if (trustedOrigins.has(parsedPageUrl.origin)) {
|
|
32
|
-
return {
|
|
33
|
-
origin: parsedPageUrl.origin,
|
|
34
|
-
cancelUrl: parsedPageUrl.toString()
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
} catch {
|
|
38
|
-
}
|
|
39
|
-
return { origin: fallbackOrigin, cancelUrl: fallbackOrigin };
|
|
40
|
-
}
|
|
1
|
+
import { getReturnUrls } from "./return-urls";
|
|
41
2
|
export {
|
|
42
|
-
getStorefrontUrls
|
|
3
|
+
getReturnUrls as getStorefrontUrls
|
|
43
4
|
};
|
|
44
5
|
//# sourceMappingURL=storefront-urls.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../cart/storefront-urls.ts"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["// Compat alias: sites scaffolded before the return-urls rename import\n// `@replohq/sdk/cart/storefront-urls` and their source never updates, so this\n// re-export must stay published for as long as those sites are live.\n// eslint-disable-next-line replo/no-export-from -- compat alias entrypoint: deployed site code imports this path and only a re-export can keep it resolving.\nexport { getReturnUrls as getStorefrontUrls } from \"./return-urls\";\n"],
|
|
5
|
+
"mappings": "AAIA,SAA0B,qBAAyB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"branch": "HEAD",
|
|
5
|
-
"commit": "
|
|
6
|
-
"builtAt": "2026-
|
|
5
|
+
"commit": "3596a406ee1c678085915d00db96f52ad3f9ac76",
|
|
6
|
+
"builtAt": "2026-09-01T19:49:23.848Z"
|
|
7
7
|
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replohq/sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"reploBuild": {
|
|
5
5
|
"packageName": "@replohq/sdk",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "1.1.0",
|
|
7
7
|
"branch": "HEAD",
|
|
8
|
-
"commit": "
|
|
9
|
-
"builtAt": "2026-
|
|
8
|
+
"commit": "3596a406ee1c678085915d00db96f52ad3f9ac76",
|
|
9
|
+
"builtAt": "2026-09-01T19:49:23.848Z"
|
|
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",
|
|
@@ -65,6 +65,10 @@
|
|
|
65
65
|
"types": "./cart/gateways/cart-constants.d.ts",
|
|
66
66
|
"default": "./cart/gateways/cart-constants.js"
|
|
67
67
|
},
|
|
68
|
+
"./cart/return-urls": {
|
|
69
|
+
"types": "./cart/return-urls.d.ts",
|
|
70
|
+
"default": "./cart/return-urls.js"
|
|
71
|
+
},
|
|
68
72
|
"./money": {
|
|
69
73
|
"types": "./money.d.ts",
|
|
70
74
|
"default": "./money.js"
|
|
@@ -241,6 +245,10 @@
|
|
|
241
245
|
"types": "./_vendor/schemas/loaderKeys.d.ts",
|
|
242
246
|
"default": "./_vendor/schemas/loaderKeys.mjs"
|
|
243
247
|
},
|
|
248
|
+
"./cart/storefront-urls": {
|
|
249
|
+
"types": "./cart/storefront-urls.d.ts",
|
|
250
|
+
"default": "./cart/storefront-urls.js"
|
|
251
|
+
},
|
|
244
252
|
"./package.json": "./package.json"
|
|
245
253
|
},
|
|
246
254
|
"dependencies": {
|