@replohq/sdk 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/_vendor/replo-utils/lib/string.d.ts +0 -2
  2. package/_vendor/schemas/errors.d.ts +2 -3
  3. package/_vendor/schemas/generated/errors.d.ts +3 -0
  4. package/_vendor/schemas/loaderKeys.mjs +0 -1
  5. package/_vendor/schemas/money.d.ts +1 -1
  6. package/_vendor/schemas/money.mjs +1 -7
  7. package/_vendor/schemas/routing/locale.mjs +1 -1
  8. package/_vendor/schemas/routing/rules.mjs +1 -2
  9. package/analytics/analytics-provider.js +0 -1
  10. package/analytics/analytics-sink.js +0 -1
  11. package/analytics/get-analytics-sinks.js +0 -1
  12. package/analytics/hooks/use-product-viewed-analytics.js +0 -1
  13. package/analytics/replo-first-party-pixel.js +0 -1
  14. package/analytics/replo-pixel-script.js +0 -1
  15. package/analytics/sinks/converge-event-mapper.js +0 -1
  16. package/analytics/sinks/converge-sink.js +0 -1
  17. package/analytics/sinks/elevar-sink.js +0 -1
  18. package/analytics/sinks/ga4-sink.js +0 -4
  19. package/analytics/sinks/meta-sink.js +0 -4
  20. package/analytics/sinks/northbeam-sink.js +0 -1
  21. package/analytics/sinks/pinterest-sink.js +0 -4
  22. package/analytics/sinks/reddit-sink.js +0 -4
  23. package/analytics/sinks/replo-sink.js +0 -2
  24. package/analytics/sinks/snapchat-sink.js +0 -4
  25. package/analytics/sinks/tiktok-sink.js +0 -4
  26. package/analytics/sinks/window-sink.js +0 -3
  27. package/analytics/utils/analytics-utils.js +0 -1
  28. package/analytics/utils/navigation-tracker.js +0 -1
  29. package/analytics/utils/shopify-funnel-gate.js +0 -1
  30. package/cart/buy-now-action.js +0 -3
  31. package/cart/cart-actions.js +0 -1
  32. package/cart/cart-loader.js +0 -1
  33. package/cart/cart-provider.js +1 -2
  34. package/cart/cart-schema.js +0 -1
  35. package/cart/cart-types.js +0 -1
  36. package/cart/gateways/cart-constants.js +0 -2
  37. package/cart/gateways/cart-gateway.js +0 -1
  38. package/cart/gateways/cookie-cart-gateway.js +0 -5
  39. package/cart/gateways/default-cart-gateway.js +0 -1
  40. package/cart/gateways/resolver-cart-gateway.js +0 -5
  41. package/cart/gateways/stripe-cart-gateway.js +0 -1
  42. package/cart/hooks/use-add-to-cart.js +0 -4
  43. package/cart/hooks/use-buy-now.js +0 -1
  44. package/cart/return-urls.js +0 -1
  45. package/cart/storefront-urls.js +0 -1
  46. package/cart/utils/cart-adjustments.js +0 -1
  47. package/cart/utils/cart-analytics.js +0 -1
  48. package/cart/utils/cart-error.js +0 -1
  49. package/cart/utils/cart-utils.js +0 -1
  50. package/cart/utils/cookie-cart-persistence.js +0 -1
  51. package/cart/utils/variant-to-cart-line.js +0 -6
  52. package/chunk-7OPH5UFQ.mjs +0 -1
  53. package/chunk-AL3F36PH.mjs +5 -127
  54. package/chunk-EUQVEPSD.mjs +0 -1
  55. package/chunk-GV5FY7ZZ.mjs +0 -11
  56. package/chunk-UJCSKKID.mjs +0 -4
  57. package/chunk-VKO7P3ED.mjs +0 -2
  58. package/chunk-VPEVERWE.mjs +0 -1
  59. package/{chunk-5M7VU32I.mjs → chunk-YJQVNUW7.mjs} +1 -12
  60. package/consent/consent-action.js +0 -1
  61. package/consent/consent-actions.js +0 -1
  62. package/consent/consent-platform.js +0 -3
  63. package/consent/consent-store.js +0 -5
  64. package/consent/cookie.js +0 -1
  65. package/consent/inject-script-descriptors.js +0 -1
  66. package/consent/replo-scripts.js +0 -1
  67. package/consent/script-registration-store.js +0 -2
  68. package/consent/script-snippets.js +0 -7
  69. package/consent/types.js +0 -1
  70. package/consent/window-api.js +0 -1
  71. package/content.js +0 -1
  72. package/env.js +0 -1
  73. package/gateways/canopy-api.js +0 -10
  74. package/hooks/use-client-value.d.ts +1 -1
  75. package/hooks/use-client-value.js +0 -1
  76. package/hooks/use-formatted-price.js +0 -1
  77. package/lib/buildMetadata.js +3 -3
  78. package/lib/canopy-error.js +0 -1
  79. package/lib/external-navigation.d.ts +1 -1
  80. package/lib/external-navigation.js +0 -1
  81. package/lib/get-query-client.js +0 -1
  82. package/lib/integration-utils.js +0 -1
  83. package/lib/shopify-utils.js +0 -1
  84. package/lib/url-utils.js +0 -1
  85. package/loaders/collection-loader-args.js +0 -1
  86. package/loaders/collection-loader.js +0 -1
  87. package/loaders/collection-products-loader-args.js +0 -1
  88. package/loaders/collection-products-loader.js +0 -1
  89. package/loaders/contentful-entries-loader.js +0 -1
  90. package/loaders/contentful-entry-loader.js +0 -1
  91. package/loaders/invoke-loader-action.js +0 -1
  92. package/loaders/invoke-loader-server.js +0 -1
  93. package/loaders/invoke-loader.js +0 -1
  94. package/loaders/judgeme-review-aggregate-loader.js +0 -1
  95. package/loaders/judgeme-reviews-loader.js +0 -1
  96. package/loaders/levanta-product-loader.js +0 -1
  97. package/loaders/loader-error-boundary.d.ts +11 -2
  98. package/loaders/loader-error-boundary.js +52 -9
  99. package/loaders/loader-error-details.d.ts +2 -0
  100. package/loaders/loader-error-details.js +7 -0
  101. package/loaders/loader-keys.js +0 -1
  102. package/loaders/loader-schemas.js +0 -4
  103. package/loaders/loox-review-aggregate-loader.js +0 -1
  104. package/loaders/loox-reviews-loader.js +0 -1
  105. package/loaders/metaobject-loader.js +0 -1
  106. package/loaders/metaobjects-loader.js +0 -1
  107. package/loaders/okendo-review-aggregate-loader.js +0 -1
  108. package/loaders/okendo-reviews-loader.js +0 -1
  109. package/loaders/prefetch-loaders.js +0 -1
  110. package/loaders/preview-placeholder.js +0 -5
  111. package/loaders/product-loader-args.js +0 -1
  112. package/loaders/product-loader.js +0 -1
  113. package/loaders/rebuy-recommendations-loader.js +0 -1
  114. package/loaders/reviews-io-review-aggregate-loader.js +0 -1
  115. package/loaders/reviews-io-reviews-loader.js +0 -1
  116. package/loaders/smile-earning-rules-loader.js +0 -1
  117. package/loaders/smile-rewards-loader.js +0 -1
  118. package/loaders/smile-vip-tiers-loader.js +0 -1
  119. package/loaders/statsig-experiment-loader.js +0 -1
  120. package/loaders/yotpo-review-aggregate-loader.js +0 -1
  121. package/loaders/yotpo-reviews-loader.js +0 -1
  122. package/money.js +0 -1
  123. package/next/config.js +0 -18
  124. package/next/image-loader.js +6 -5
  125. package/next/open-next.js +0 -1
  126. package/orders/order-status-schema.js +0 -4
  127. package/orders/order-status.js +0 -3
  128. package/package.json +4 -4
  129. package/providers/query-provider.js +0 -1
  130. package/providers/replo-provider.js +4 -3
  131. package/routing/evaluate.js +0 -1
  132. package/routing/geo.js +0 -1
  133. package/routing/locale.js +0 -1
  134. package/routing/rules.js +0 -1
  135. package/analytics/analytics-provider.js.map +0 -7
  136. package/analytics/analytics-sink.js.map +0 -7
  137. package/analytics/get-analytics-sinks.js.map +0 -7
  138. package/analytics/hooks/use-product-viewed-analytics.js.map +0 -7
  139. package/analytics/replo-first-party-pixel.js.map +0 -7
  140. package/analytics/replo-pixel-script.js.map +0 -7
  141. package/analytics/sinks/converge-event-mapper.js.map +0 -7
  142. package/analytics/sinks/converge-sink.js.map +0 -7
  143. package/analytics/sinks/elevar-sink.js.map +0 -7
  144. package/analytics/sinks/ga4-sink.js.map +0 -7
  145. package/analytics/sinks/meta-sink.js.map +0 -7
  146. package/analytics/sinks/northbeam-sink.js.map +0 -7
  147. package/analytics/sinks/pinterest-sink.js.map +0 -7
  148. package/analytics/sinks/reddit-sink.js.map +0 -7
  149. package/analytics/sinks/replo-sink.js.map +0 -7
  150. package/analytics/sinks/snapchat-sink.js.map +0 -7
  151. package/analytics/sinks/tiktok-sink.js.map +0 -7
  152. package/analytics/sinks/window-sink.js.map +0 -7
  153. package/analytics/utils/analytics-utils.js.map +0 -7
  154. package/analytics/utils/navigation-tracker.js.map +0 -7
  155. package/analytics/utils/shopify-funnel-gate.js.map +0 -7
  156. package/cart/buy-now-action.js.map +0 -7
  157. package/cart/cart-actions.js.map +0 -7
  158. package/cart/cart-loader.js.map +0 -7
  159. package/cart/cart-provider.js.map +0 -7
  160. package/cart/cart-schema.js.map +0 -7
  161. package/cart/cart-types.js.map +0 -7
  162. package/cart/gateways/cart-constants.js.map +0 -7
  163. package/cart/gateways/cart-gateway.js.map +0 -7
  164. package/cart/gateways/cookie-cart-gateway.js.map +0 -7
  165. package/cart/gateways/default-cart-gateway.js.map +0 -7
  166. package/cart/gateways/resolver-cart-gateway.js.map +0 -7
  167. package/cart/gateways/stripe-cart-gateway.js.map +0 -7
  168. package/cart/hooks/use-add-to-cart.js.map +0 -7
  169. package/cart/hooks/use-buy-now.js.map +0 -7
  170. package/cart/return-urls.js.map +0 -7
  171. package/cart/storefront-urls.js.map +0 -7
  172. package/cart/utils/cart-adjustments.js.map +0 -7
  173. package/cart/utils/cart-analytics.js.map +0 -7
  174. package/cart/utils/cart-error.js.map +0 -7
  175. package/cart/utils/cart-utils.js.map +0 -7
  176. package/cart/utils/cookie-cart-persistence.js.map +0 -7
  177. package/cart/utils/variant-to-cart-line.js.map +0 -7
  178. package/consent/consent-action.js.map +0 -7
  179. package/consent/consent-actions.js.map +0 -7
  180. package/consent/consent-platform.js.map +0 -7
  181. package/consent/consent-store.js.map +0 -7
  182. package/consent/cookie.js.map +0 -7
  183. package/consent/inject-script-descriptors.js.map +0 -7
  184. package/consent/replo-scripts.js.map +0 -7
  185. package/consent/script-registration-store.js.map +0 -7
  186. package/consent/script-snippets.js.map +0 -7
  187. package/consent/types.js.map +0 -7
  188. package/consent/window-api.js.map +0 -7
  189. package/content.js.map +0 -7
  190. package/env.js.map +0 -7
  191. package/gateways/canopy-api.js.map +0 -7
  192. package/hooks/use-client-value.js.map +0 -7
  193. package/hooks/use-formatted-price.js.map +0 -7
  194. package/lib/buildMetadata.js.map +0 -7
  195. package/lib/canopy-error.js.map +0 -7
  196. package/lib/external-navigation.js.map +0 -7
  197. package/lib/get-query-client.js.map +0 -7
  198. package/lib/integration-utils.js.map +0 -7
  199. package/lib/shopify-utils.js.map +0 -7
  200. package/lib/url-utils.js.map +0 -7
  201. package/loaders/collection-loader-args.js.map +0 -7
  202. package/loaders/collection-loader.js.map +0 -7
  203. package/loaders/collection-products-loader-args.js.map +0 -7
  204. package/loaders/collection-products-loader.js.map +0 -7
  205. package/loaders/contentful-entries-loader.js.map +0 -7
  206. package/loaders/contentful-entry-loader.js.map +0 -7
  207. package/loaders/invoke-loader-action.js.map +0 -7
  208. package/loaders/invoke-loader-server.js.map +0 -7
  209. package/loaders/invoke-loader.js.map +0 -7
  210. package/loaders/judgeme-review-aggregate-loader.js.map +0 -7
  211. package/loaders/judgeme-reviews-loader.js.map +0 -7
  212. package/loaders/levanta-product-loader.js.map +0 -7
  213. package/loaders/loader-error-boundary.js.map +0 -7
  214. package/loaders/loader-keys.js.map +0 -7
  215. package/loaders/loader-schemas.js.map +0 -7
  216. package/loaders/loox-review-aggregate-loader.js.map +0 -7
  217. package/loaders/loox-reviews-loader.js.map +0 -7
  218. package/loaders/metaobject-loader.js.map +0 -7
  219. package/loaders/metaobjects-loader.js.map +0 -7
  220. package/loaders/okendo-review-aggregate-loader.js.map +0 -7
  221. package/loaders/okendo-reviews-loader.js.map +0 -7
  222. package/loaders/prefetch-loaders.js.map +0 -7
  223. package/loaders/preview-placeholder.js.map +0 -7
  224. package/loaders/product-loader-args.js.map +0 -7
  225. package/loaders/product-loader.js.map +0 -7
  226. package/loaders/rebuy-recommendations-loader.js.map +0 -7
  227. package/loaders/reviews-io-review-aggregate-loader.js.map +0 -7
  228. package/loaders/reviews-io-reviews-loader.js.map +0 -7
  229. package/loaders/smile-earning-rules-loader.js.map +0 -7
  230. package/loaders/smile-rewards-loader.js.map +0 -7
  231. package/loaders/smile-vip-tiers-loader.js.map +0 -7
  232. package/loaders/statsig-experiment-loader.js.map +0 -7
  233. package/loaders/yotpo-review-aggregate-loader.js.map +0 -7
  234. package/loaders/yotpo-reviews-loader.js.map +0 -7
  235. package/money.js.map +0 -7
  236. package/next/config.js.map +0 -7
  237. package/next/image-loader.js.map +0 -7
  238. package/next/open-next.js.map +0 -7
  239. package/orders/order-status-schema.js.map +0 -7
  240. package/orders/order-status.js.map +0 -7
  241. package/providers/query-provider.js.map +0 -7
  242. package/providers/replo-provider.js.map +0 -7
  243. package/routing/evaluate.js.map +0 -7
  244. package/routing/geo.js.map +0 -7
  245. package/routing/locale.js.map +0 -7
  246. package/routing/rules.js.map +0 -7
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../cart/utils/cookie-cart-persistence.ts"],
4
- "sourcesContent": ["\"use server\";\n\nimport type {\n AddToCartPayload,\n Cart,\n CartLine,\n CartLinePayload,\n RemoveFromCartPayload,\n UpdateCartLineItemPayload,\n} from \"../cart-types\";\n\nimport { cookies } from \"next/headers\";\n\nimport { getCartCookieOptions } from \"../gateways/cart-constants\";\nimport { recalculateCartCost } from \"./cart-utils\";\n\n/**\n * Line merge function type - returns index of existing line to merge with, or -1 to add as new line\n */\nexport type LineMergeFn = (\n existingLines: CartLine[],\n newLine: CartLinePayload,\n) => number;\n\n/**\n * Options for creating a cart with persistence\n */\ninterface CreateCartOptions {\n cookieName: string;\n lines?: CartLinePayload[];\n lineIdGenerator: () => string;\n cartIdGenerator: () => string;\n reconcile?: (cart: Cart) => Promise<Cart>;\n}\n\n/**\n * Options for adding to cart with persistence\n */\ninterface AddToCartOptions {\n cookieName: string;\n mergeFn: LineMergeFn;\n lineIdGenerator: () => string;\n reconcile?: (cart: Cart) => Promise<Cart>;\n}\n\n/**\n * Save cart to cookie\n */\nexport async function saveCartToCookie(cart: Cart, cookieName: string) {\n const cookieStore = await cookies();\n cookieStore.set(cookieName, JSON.stringify(cart), getCartCookieOptions());\n}\n\n/**\n * Load cart from cookie\n */\nexport async function loadCartFromCookie(\n cookieName: string,\n): Promise<Cart | null> {\n const cookieStore = await cookies();\n const cartCookie = cookieStore.get(cookieName);\n\n if (!cartCookie?.value) {\n return null;\n }\n\n try {\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n return JSON.parse(cartCookie.value) as Cart;\n } catch (error) {\n console.warn(\"Failed to parse cart cookie:\", error);\n await clearCartCookie(cookieName);\n return null;\n }\n}\n\n/**\n * Clear cart cookie\n */\nexport async function clearCartCookie(cookieName: string) {\n const cookieStore = await cookies();\n // NOTE (Max, 2026-08-25): `delete` omits Partitioned, which leaves the\n // partitioned cookie in place \u2014 expire it with the attributes it was set with.\n cookieStore.set(cookieName, \"\", { ...getCartCookieOptions(), maxAge: 0 });\n}\n\n/**\n * Update cart in cookie atomically (load-transform-save pattern)\n */\nexport async function updateCartInCookie(\n cartId: string,\n cookieName: string,\n updater: (cart: Cart) => Cart,\n reconcile?: (cart: Cart) => Promise<Cart>,\n): Promise<Cart | null> {\n const existingCart = await loadCartFromCookie(cookieName);\n\n if (!existingCart || existingCart.id !== cartId) {\n return null;\n }\n\n let updatedCart = recalculateCartCost(updater(existingCart));\n if (reconcile) {\n updatedCart = await reconcile(updatedCart);\n }\n\n await saveCartToCookie(updatedCart, cookieName);\n\n return updatedCart;\n}\n\n/**\n * Create a new cart with persistence\n */\nexport async function createCartWithPersistence(\n options: CreateCartOptions,\n): Promise<Cart | null> {\n const cartId = options.cartIdGenerator();\n\n const cart: Cart = {\n id: cartId,\n lines: [],\n cost: {\n subtotalAmount: 0,\n totalAmount: 0,\n currencyCode: \"USD\",\n },\n checkoutUrl: \"/checkout\",\n };\n\n for (const line of options.lines ?? []) {\n cart.lines.push(\n convertCartLinePayloadToCartLine(line, options.lineIdGenerator),\n );\n }\n\n let cartWithCost = recalculateCartCost(cart);\n if (options.reconcile) {\n cartWithCost = await options.reconcile(cartWithCost);\n }\n await saveCartToCookie(cartWithCost, options.cookieName);\n return cartWithCost;\n}\n\n/**\n * Add lines to cart with configurable line merging strategy\n */\nexport async function addToCartWithPersistence(\n payload: AddToCartPayload,\n options: AddToCartOptions,\n): Promise<Cart | null> {\n return await updateCartInCookie(\n payload.cartId,\n options.cookieName,\n (cart) => {\n for (const linePayload of payload.lines) {\n const existingLineIndex = options.mergeFn(cart.lines, linePayload);\n\n if (existingLineIndex >= 0) {\n cart.lines[existingLineIndex]!.quantity += linePayload.quantity;\n } else {\n const newLine: CartLine = convertCartLinePayloadToCartLine(\n linePayload,\n options.lineIdGenerator,\n );\n cart.lines.push(newLine);\n }\n }\n\n return cart;\n },\n options.reconcile,\n );\n}\n\n/**\n * Update cart line item\n */\nexport async function updateCartLineItemWithPersistence(\n payload: UpdateCartLineItemPayload,\n cookieName: string,\n reconcile?: (cart: Cart) => Promise<Cart>,\n): Promise<Cart | null> {\n return await updateCartInCookie(\n payload.cartId,\n cookieName,\n (cart) => {\n const lineIndex = cart.lines.findIndex(\n (line) => line.id === payload.line.id,\n );\n if (lineIndex < 0) {\n return cart;\n }\n if (payload.line.quantity <= 0) {\n cart.lines.splice(lineIndex, 1);\n return cart;\n }\n const line = cart.lines[lineIndex]!;\n line.quantity = payload.line.quantity;\n if (payload.line.properties) {\n line.attributes = payload.line.properties;\n }\n if (payload.line.merchandise) {\n line.merchandise = payload.line.merchandise;\n }\n return cart;\n },\n reconcile,\n );\n}\n\n/**\n * Remove cart line items\n */\nexport async function removeCartLineItemsWithPersistence(\n payload: RemoveFromCartPayload,\n cookieName: string,\n): Promise<Cart | null> {\n return await updateCartInCookie(payload.cartId, cookieName, (cart) => {\n const removeSet = new Set(payload.lineIds);\n cart.lines = cart.lines.filter((line) => !removeSet.has(line.id));\n return cart;\n });\n}\n\n/**\n * Convert CartLinePayload to CartLine with custom ID generator\n */\nfunction convertCartLinePayloadToCartLine(\n linePayload: CartLinePayload,\n lineIdGenerator: () => string,\n): CartLine {\n // Cookie-persisted carts (Stripe/default) are their own source of truth \u2014\n // there's no server roundtrip to backfill merchandise later. Fall back to a\n // placeholder when the caller didn't pass merchandise so the persisted line\n // is renderable; otherwise an unresolvable line would render as a skeleton\n // forever. Shopify carts hit a different code path and use the skeleton\n // treatment in cart-provider.tsx.\n const fallbackMerchandise = {\n id: linePayload.id,\n product: {\n id: linePayload.merchandise?.product.id ?? linePayload.id,\n title: linePayload.merchandise?.product.title ?? \"Product\",\n },\n title: linePayload.merchandise?.title ?? \"Product Variant\",\n price: linePayload.merchandise?.price ?? 0,\n compareAtPrice: linePayload.merchandise?.compareAtPrice ?? null,\n image: linePayload.merchandise?.image\n ? {\n url: linePayload.merchandise.image.url,\n altText: linePayload.merchandise.image.altText,\n }\n : null,\n selectedOptions: linePayload.merchandise?.selectedOptions ?? [],\n };\n\n return {\n id: lineIdGenerator(),\n quantity: linePayload.quantity,\n merchandise: linePayload.merchandise ?? fallbackMerchandise,\n attributes: linePayload.properties ?? [],\n sellingPlanAllocation: linePayload.sellingPlanId\n ? {\n sellingPlan: {\n id: linePayload.sellingPlanId,\n name: \"Subscription Plan\",\n description: null,\n },\n priceAdjustments: [],\n checkoutChargeAmount: { amount: 0, currencyCode: \"USD\" },\n remainingBalanceChargeAmount: { amount: 0, currencyCode: \"USD\" },\n }\n : null,\n };\n}\n"],
5
- "mappings": "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",
6
- "names": ["line"]
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../cart/utils/variant-to-cart-line.ts"],
4
- "sourcesContent": ["import type {\n CartLine,\n CartLineAttribute,\n CartLineMerchandise,\n SellingPlanAllocation,\n} from \"../cart-types\";\n\nimport { fromMajorUnitsToMinorUnits } from \"schemas/money\";\n\n// TODO: Temporary \u2014 inlined from removed features/product/utils/pricing-utils. Keep until cart uses canonical product/loader types.\nfunction applyPriceAdjustment(\n variantPrice: number,\n adjustment: {\n value: number;\n valueType: \"percentage\" | \"fixedAmount\" | \"price\";\n },\n currencyCode: string,\n): number {\n switch (adjustment.valueType) {\n // NOTE (Max, 2026-08-03): variantPrice is integer minor units, but the\n // loader emits money-typed adjustments as decimal major units (its\n // storefront contract), so the price/fixedAmount arms convert here at the\n // cart boundary. Percentage is unitless and needs no conversion.\n case \"price\":\n return fromMajorUnitsToMinorUnits({\n amount: adjustment.value,\n currencyCode,\n });\n case \"percentage\":\n return Math.max(\n 0,\n Math.round(variantPrice * (1 - adjustment.value / 100)),\n );\n case \"fixedAmount\":\n return Math.max(\n 0,\n variantPrice -\n fromMajorUnitsToMinorUnits({\n amount: adjustment.value,\n currencyCode,\n }),\n );\n }\n}\n\n// === Cart Line Utilities ===\n\n/**\n * Gets a specific attribute value from a cart line.\n *\n * @param cartLine - The cart line to get the attribute from\n * @param key - The attribute key to retrieve\n * @returns The attribute value, or undefined if not found\n */\nexport function getCartLineAttribute(\n cartLine: CartLine,\n key: string,\n): string | number | boolean | undefined {\n return cartLine.attributes.find((attr: CartLineAttribute) => attr.key === key)\n ?.value;\n}\n\n/**\n * Calculates pricing information for a cart line, including discounts and\n * selling plan adjustments.\n *\n * @param cartLine - The cart line to calculate pricing for\n * @returns Object containing compareAtPrice, discountPercentage, hasDiscount, sellingPlanAdjustedPrice, and finalPrice\n */\nexport function getCartLinePricing(cartLine: CartLine) {\n if (!cartLine.merchandise) {\n return {\n compareAtPrice: 0,\n discountPercentage: 0,\n hasDiscount: false,\n sellingPlanAdjustedPrice: 0,\n finalPrice: 0,\n };\n }\n\n const compareAtPrice = cartLine.merchandise.compareAtPrice ?? 0;\n const currentPrice = cartLine.merchandise.price;\n const discountPercentage =\n compareAtPrice > currentPrice\n ? ((compareAtPrice - currentPrice) / compareAtPrice) * 100\n : 0;\n const hasDiscount = compareAtPrice > currentPrice;\n\n let sellingPlanAdjustedPrice = cartLine.merchandise.price;\n if (\n cartLine.sellingPlanAllocation &&\n cartLine.sellingPlanAllocation.priceAdjustments.length > 0 &&\n cartLine.sellingPlanAllocation.priceAdjustments[0]?.price\n ) {\n // Use the price from the first price adjustment, which represents the selling plan price\n sellingPlanAdjustedPrice =\n cartLine.sellingPlanAllocation.priceAdjustments[0]!.price.amount;\n }\n\n return {\n compareAtPrice,\n discountPercentage,\n hasDiscount: hasDiscount,\n sellingPlanAdjustedPrice,\n finalPrice: sellingPlanAdjustedPrice,\n };\n}\n\n/**\n * Extracts bundle information from cart line attributes.\n *\n * @param cartLine - The cart line to get bundle info from\n * @returns Object containing bundle ID, discount, and whether it's a bundle item\n */\nexport function getCartLineBundleInfo(cartLine: CartLine) {\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const bundleId = getCartLineAttribute(cartLine, \"bundleId\") as string;\n const bundleDiscount = Number(\n getCartLineAttribute(cartLine, \"bundleDiscount\") ?? 0,\n );\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const isBundleItem = getCartLineAttribute(\n cartLine,\n \"isBundleItem\",\n ) as boolean;\n\n return {\n bundleId: bundleId ?? null,\n bundleDiscount,\n isBundleItem: isBundleItem ?? false,\n };\n}\n\n/**\n * Extracts special item information from cart line attributes.\n *\n * @param cartLine - The cart line to get special info from\n * @returns Object containing whether it's a special item and any special instructions\n */\nexport function getCartLineSpecialInfo(cartLine: CartLine) {\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const isSpecialItem = getCartLineAttribute(\n cartLine,\n \"isSpecialItem\",\n ) as boolean;\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const specialInstructions = getCartLineAttribute(\n cartLine,\n \"specialInstructions\",\n ) as string;\n\n return {\n isSpecialItem: isSpecialItem ?? false,\n specialInstructions: specialInstructions\n ? specialInstructions.split(\"|\")\n : [],\n };\n}\n\n/**\n * Extracts subscription (selling plan) information from a cart line.\n *\n * @param cartLine - The cart line to get subscription info from\n * @returns Object containing isSubscription, frequency, discount, and sellingPlan\n */\nexport function getCartLineSubscriptionInfo(cartLine: CartLine) {\n if (cartLine.sellingPlanAllocation) {\n return {\n isSubscription: true,\n frequency: cartLine.sellingPlanAllocation.sellingPlan.name,\n discount: 0, // Shopify calculates this from price adjustments when available\n sellingPlan: cartLine.sellingPlanAllocation.sellingPlan,\n };\n }\n\n // No subscription if no sellingPlanAllocation\n return {\n isSubscription: false,\n frequency: null,\n discount: 0,\n sellingPlan: null,\n };\n}\n\n/**\n * Creates an optimistic selling plan allocation using product data.\n * This matches the exact structure that Shopify returns so the UI doesn't need conditional logic.\n */\nexport function createOptimisticSellingPlanAllocation({\n merchandise,\n sellingPlanId,\n currencyCode = \"USD\",\n}: {\n merchandise: CartLineMerchandise;\n sellingPlanId: string | null;\n currencyCode?: string;\n}): SellingPlanAllocation | null {\n const { product, price: variantPrice } = merchandise;\n\n if (!sellingPlanId || !product.sellingPlanGroups) {\n return null;\n }\n\n // Find the selling plan in the product's selling plan groups\n for (const group of product.sellingPlanGroups) {\n const sellingPlan = group.sellingPlans.find(\n (sp) => sp.id === sellingPlanId,\n );\n if (sellingPlan) {\n return {\n sellingPlan: {\n id: sellingPlan.id,\n name: sellingPlan.name,\n description: sellingPlan.description,\n },\n priceAdjustments: sellingPlan.priceAdjustments.map((adj) => {\n const adjustedPrice =\n variantPrice !== undefined\n ? applyPriceAdjustment(variantPrice, adj, currencyCode)\n : 0;\n\n return {\n compareAtPrice: {\n amount: variantPrice ?? 0,\n currencyCode,\n },\n perDeliveryPrice: {\n amount: adjustedPrice,\n currencyCode,\n },\n price: {\n amount: adjustedPrice,\n currencyCode,\n },\n unitPrice: null,\n };\n }),\n checkoutChargeAmount: {\n amount: 0,\n currencyCode,\n },\n remainingBalanceChargeAmount: {\n amount: 0,\n currencyCode,\n },\n };\n }\n }\n\n return null;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/consent-action.ts"],
4
- "sourcesContent": ["import type { ConsentAction, ConsentCategory } from \"schemas/generated/consent\";\nimport type { ConsentCategories } from \"./cookie\";\n\n/**\n * Pure mapping from a consent decision (previous state vs the resulting state)\n * to the legal-record `action` written to the proof-of-consent audit log.\n *\n * Only non-necessary categories matter (`necessary` is always granted). The\n * mapping is:\n * - first decision (`decidedAt === null`), some category granted -> \"accept\"\n * - first decision, nothing granted -> \"reject\"\n * - later decision that leaves nothing granted -> \"withdraw\"\n * - any other later change -> \"update\"\n *\n * `withdraw` is reserved for going from some consent to none (the GDPR\n * withdrawal right); a partial reduction that still leaves something granted is\n * an `update`.\n */\nconst NON_NECESSARY_CATEGORIES: Exclude<ConsentCategory, \"necessary\">[] = [\n \"analytics\",\n \"marketing\",\n \"preferences\",\n \"sale_of_data\",\n];\n\nexport function deriveConsentAction({\n previous,\n next,\n}: {\n previous: { decidedAt: number | null; categories: ConsentCategories };\n next: ConsentCategories;\n}): ConsentAction {\n const anyGranted = NON_NECESSARY_CATEGORIES.some(\n (category) => next[category],\n );\n\n if (previous.decidedAt === null) {\n return anyGranted ? \"accept\" : \"reject\";\n }\n if (!anyGranted) {\n return \"withdraw\";\n }\n return \"update\";\n}\n"],
5
- "mappings": "AAkBA,MAAM,2BAAoE;AAAA,EACxE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGkB;AAChB,QAAM,aAAa,yBAAyB;AAAA,IAC1C,CAAC,aAAa,KAAK,QAAQ;AAAA,EAC7B;AAEA,MAAI,SAAS,cAAc,MAAM;AAC/B,WAAO,aAAa,WAAW;AAAA,EACjC;AACA,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/consent-actions.ts"],
4
- "sourcesContent": ["\"use server\";\n\nimport type {\n ConsentAction,\n ConsentMode,\n ConsentRecordRequest,\n} from \"schemas/generated/consent\";\nimport type { ConsentCategories } from \"./cookie\";\n\nimport { headers } from \"next/headers\";\n\nimport { getEnv } from \"../env\";\n\n/**\n * Records a proof-of-consent decision by forwarding it to analytics-fire.\n *\n * Runs server-side \u2014 exactly like the cart's actions \u2014 so the browser never needs\n * the project id or the analytics-fire URL: both are read from the worker env\n * here via `getEnv()`. The visitor's user-agent / referrer / coarse geo come from\n * the incoming request headers and are forwarded so analytics-fire derives them\n * the same way it does for the legacy pixel.\n *\n * Best-effort and self-disabling: no-ops when analytics-fire isn't configured\n * (e.g. lean local dev) and never throws into the page.\n */\nexport async function recordConsentAction(decision: {\n consentId: string;\n action: ConsentAction;\n /** Full resulting consent state (all categories), not a diff. */\n categories: ConsentCategories;\n consentMode: ConsentMode;\n decidedAt: number;\n /**\n * Site-owned policy version declared by the consent caller (via\n * `useConsent({ version })`); the site bumps it when the banner copy or\n * category set changes.\n */\n policyVersion: number;\n}) {\n // Best-effort and total: never reject (the caller fire-and-forgets it), even if\n // the Cloudflare env / request context is unavailable.\n try {\n const { PROJECT_ID, ANALYTICS_FIRE_API_URL } = await getEnv();\n if (!ANALYTICS_FIRE_API_URL) {\n return;\n }\n\n const incoming = await headers();\n const body: ConsentRecordRequest = {\n consentId: decision.consentId,\n projectId: PROJECT_ID,\n decidedAt: decision.decidedAt,\n action: decision.action,\n categories: decision.categories,\n consentMode: decision.consentMode,\n policyVersion: decision.policyVersion,\n };\n\n await fetch(`${ANALYTICS_FIRE_API_URL}/api/v1/consent`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n \"user-agent\": incoming.get(\"user-agent\") ?? \"\",\n referer: incoming.get(\"referer\") ?? \"\",\n \"cf-ipcountry\": incoming.get(\"cf-ipcountry\") ?? \"\",\n },\n body: JSON.stringify(body),\n });\n } catch {\n // Consent logging must never surface an error in the page.\n }\n}\n"],
5
- "mappings": ";AASA,SAAS,eAAe;AAExB,SAAS,cAAc;AAcvB,eAAsB,oBAAoB,UAavC;AAGD,MAAI;AACF,UAAM,EAAE,YAAY,uBAAuB,IAAI,MAAM,OAAO;AAC5D,QAAI,CAAC,wBAAwB;AAC3B;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,QAAQ;AAC/B,UAAM,OAA6B;AAAA,MACjC,WAAW,SAAS;AAAA,MACpB,WAAW;AAAA,MACX,WAAW,SAAS;AAAA,MACpB,QAAQ,SAAS;AAAA,MACjB,YAAY,SAAS;AAAA,MACrB,aAAa,SAAS;AAAA,MACtB,eAAe,SAAS;AAAA,IAC1B;AAEA,UAAM,MAAM,GAAG,sBAAsB,mBAAmB;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,cAAc,SAAS,IAAI,YAAY,KAAK;AAAA,QAC5C,SAAS,SAAS,IAAI,SAAS,KAAK;AAAA,QACpC,gBAAgB,SAAS,IAAI,cAAc,KAAK;AAAA,MAClD;AAAA,MACA,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH,QAAQ;AAAA,EAER;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/consent-platform.ts"],
4
- "sourcesContent": ["import type { ConsentCategories } from \"./cookie\";\nimport type {\n ConsentCategory,\n ReploScriptEntry,\n ScriptTagDescriptor,\n} from \"./types\";\n\nimport { z } from \"zod\";\n\nimport { DENIED_CATEGORIES } from \"./cookie\";\n\n// A connected consent platform owns consent instead of Replo's banner: managed\n// tags are blocked until it activates them, and Replo stores no consent itself.\n\nexport interface ConsentPlatformConfig {\n id: string;\n loader: ScriptTagDescriptor[];\n blockingAttribute: {\n name: string;\n values: Partial<Record<ConsentCategory, string>>;\n };\n /** Cookiebot preset only: enables consent read-back and runScripts re-scan. */\n isCookiebot: boolean;\n}\n\n/** Cookiebot's fixed vocabulary. It has no sale-of-data category, so marketing covers it; `necessary` is never blocked. The `satisfies` makes a future ConsentCategory a compile error instead of a script that silently runs before consent. */\nconst COOKIEBOT_CATEGORY_BY_CONSENT: Partial<Record<ConsentCategory, string>> =\n {\n analytics: \"statistics\",\n marketing: \"marketing\",\n preferences: \"preferences\",\n sale_of_data: \"marketing\",\n } satisfies Record<Exclude<ConsentCategory, \"necessary\">, string>;\n\nfunction cookiebotPreset(identifier: string): ConsentPlatformConfig {\n return {\n id: \"cookiebot\",\n loader: [\n {\n kind: \"external\",\n src: \"https://consent.cookiebot.com/uc.js\",\n attributes: {\n id: \"Cookiebot\",\n \"data-cbid\": identifier,\n // No data-blockingmode: uc.js only ever checks for \"auto\"; manual is\n // its coded default, so any other value (or absence) is identical.\n },\n },\n ],\n blockingAttribute: {\n name: \"data-cookieconsent\",\n values: COOKIEBOT_CATEGORY_BY_CONSENT,\n },\n isCookiebot: true,\n };\n}\n\n/** Whether this entry connects a consent platform (vs a tracker it would gate). */\nexport function isConsentPlatformEntry(entry: ReploScriptEntry): boolean {\n return entry.type === \"Cookiebot\" || entry.type === \"consentPlatform\";\n}\n\n/** The connected platform's config, or null when Replo's banner owns consent. */\nexport function findConsentPlatform(\n scripts: ReploScriptEntry[],\n): ConsentPlatformConfig | null {\n for (const entry of scripts) {\n if (entry.type === \"Cookiebot\") {\n return cookiebotPreset(entry.identifier);\n }\n if (entry.type === \"consentPlatform\") {\n return {\n id: entry.id,\n loader: [\n { kind: \"external\", src: entry.src, attributes: entry.attributes },\n ],\n blockingAttribute: entry.blockingAttribute,\n isCookiebot: false,\n };\n }\n }\n return null;\n}\n\n/** Blocks a gated script; `text/plain` is unconditional so an unmapped category fails closed. */\nexport function getBlockingAttributes(\n config: ConsentPlatformConfig,\n requiredConsent: ConsentCategory[],\n): Record<string, string> {\n const gated = requiredConsent.filter((category) => category !== \"necessary\");\n if (gated.length === 0) {\n return {};\n }\n const tokens = [\n ...new Set(\n gated.flatMap(\n (category) => config.blockingAttribute.values[category] ?? [],\n ),\n ),\n ];\n return {\n type: \"text/plain\",\n ...(tokens.length > 0\n ? { [config.blockingAttribute.name]: tokens.join(\",\") }\n : {}),\n };\n}\n\n/** The bits of Cookiebot's `window.Cookiebot` global we read. */\ninterface CookiebotGlobal {\n consent?: Partial<Record<CookiebotCategory, boolean>>;\n hasResponse?: boolean;\n runScripts?: () => void;\n}\n\ntype CookiebotCategory =\n | \"necessary\"\n | \"preferences\"\n | \"statistics\"\n | \"marketing\";\n\ndeclare global {\n interface Window {\n Cookiebot?: CookiebotGlobal;\n }\n}\n\n/** `CookiebotOnConsentReady` also fires on load for an already-stored answer, catching returning visitors. */\nconst COOKIEBOT_EVENTS = [\n \"CookiebotOnConsentReady\",\n \"CookiebotOnAccept\",\n \"CookiebotOnDecline\",\n] as const;\n\ntype OptionalConsentCategory = Exclude<ConsentCategory, \"necessary\">;\n\nexport const OPTIONAL_CONSENT_CATEGORIES = [\n \"analytics\",\n \"marketing\",\n \"preferences\",\n \"sale_of_data\",\n] as const satisfies readonly OptionalConsentCategory[];\n\nconst genericConsentCategorySchema = z.enum(OPTIONAL_CONSENT_CATEGORIES);\nconst GENERIC_CONSENT_GRANTED_EVENT = \"reploConsentCategoryGranted\";\n\nlet delegatedPlatform: ConsentPlatformConfig | null = null;\nlet listeningToCookiebot = false;\nlet listeningToGenericPlatform = false;\nlet categories: ConsentCategories = DENIED_CATEGORIES;\nlet decidedAt: number | null = null;\nconst listeners = new Set<() => void>();\nconst reloadCurrentPage = () => window.location.reload();\nlet reloadPage = reloadCurrentPage;\n\n/** The delegated platform, or null when Replo's own banner owns consent. */\nexport function findDelegatedPlatform(): ConsentPlatformConfig | null {\n return delegatedPlatform;\n}\n\n/** The platform's answer as Replo consent. */\nexport function getDelegatedConsent(): {\n categories: ConsentCategories;\n decidedAt: number | null;\n} {\n return { categories, decidedAt };\n}\n\ninterface CookiebotConsentReadResult {\n hasChanged: boolean;\n hasDowngrade: boolean;\n}\n\n/** Reads Cookiebot's answer into module state. */\nfunction readCookiebotConsent(): CookiebotConsentReadResult {\n const cookiebot =\n typeof window === \"undefined\" ? undefined : window.Cookiebot;\n if (!cookiebot?.consent || cookiebot.hasResponse !== true) {\n return { hasChanged: false, hasDowngrade: false };\n }\n const consent = cookiebot.consent;\n const next: ConsentCategories = {\n necessary: true,\n analytics: consent.statistics === true,\n marketing: consent.marketing === true,\n preferences: consent.preferences === true,\n sale_of_data: consent.marketing === true,\n };\n // Consumers memoize on the categories object, so only replace it when the answer moved.\n const unchanged =\n next.analytics === categories.analytics &&\n next.marketing === categories.marketing &&\n next.preferences === categories.preferences &&\n next.sale_of_data === categories.sale_of_data;\n if (decidedAt !== null && unchanged) {\n return { hasChanged: false, hasDowngrade: false };\n }\n const hasDowngrade =\n decidedAt !== null &&\n OPTIONAL_CONSENT_CATEGORIES.some((category) => {\n return categories[category] && !next[category];\n });\n categories = next;\n decidedAt = Date.now();\n return { hasChanged: true, hasDowngrade };\n}\n\nfunction notifyConsentListeners() {\n for (const listener of listeners) {\n listener();\n }\n}\n\nfunction handleCookiebotEvent() {\n const result = readCookiebotConsent();\n if (!result.hasChanged) {\n return;\n }\n notifyConsentListeners();\n if (result.hasDowngrade) {\n reloadPage();\n }\n}\n\nfunction handleGenericConsentActivation(event: Event) {\n if (!(event instanceof CustomEvent)) {\n return;\n }\n const result = genericConsentCategorySchema.safeParse(event.detail);\n if (!result.success || categories[result.data]) {\n return;\n }\n categories = { ...categories, [result.data]: true };\n decidedAt = Date.now();\n notifyConsentListeners();\n}\n\n/** Inline marker a generic CMP executes when it releases one consent category. */\nexport function buildGrantMarker(\n category: OptionalConsentCategory,\n): ScriptTagDescriptor {\n return {\n kind: \"inline\",\n body: `document.dispatchEvent(new CustomEvent(${JSON.stringify(GENERIC_CONSENT_GRANTED_EVENT)}, { detail: ${JSON.stringify(category)} }));`,\n };\n}\n\n/** Latches the platform as the consent backend; un-delegating would need a reload, as native revocation already does. */\nexport function enableConsentDelegation(config: ConsentPlatformConfig) {\n // Client-only: script gating is a DOM concern, and latching module state\n // during SSR would leak the config across requests in a Worker isolate.\n if (typeof window === \"undefined\" || delegatedPlatform) {\n return;\n }\n delegatedPlatform = config;\n if (config.isCookiebot && !listeningToCookiebot) {\n listeningToCookiebot = true;\n for (const event of COOKIEBOT_EVENTS) {\n window.addEventListener(event, handleCookiebotEvent);\n }\n }\n if (!config.isCookiebot && !listeningToGenericPlatform) {\n listeningToGenericPlatform = true;\n document.addEventListener(\n GENERIC_CONSENT_GRANTED_EVENT,\n handleGenericConsentActivation,\n );\n }\n if (config.isCookiebot) {\n // No notify: this runs during render, where notifying re-renders other components.\n readCookiebotConsent();\n }\n}\n\n/** Cookiebot-only re-scan; other platforms rely on the loader injecting after every tag. */\nexport function activateBlockedScripts() {\n if (!delegatedPlatform?.isCookiebot) {\n return;\n }\n window.Cookiebot?.runScripts?.();\n}\n\n/** Subscribe to delegated-consent changes. Returns an unsubscribe function. */\nexport function subscribeToConsentDelegation(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/** Replaces navigation in tests so withdrawal behavior can be asserted. */\nexport function setConsentPlatformReloadForTests(reload: () => void) {\n reloadPage = reload;\n}\n\n/** Test-only reset; leaves subscriptions alone so the store's own stays attached. */\nexport function resetConsentPlatformForTests() {\n if (typeof window !== \"undefined\" && listeningToCookiebot) {\n for (const event of COOKIEBOT_EVENTS) {\n window.removeEventListener(event, handleCookiebotEvent);\n }\n }\n if (typeof document !== \"undefined\" && listeningToGenericPlatform) {\n document.removeEventListener(\n GENERIC_CONSENT_GRANTED_EVENT,\n handleGenericConsentActivation,\n );\n }\n delegatedPlatform = null;\n listeningToCookiebot = false;\n listeningToGenericPlatform = false;\n categories = DENIED_CATEGORIES;\n decidedAt = null;\n reloadPage = reloadCurrentPage;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/consent-store.ts"],
4
- "sourcesContent": ["\"use client\";\n\nimport type { ConsentCategory, ConsentMode } from \"schemas/generated/consent\";\nimport type { ConsentCategories } from \"./cookie\";\n\nimport { useSyncExternalStore } from \"react\";\n\nimport { deriveConsentAction } from \"./consent-action\";\nimport { recordConsentAction } from \"./consent-actions\";\nimport {\n findDelegatedPlatform,\n getDelegatedConsent,\n OPTIONAL_CONSENT_CATEGORIES,\n subscribeToConsentDelegation,\n} from \"./consent-platform\";\nimport {\n CONSENT_COOKIE_MAX_AGE_SECONDS,\n CONSENT_COOKIE_NAME,\n DENIED_CATEGORIES,\n GRANTED_CATEGORIES,\n parseConsentCookie,\n serializeConsentCookieValue,\n} from \"./cookie\";\n\n/**\n * Framework-agnostic, cookie-backed consent store. Lives as a single module\n * singleton so `ReploScripts`, the site's consent banner, and `AnalyticsProvider`\n * all share the same state regardless of where they sit in the React tree \u2014 no\n * provider-ancestor relationship is required.\n *\n * Consent-dependent work (script injection, banner visibility) happens on the\n * client (effects / post-mount), so there is no SSR/hydration mismatch. The\n * client reads the (non-httpOnly) consent cookie directly, so no server seed is\n * needed. The whole-site `mode` comes from the `data-replo-consent-mode`\n * attribute on `<html>`.\n */\n\nexport interface ConsentState {\n /** Which platform owns consent. Absent means Replo's native banner; \"external\" is a generic `consentPlatform` entry. */\n cmp?: \"replo\" | \"cookiebot\" | \"external\";\n mode: ConsentMode;\n categories: ConsentCategories;\n /** Epoch ms when the visitor last made a choice, or null if undecided. */\n decidedAt: number | null;\n /**\n * Persistent pseudonymous per-browser id linking a visitor's consent history.\n * Absent until the first `commit()` (and on legacy pre-id cookies, until the\n * next interaction lazily backfills it).\n */\n consentId?: string;\n}\n\n// A stable reference for SSR / first hydration render. Consent-dependent DOM is\n// only produced on the client, so this constant never causes a mismatch.\nconst SERVER_SNAPSHOT: ConsentState = Object.freeze({\n cmp: \"replo\",\n mode: \"off\",\n categories: DENIED_CATEGORIES,\n decidedAt: null,\n});\n\nconst listeners = new Set<() => void>();\nlet snapshot: ConsentState | null = null;\n\nfunction isConsentMode(value: string | null | undefined): value is ConsentMode {\n return value === \"off\" || value === \"simple\" || value === \"per-category\";\n}\n\nfunction resolveMode(): ConsentMode {\n if (typeof window === \"undefined\") {\n return \"off\";\n }\n const attribute = document.documentElement.dataset.reploConsentMode;\n return isConsentMode(attribute) ? attribute : \"off\";\n}\n\n// Used when the consent caller doesn't declare a policy version via\n// `useConsent({ version })` or the store method options.\nconst DEFAULT_CONSENT_POLICY_VERSION = 1;\n\nfunction computeSnapshot(): ConsentState {\n if (typeof window === \"undefined\") {\n return SERVER_SNAPSHOT;\n }\n\n const mode = resolveMode();\n\n // Delegated sites turn the native banner off, so `mode` alone would allow everything.\n const platform = findDelegatedPlatform();\n if (platform) {\n return {\n cmp: platform.isCookiebot ? \"cookiebot\" : \"external\",\n mode,\n ...getDelegatedConsent(),\n };\n }\n\n const fromCookie = parseConsentCookie(document.cookie);\n if (fromCookie) {\n return {\n cmp: \"replo\",\n mode,\n categories: {\n necessary: true,\n analytics: fromCookie.categories.analytics,\n marketing: fromCookie.categories.marketing,\n preferences: fromCookie.categories.preferences,\n sale_of_data: fromCookie.categories.sale_of_data,\n },\n decidedAt: fromCookie.decidedAt,\n consentId: fromCookie.consentId,\n };\n }\n\n return { cmp: \"replo\", mode, categories: DENIED_CATEGORIES, decidedAt: null };\n}\n\nfunction writeCookie(\n categories: ConsentCategories,\n decidedAt: number,\n consentId: string,\n) {\n if (typeof document === \"undefined\") {\n return;\n }\n const value = serializeConsentCookieValue({\n categories,\n decidedAt,\n consentId,\n });\n document.cookie = `${CONSENT_COOKIE_NAME}=${value}; path=/; max-age=${CONSENT_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;\n}\n\nfunction notify() {\n for (const listener of listeners) {\n listener();\n }\n}\n\nfunction commit({\n categories,\n policyVersion = DEFAULT_CONSENT_POLICY_VERSION,\n}: {\n categories: ConsentCategories;\n policyVersion?: number;\n}) {\n const previous = consentStore.getSnapshot();\n // The platform owns the decision, so a native accept would write a cookie nothing reads.\n if (previous.cmp === \"cookiebot\" || previous.cmp === \"external\") {\n return;\n }\n const decidedAt = Date.now();\n // Reuse the visitor's existing id, or mint one on the first decision (also\n // lazily backfills legacy pre-id cookies). `crypto.randomUUID` is available in\n // every browser we target and on the server (where commit() never runs).\n const consentId = previous.consentId ?? crypto.randomUUID();\n const resultingCategories: ConsentCategories = {\n ...categories,\n necessary: true,\n };\n writeCookie(categories, decidedAt, consentId);\n snapshot = {\n cmp: previous.cmp,\n mode: previous.mode,\n categories: resultingCategories,\n decidedAt,\n consentId,\n };\n notify();\n\n // Revocation cannot unload an already-loaded vendor. If the visitor had\n // previously decided and is now turning a granted category off, reload so the\n // page renders without those scripts. Deferred until after the audit record is\n // sent so the reload doesn't cancel the in-flight server action.\n const wasDowngrade =\n previous.decidedAt !== null &&\n OPTIONAL_CONSENT_CATEGORIES.some((category) => {\n return previous.categories[category] && !categories[category];\n });\n const reloadIfDowngraded = () => {\n if (wasDowngrade && typeof window !== \"undefined\") {\n window.location.reload();\n }\n };\n\n // Proof-of-consent audit record (ungated by consent, but only on managed\n // sites). On `mode === \"off\"` there is no banner/consent UX, so a\n // `setTrackingConsent()` call from a third-party script would only produce\n // meaningless `consent_mode: \"off\"` noise \u2014 skip it. Egress runs through a\n // server action (like the cart) so the client never needs the project id or\n // analytics-fire URL.\n if (previous.mode !== \"off\") {\n void recordConsentAction({\n consentId,\n action: deriveConsentAction({ previous, next: resultingCategories }),\n categories: resultingCategories,\n consentMode: previous.mode,\n decidedAt,\n policyVersion,\n }).finally(reloadIfDowngraded);\n } else {\n reloadIfDowngraded();\n }\n}\n\nexport const consentStore = {\n getSnapshot(): ConsentState {\n // Delegation is published mid-render, so a snapshot read earlier can predate it.\n if (\n !snapshot ||\n (findDelegatedPlatform() !== null &&\n (snapshot.cmp ?? \"replo\") === \"replo\")\n ) {\n snapshot = computeSnapshot();\n }\n return snapshot;\n },\n getServerSnapshot(): ConsentState {\n return SERVER_SNAPSHOT;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n /** Grant every consent category. */\n accept(options: { policyVersion?: number } = {}) {\n commit({\n categories: GRANTED_CATEGORIES,\n policyVersion: options.policyVersion,\n });\n },\n /** Deny every non-necessary category. */\n reject(options: { policyVersion?: number } = {}) {\n commit({\n categories: DENIED_CATEGORIES,\n policyVersion: options.policyVersion,\n });\n },\n /** Set a subset of categories, leaving the rest at their current value. */\n updateCategories({\n next,\n policyVersion,\n }: {\n next: Partial<ConsentCategories>;\n policyVersion?: number;\n }) {\n const current = consentStore.getSnapshot().categories;\n commit({\n categories: { ...current, ...next, necessary: true },\n policyVersion,\n });\n },\n /** Test-only: reset cached state so the next read recomputes. */\n reset() {\n snapshot = null;\n },\n};\n\nexport interface UseConsentOptions {\n /**\n * Site-owned consent policy version, stamped onto each proof-of-consent\n * record so a decision can be tied to the exact banner the visitor saw.\n * Bump it whenever the banner copy or category set changes. Defaults to 1.\n */\n version?: number;\n}\n\nexport interface UseConsentResult extends ConsentState {\n accept(): void;\n reject(): void;\n updateCategories(next: Partial<ConsentCategories>): void;\n}\n\nexport function useConsent(options: UseConsentOptions = {}): UseConsentResult {\n const policyVersion = options.version;\n const state = useSyncExternalStore(\n consentStore.subscribe,\n consentStore.getSnapshot,\n consentStore.getServerSnapshot,\n );\n return {\n ...state,\n accept: () => consentStore.accept({ policyVersion }),\n reject: () => consentStore.reject({ policyVersion }),\n updateCategories: (next) => {\n consentStore.updateCategories({ next, policyVersion });\n },\n };\n}\n\n/**\n * Whether a script requiring `requiredConsent` may load now. `mode === \"off\"`\n * allows everything only under Replo's banner; a delegated CMP answers instead.\n */\nexport function isConsentAllowed({\n state,\n requiredConsent,\n}: {\n state: ConsentState;\n requiredConsent: ConsentCategory[];\n}): boolean {\n if (\n state.cmp !== \"cookiebot\" &&\n state.cmp !== \"external\" &&\n state.mode === \"off\"\n ) {\n return true;\n }\n if (requiredConsent.length === 0) {\n return true;\n }\n return requiredConsent.every((category) => state.categories[category]);\n}\n\n// The platform answers asynchronously; invalidate so every consent gate re-evaluates.\nsubscribeToConsentDelegation(() => {\n snapshot = null;\n notify();\n});\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/cookie.ts"],
4
- "sourcesContent": ["import type { ConsentCategory } from \"schemas/generated/consent\";\n\nimport { parseJsonOrNull } from \"replo-utils/lib/json\";\nimport { z } from \"zod\";\n\n/**\n * Pure (non-React, framework-agnostic) consent cookie helpers used by the client\n * store (`consent-store.ts`).\n */\n\nexport const CONSENT_COOKIE_NAME = \"replo_consent\";\nexport const CONSENT_COOKIE_VERSION = 1;\nexport const CONSENT_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 180; // 180 days\n\n// Validates the untrusted JSON cookie payload at the parse boundary. A stale\n// version (or any shape mismatch) fails `safeParse`, so the caller treats the\n// visitor as undecided and re-prompts.\n//\n// `consentId` is OPTIONAL on purpose: it was added after v1 shipped, and we must\n// NOT bump `CONSENT_COOKIE_VERSION` to introduce it. Bumping would fail this\n// `safeParse` for every existing cookie, re-showing the banner and dropping the\n// visitor's prior decision. Optional keeps v1 cookies valid; `commit()` lazily\n// backfills the id on the next interaction.\nconst persistedCookieSchema = z.object({\n version: z.literal(CONSENT_COOKIE_VERSION),\n decidedAt: z.number().optional(),\n consentId: z.string().optional(),\n categories: z.object({\n analytics: z.boolean(),\n marketing: z.boolean(),\n preferences: z.boolean(),\n sale_of_data: z.boolean(),\n }),\n});\n\nexport type ConsentCategories = Record<ConsentCategory, boolean>;\n\n/** Persisted cookie payload. `necessary` is always granted and never stored. */\nexport interface ConsentCookiePayload {\n version: number;\n decidedAt: number;\n /** Persistent pseudonymous per-browser id; absent on pre-id (legacy) cookies. */\n consentId?: string;\n categories: {\n analytics: boolean;\n marketing: boolean;\n preferences: boolean;\n sale_of_data: boolean;\n };\n}\n\nexport const DENIED_CATEGORIES: ConsentCategories = {\n necessary: true,\n analytics: false,\n marketing: false,\n preferences: false,\n sale_of_data: false,\n};\n\nexport const GRANTED_CATEGORIES: ConsentCategories = {\n necessary: true,\n analytics: true,\n marketing: true,\n preferences: true,\n sale_of_data: true,\n};\n\n/**\n * Parse a raw `document.cookie` (or a single cookie value) into a payload.\n * Returns null for missing, malformed, or stale-version cookies so callers\n * treat the visitor as undecided and re-prompt.\n */\nexport function parseConsentCookie(\n rawCookie: string | undefined,\n): ConsentCookiePayload | null {\n if (!rawCookie) {\n return null;\n }\n const match = rawCookie\n .split(\"; \")\n .find((entry) => entry.startsWith(`${CONSENT_COOKIE_NAME}=`));\n const encoded = match\n ? match.slice(CONSENT_COOKIE_NAME.length + 1)\n : rawCookie;\n\n // A tampered/corrupted cookie (bad percent-encoding) must not throw \u2014 that\n // would crash the client snapshot or the server seed read. Treat as undecided.\n let decoded: string;\n try {\n decoded = decodeURIComponent(encoded);\n } catch {\n return null;\n }\n\n const result = persistedCookieSchema.safeParse(parseJsonOrNull(decoded));\n if (!result.success) {\n return null;\n }\n const { decidedAt, categories, consentId } = result.data;\n\n return {\n version: CONSENT_COOKIE_VERSION,\n decidedAt: decidedAt ?? Date.now(),\n consentId,\n categories,\n };\n}\n\n/** Serialize categories into the JSON-encoded cookie value (not the full Set-Cookie line). */\nexport function serializeConsentCookieValue({\n categories,\n decidedAt,\n consentId,\n}: {\n categories: ConsentCategories;\n decidedAt: number;\n consentId?: string;\n}): string {\n const payload: ConsentCookiePayload = {\n version: CONSENT_COOKIE_VERSION,\n decidedAt,\n consentId,\n categories: {\n analytics: categories.analytics,\n marketing: categories.marketing,\n preferences: categories.preferences,\n sale_of_data: categories.sale_of_data,\n },\n };\n return encodeURIComponent(JSON.stringify(payload));\n}\n"],
5
- "mappings": "AAEA,SAAS,uBAAuB;AAChC,SAAS,SAAS;AAOX,MAAM,sBAAsB;AAC5B,MAAM,yBAAyB;AAC/B,MAAM,iCAAiC,KAAK,KAAK,KAAK;AAW7D,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,SAAS,EAAE,QAAQ,sBAAsB;AAAA,EACzC,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,YAAY,EAAE,OAAO;AAAA,IACnB,WAAW,EAAE,QAAQ;AAAA,IACrB,WAAW,EAAE,QAAQ;AAAA,IACrB,aAAa,EAAE,QAAQ;AAAA,IACvB,cAAc,EAAE,QAAQ;AAAA,EAC1B,CAAC;AACH,CAAC;AAkBM,MAAM,oBAAuC;AAAA,EAClD,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAChB;AAEO,MAAM,qBAAwC;AAAA,EACnD,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX,aAAa;AAAA,EACb,cAAc;AAChB;AAOO,SAAS,mBACd,WAC6B;AAC7B,MAAI,CAAC,WAAW;AACd,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,UACX,MAAM,IAAI,EACV,KAAK,CAAC,UAAU,MAAM,WAAW,GAAG,mBAAmB,GAAG,CAAC;AAC9D,QAAM,UAAU,QACZ,MAAM,MAAM,oBAAoB,SAAS,CAAC,IAC1C;AAIJ,MAAI;AACJ,MAAI;AACF,cAAU,mBAAmB,OAAO;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,sBAAsB,UAAU,gBAAgB,OAAO,CAAC;AACvE,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO;AAAA,EACT;AACA,QAAM,EAAE,WAAW,YAAY,UAAU,IAAI,OAAO;AAEpD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,WAAW,aAAa,KAAK,IAAI;AAAA,IACjC;AAAA,IACA;AAAA,EACF;AACF;AAGO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AACF,GAIW;AACT,QAAM,UAAgC;AAAA,IACpC,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,YAAY;AAAA,MACV,WAAW,WAAW;AAAA,MACtB,WAAW,WAAW;AAAA,MACtB,aAAa,WAAW;AAAA,MACxB,cAAc,WAAW;AAAA,IAC3B;AAAA,EACF;AACA,SAAO,mBAAmB,KAAK,UAAU,OAAO,CAAC;AACnD;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/inject-script-descriptors.ts"],
4
- "sourcesContent": ["import type { ScriptTagDescriptor } from \"schemas/generated/consent\";\n\n/**\n * Creates and appends the script nodes for `descriptors`, skipping node ids\n * already in the document, and returns only the nodes created by this call.\n * Lives outside replo-scripts.tsx so the component file only exports components\n * (HMR) and this stays off the published public surface.\n */\nexport function injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes,\n}: {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Applied last, so a consent platform's `text/plain` beats a descriptor's own type. */\n extraAttributes?: Record<string, string>;\n}): HTMLScriptElement[] {\n const createdNodes: HTMLScriptElement[] = [];\n descriptors.forEach((descriptor, index) => {\n const nodeId = `${baseId}#${index}`;\n if (document.querySelector(`script[data-replo-script-id=\"${nodeId}\"]`)) {\n return;\n }\n const scriptElement = document.createElement(\"script\");\n scriptElement.dataset.reploScriptId = nodeId;\n if (descriptor.kind === \"external\") {\n scriptElement.src = descriptor.src;\n scriptElement.async = true;\n for (const [attributeName, attributeValue] of Object.entries(\n descriptor.attributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n } else if (descriptor.module && extraAttributes?.type === \"text/plain\") {\n // A consent platform restores blocked tags as CLASSIC scripts, which is a\n // SyntaxError for module source. The blocked body is a classic shim that\n // re-creates the real module tag when the platform activates it.\n scriptElement.textContent = buildModuleShim(descriptor.body);\n } else {\n // Module snippets (top-level await, import()) are a SyntaxError as\n // classic scripts, so the type must survive injection.\n if (descriptor.module) {\n scriptElement.type = \"module\";\n }\n scriptElement.textContent = descriptor.body;\n }\n for (const [attributeName, attributeValue] of Object.entries(\n extraAttributes ?? {},\n )) {\n scriptElement.setAttribute(attributeName, attributeValue);\n }\n document.head.append(scriptElement);\n createdNodes.push(scriptElement);\n });\n return createdNodes;\n}\n\nfunction buildModuleShim(body: string): string {\n return [\n 'var moduleScript = document.createElement(\"script\");',\n 'moduleScript.type = \"module\";',\n `moduleScript.textContent = ${JSON.stringify(body)};`,\n \"document.head.append(moduleScript);\",\n ].join(\"\\n\");\n}\n"],
5
- "mappings": "AAQO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAKwB;AACtB,QAAM,eAAoC,CAAC;AAC3C,cAAY,QAAQ,CAAC,YAAY,UAAU;AACzC,UAAM,SAAS,GAAG,MAAM,IAAI,KAAK;AACjC,QAAI,SAAS,cAAc,gCAAgC,MAAM,IAAI,GAAG;AACtE;AAAA,IACF;AACA,UAAM,gBAAgB,SAAS,cAAc,QAAQ;AACrD,kBAAc,QAAQ,gBAAgB;AACtC,QAAI,WAAW,SAAS,YAAY;AAClC,oBAAc,MAAM,WAAW;AAC/B,oBAAc,QAAQ;AACtB,iBAAW,CAAC,eAAe,cAAc,KAAK,OAAO;AAAA,QACnD,WAAW,cAAc,CAAC;AAAA,MAC5B,GAAG;AACD,sBAAc,aAAa,eAAe,cAAc;AAAA,MAC1D;AAAA,IACF,WAAW,WAAW,UAAU,iBAAiB,SAAS,cAAc;AAItE,oBAAc,cAAc,gBAAgB,WAAW,IAAI;AAAA,IAC7D,OAAO;AAGL,UAAI,WAAW,QAAQ;AACrB,sBAAc,OAAO;AAAA,MACvB;AACA,oBAAc,cAAc,WAAW;AAAA,IACzC;AACA,eAAW,CAAC,eAAe,cAAc,KAAK,OAAO;AAAA,MACnD,mBAAmB,CAAC;AAAA,IACtB,GAAG;AACD,oBAAc,aAAa,eAAe,cAAc;AAAA,IAC1D;AACA,aAAS,KAAK,OAAO,aAAa;AAClC,iBAAa,KAAK,aAAa;AAAA,EACjC,CAAC;AACD,SAAO;AACT;AAEA,SAAS,gBAAgB,MAAsB;AAC7C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,8BAA8B,KAAK,UAAU,IAAI,CAAC;AAAA,IAClD;AAAA,EACF,EAAE,KAAK,IAAI;AACb;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/replo-scripts.tsx"],
4
- "sourcesContent": ["\"use client\";\n\nimport type {\n ConsentCategory,\n ReploScriptEntry,\n ScriptTagDescriptor,\n} from \"schemas/generated/consent\";\nimport type { RegisteredScript } from \"./script-registration-store\";\n\nimport { Component } from \"react\";\n\nimport {\n activateBlockedScripts,\n buildGrantMarker,\n enableConsentDelegation,\n findConsentPlatform,\n findDelegatedPlatform,\n getBlockingAttributes,\n isConsentPlatformEntry,\n OPTIONAL_CONSENT_CATEGORIES,\n} from \"./consent-platform\";\nimport { isConsentAllowed, useConsent } from \"./consent-store\";\nimport { injectScriptDescriptors } from \"./inject-script-descriptors\";\nimport {\n registerScript,\n snippetScriptId,\n unregisterScript,\n} from \"./script-registration-store\";\nimport { buildScriptTags, defaultConsentFor } from \"./script-snippets\";\nimport { installConsentWindowApi } from \"./window-api\";\n\n/**\n * Stable identity for an entry, used as React key and DOM dedupe key. `snippet`\n * and `custom` entries key on their `id`; identifier providers on type+id.\n */\nfunction scriptId(entry: ReploScriptEntry): string {\n if (entry.type === \"custom\") {\n return `custom:${entry.id}`;\n }\n if (entry.type === \"snippet\") {\n return snippetScriptId(entry.id);\n }\n if (entry.type === \"consentPlatform\") {\n return `consent-platform:${entry.id}`;\n }\n return `${entry.type}:${entry.identifier}`;\n}\n\nfunction requiredConsentFor(entry: ReploScriptEntry): ConsentCategory[] {\n if (entry.type === \"custom\" || entry.type === \"snippet\") {\n return entry.requiredConsent;\n }\n // Platform entries are filtered out before render; arms exist for the types.\n if (entry.type === \"consentPlatform\" || entry.type === \"Cookiebot\") {\n return [];\n }\n return entry.requiredConsent ?? defaultConsentFor(entry.type);\n}\n\n/**\n * Resolves an entry to the concrete tags to inject. An identifier provider may\n * resolve to multiple tags (GA4 = external loader + inline config); a `snippet`\n * entry injects its pasted body inline; a `custom` entry is a single external\n * `src` or inline `body`.\n */\nfunction descriptorsFor(entry: ReploScriptEntry): ScriptTagDescriptor[] {\n if (entry.type === \"custom\") {\n if (entry.src) {\n return [{ kind: \"external\", src: entry.src }];\n }\n if (entry.body) {\n return [{ kind: \"inline\", body: entry.body }];\n }\n return [];\n }\n if (entry.type === \"snippet\") {\n return [{ kind: \"inline\", body: entry.body, module: entry.module }];\n }\n // Platform entries load from their platform config rather than per-entry.\n if (entry.type === \"consentPlatform\" || entry.type === \"Cookiebot\") {\n return [];\n }\n return buildScriptTags({ type: entry.type, identifier: entry.identifier });\n}\n\n/**\n * Injects the resolved tags into `document.head` once a caller has decided\n * consent allows it. Reused both for author-managed `ReploScripts` entries and\n * for the implicit Replo first-party pixel.\n *\n * DOM insertion (not JSX) is required because inline `<script>` bodies set via\n * React's `dangerouslySetInnerHTML` never execute. Each node is tagged with\n * `data-replo-script-id` (plus a per-tag index) so re-renders and React Strict\n * Mode double-invokes do not double-inject.\n */\ntype InjectedScriptProps = {\n baseId: string;\n descriptors: ScriptTagDescriptor[];\n /** Used under a delegated platform to derive the attributes that gate the tag. */\n requiredConsent?: ConsentCategory[];\n};\n\nexport class InjectedScript extends Component<InjectedScriptProps> {\n componentDidMount() {\n const { baseId, descriptors, requiredConsent = [] } = this.props;\n const platform = findDelegatedPlatform();\n const blockingAttributes = platform\n ? getBlockingAttributes(platform, requiredConsent)\n : {};\n const blockedByPlatform = \"type\" in blockingAttributes;\n const createdNodes = injectScriptDescriptors({\n baseId,\n descriptors,\n extraAttributes: blockingAttributes,\n });\n\n // Cookiebot may finish its startup scan before late consent-gated tags mount.\n if (blockedByPlatform && createdNodes.length > 0) {\n activateBlockedScripts();\n }\n // NOTE (Ryan, 2026-09-02): Executed scripts stay mounted because removing their nodes cannot unload the vendor.\n }\n\n render() {\n return null;\n }\n}\n\nclass ScriptRegistration extends Component<{ script: RegisteredScript }> {\n componentDidMount() {\n registerScript(this.props.script);\n }\n\n componentWillUnmount() {\n unregisterScript(this.props.script.id);\n }\n\n render() {\n return null;\n }\n}\n\nfunction ManagedScript({ entry }: { entry: ReploScriptEntry }) {\n const consent = useConsent();\n const requiredConsent = requiredConsentFor(entry);\n const id = scriptId(entry);\n const isBlockedByNativeConsent =\n !findDelegatedPlatform() &&\n !isConsentAllowed({ state: consent, requiredConsent });\n\n return (\n <>\n <ScriptRegistration\n key={`${id}:${requiredConsent.join(\",\")}`}\n script={{ id, type: entry.type, requiredConsent }}\n />\n {!isBlockedByNativeConsent && (\n <InjectedScript\n baseId={id}\n descriptors={descriptorsFor(entry)}\n requiredConsent={requiredConsent}\n />\n )}\n </>\n );\n}\n\nclass ConsentWindowApi extends Component {\n private uninstall = () => {};\n\n componentDidMount() {\n this.uninstall = installConsentWindowApi();\n }\n\n componentWillUnmount() {\n this.uninstall();\n }\n\n render() {\n return null;\n }\n}\n\n/**\n * The single registry component for all managed tracking scripts on the site.\n * Authored once in `app/layout.tsx`; the agent/miniapp only edit the `scripts`\n * array. Each entry registers itself and is gated + injected per consent.\n *\n * A consent-platform entry (`Cookiebot`, or a generic `consentPlatform`) takes\n * over consent site-wide: scripts carry the platform's blocking attributes, its\n * loader injects after them so its startup scan sees the full set, and Replo's\n * own gates follow the platform instead of the native banner.\n */\nexport function ReploScripts({ scripts }: { scripts: ReploScriptEntry[] }) {\n // The earlier-mounted first-party pixel needs delegation before mount lifecycles run.\n const platform = findConsentPlatform(scripts);\n if (platform) {\n enableConsentDelegation(platform);\n }\n\n // The loader renders last so every blocked tag mounts before its startup scan.\n return (\n <>\n <ConsentWindowApi />\n {scripts\n .filter((entry) => !isConsentPlatformEntry(entry))\n .map((entry) => (\n <ManagedScript key={scriptId(entry)} entry={entry} />\n ))}\n {platform &&\n !platform.isCookiebot &&\n OPTIONAL_CONSENT_CATEGORIES.map((category) => (\n <InjectedScript\n key={category}\n baseId={`consent-platform:${platform.id}:grant:${category}`}\n descriptors={[buildGrantMarker(category)]}\n requiredConsent={[category]}\n />\n ))}\n {platform && (\n <InjectedScript\n baseId={`consent-platform:${platform.id}`}\n descriptors={platform.loader}\n />\n )}\n </>\n );\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/script-registration-store.ts"],
4
- "sourcesContent": ["\"use client\";\n\nimport type {\n ConsentCategory,\n ReploScriptType,\n} from \"schemas/generated/consent\";\n\nimport { useSyncExternalStore } from \"react\";\n\n/**\n * Tracks which managed scripts are currently mounted on the page. Each\n * `ReploScripts` entry registers itself here on mount, so the analytics provider\n * can create sinks only for providers that are actually present (and consented),\n * without importing a static list. Like the consent store, this is a singleton\n * rather than React context so it works regardless of tree position.\n */\n\nexport interface RegisteredScript {\n id: string;\n type: ReploScriptType;\n requiredConsent: ConsentCategory[];\n}\n\n/**\n * Registration id of a `snippet` entry. Snippet providers have no distinct\n * `type`, so both `ReploScripts` (registering) and the analytics sink factory\n * (matching) identify them by this id.\n */\nexport function snippetScriptId(entryId: string) {\n return `snippet:${entryId}`;\n}\n\nconst registry = new Map<string, RegisteredScript>();\nconst listeners = new Set<() => void>();\n\n// useSyncExternalStore requires a referentially-stable snapshot between emits.\nlet snapshot: RegisteredScript[] = [];\n\nconst SERVER_SNAPSHOT: RegisteredScript[] = [];\n\nfunction emit() {\n snapshot = [...registry.values()];\n for (const listener of listeners) {\n listener();\n }\n}\n\nexport const scriptRegistrationStore = {\n register(script: RegisteredScript) {\n registry.set(script.id, script);\n emit();\n },\n unregister(id: string) {\n if (registry.delete(id)) {\n emit();\n }\n },\n getSnapshot(): RegisteredScript[] {\n return snapshot;\n },\n getServerSnapshot(): RegisteredScript[] {\n return SERVER_SNAPSHOT;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n /** Test-only: clear the registry between cases. */\n reset() {\n registry.clear();\n listeners.clear();\n snapshot = [];\n },\n};\n\nexport function registerScript(script: RegisteredScript) {\n scriptRegistrationStore.register(script);\n}\n\nexport function unregisterScript(id: string) {\n scriptRegistrationStore.unregister(id);\n}\n\nexport function useRegisteredScripts(): RegisteredScript[] {\n return useSyncExternalStore(\n scriptRegistrationStore.subscribe,\n scriptRegistrationStore.getSnapshot,\n scriptRegistrationStore.getServerSnapshot,\n );\n}\n"],
5
- "mappings": ";AAOA,SAAS,4BAA4B;AAqB9B,SAAS,gBAAgB,SAAiB;AAC/C,SAAO,WAAW,OAAO;AAC3B;AAEA,MAAM,WAAW,oBAAI,IAA8B;AACnD,MAAM,YAAY,oBAAI,IAAgB;AAGtC,IAAI,WAA+B,CAAC;AAEpC,MAAM,kBAAsC,CAAC;AAE7C,SAAS,OAAO;AACd,aAAW,CAAC,GAAG,SAAS,OAAO,CAAC;AAChC,aAAW,YAAY,WAAW;AAChC,aAAS;AAAA,EACX;AACF;AAEO,MAAM,0BAA0B;AAAA,EACrC,SAAS,QAA0B;AACjC,aAAS,IAAI,OAAO,IAAI,MAAM;AAC9B,SAAK;AAAA,EACP;AAAA,EACA,WAAW,IAAY;AACrB,QAAI,SAAS,OAAO,EAAE,GAAG;AACvB,WAAK;AAAA,IACP;AAAA,EACF;AAAA,EACA,cAAkC;AAChC,WAAO;AAAA,EACT;AAAA,EACA,oBAAwC;AACtC,WAAO;AAAA,EACT;AAAA,EACA,UAAU,UAAkC;AAC1C,cAAU,IAAI,QAAQ;AACtB,WAAO,MAAM;AACX,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA,EAEA,QAAQ;AACN,aAAS,MAAM;AACf,cAAU,MAAM;AAChB,eAAW,CAAC;AAAA,EACd;AACF;AAEO,SAAS,eAAe,QAA0B;AACvD,0BAAwB,SAAS,MAAM;AACzC;AAEO,SAAS,iBAAiB,IAAY;AAC3C,0BAAwB,WAAW,EAAE;AACvC;AAEO,SAAS,uBAA2C;AACzD,SAAO;AAAA,IACL,wBAAwB;AAAA,IACxB,wBAAwB;AAAA,IACxB,wBAAwB;AAAA,EAC1B;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/script-snippets.ts"],
4
- "sourcesContent": ["import type {\n ConsentCategory,\n ReploScriptType,\n ScriptTagDescriptor,\n} from \"schemas/generated/consent\";\n\n// Identifier providers: those whose script the runtime synthesizes from an ID.\n// `snippet` (pasted whole) and `custom` carry their own bodies instead.\n// Cookiebot's loader is built by its consent-platform preset, not here.\ntype ProviderType = Exclude<\n ReploScriptType,\n \"custom\" | \"snippet\" | \"consentPlatform\" | \"Cookiebot\"\n>;\n\n/**\n * Runtime-safe per-provider data for the consent system. This is the subset of\n * the website-builder miniapp's `scriptConstants.ts` that must ship in the\n * customer site bundle: how to materialize each provider's script tags, and the\n * default consent category that gates it. Miniapp-only concerns (LLM detection\n * schemas, logos, marketing copy) intentionally stay in the miniapp.\n *\n * Unlike the miniapp's `buildSnippet`, which returns a JSX *string* for the\n * agent to paste into `layout.tsx`, this returns structured descriptors so the\n * runtime can inject real DOM nodes (inline `<script>` bodies set via React's\n * `dangerouslySetInnerHTML` never execute).\n */\ntype ProviderSnippet = {\n defaultConsent: ConsentCategory[];\n buildTags: (identifier: string) => ScriptTagDescriptor[];\n};\n\n/**\n * NOTE (Ryan, 2026-05-28, REPL-27515): `defaultConsent` is net-new,\n * legally-sensitive categorization that does not exist elsewhere in the repo.\n * These are conservative defaults a customer can override per entry via\n * `requiredConsent`; container/CDP providers that fan out to multiple vendors\n * (GTM, Segment) require the union of categories they can serve.\n */\nconst PROVIDER_SNIPPETS: Record<ProviderType, ProviderSnippet> = {\n GA4: {\n defaultConsent: [\"analytics\"],\n buildTags: (id) => [\n {\n kind: \"external\",\n src: `https://www.googletagmanager.com/gtag/js?id=${id}`,\n },\n {\n kind: \"inline\",\n body: `window.dataLayer = window.dataLayer || [];\\nfunction gtag(){dataLayer.push(arguments);}\\ngtag('js', new Date());\\ngtag('config', '${id}');`,\n },\n ],\n },\n GoogleTagManager: {\n defaultConsent: [\"analytics\", \"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${id}');`,\n },\n ],\n },\n Meta: {\n defaultConsent: [\"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');fbq('init','${id}');fbq('track','PageView');`,\n },\n ],\n },\n TikTok: {\n defaultConsent: [\"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `!function(w,d,t){w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];ttq.methods=[\"page\",\"track\",\"identify\",\"instances\",\"debug\",\"on\",\"off\",\"once\",\"ready\",\"alias\",\"group\",\"enableCookie\",\"disableCookie\",\"holdConsent\",\"revokeConsent\",\"grantConsent\"],ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e};ttq.load=function(e,n){var r=\"https://analytics.tiktok.com/i18n/pixel/events.js\",o=n&&n.partner;ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=r,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};var a=document.createElement(\"script\");a.type=\"text/javascript\",a.async=!0,a.src=r+\"?sdkid=\"+e+\"&lib=\"+t;var s=document.getElementsByTagName(\"script\")[0];s.parentNode.insertBefore(a,s)};ttq.load('${id}');ttq.page();}(window,document,'ttq');`,\n },\n ],\n },\n Pinterest: {\n defaultConsent: [\"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `!function(e){if(!window.pintrk){window.pintrk=function(){window.pintrk.queue.push(Array.prototype.slice.call(arguments))};var n=window.pintrk;n.queue=[],n.version=\"3.0\";var t=document.createElement(\"script\");t.async=!0,t.src=e;var r=document.getElementsByTagName(\"script\")[0];r.parentNode.insertBefore(t,r)}}(\"https://s.pinimg.com/ct/core.js\");pintrk('load','${id}');pintrk('page');`,\n },\n ],\n },\n Reddit: {\n defaultConsent: [\"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `!function(w,d){if(!w.rdt){var p=w.rdt=function(){p.sendEvent?p.sendEvent.apply(p,arguments):p.callQueue.push(arguments)};p.callQueue=[];var t=d.createElement(\"script\");t.src=\"https://www.redditstatic.com/ads/pixel.js\",t.async=!0;var s=d.getElementsByTagName(\"script\")[0];s.parentNode.insertBefore(t,s)}}(window,document);rdt('init','${id}');rdt('track','PageVisit');`,\n },\n ],\n },\n Snapchat: {\n defaultConsent: [\"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `(function(e,t,n){if(e.snaptr)return;var a=e.snaptr=function(){a.handleRequest?a.handleRequest.apply(a,arguments):a.queue.push(arguments)};a.queue=[];var s='script';var r=t.createElement(s);r.async=!0;r.src=n;var u=t.getElementsByTagName(s)[0];u.parentNode.insertBefore(r,u);})(window,document,'https://sc-static.net/scevent.min.js');snaptr('init','${id}',{});snaptr('track','PAGE_VIEW');`,\n },\n ],\n },\n Hotjar: {\n defaultConsent: [\"analytics\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `(function(h,o,t,j,a,r){h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};h._hjSettings={hjid:${id},hjsv:6};a=o.getElementsByTagName('head')[0];r=o.createElement('script');r.async=1;r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;a.appendChild(r);})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');`,\n },\n ],\n },\n MicrosoftClarity: {\n defaultConsent: [\"analytics\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};t=l.createElement(r);t.async=1;t.src=\"https://www.clarity.ms/tag/\"+i;y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);})(window,document,\"clarity\",\"script\",\"${id}\");`,\n },\n ],\n },\n Contentsquare: {\n defaultConsent: [\"analytics\"],\n // The UXA tag is a single async external script keyed by the 13-char tag id;\n // it bootstraps the Contentsquare `_uxa` queue itself once loaded.\n buildTags: (id) => [\n {\n kind: \"external\",\n src: `https://t.contentsquare.net/uxa/${id}.js`,\n attributes: { async: \"true\" },\n },\n ],\n },\n Segment: {\n defaultConsent: [\"analytics\", \"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `!function(){var i=\"analytics\",analytics=window[i]=window[i]||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error(\"Segment snippet included twice.\");else{analytics.invoked=!0;analytics.methods=[\"trackSubmit\",\"trackClick\",\"trackLink\",\"trackForm\",\"pageview\",\"identify\",\"reset\",\"group\",\"track\",\"ready\",\"alias\",\"debug\",\"page\",\"screen\",\"once\",\"off\",\"on\",\"addSourceMiddleware\",\"addIntegrationMiddleware\",\"setAnonymousId\",\"addDestinationMiddleware\",\"register\"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement(\"script\");t.type=\"text/javascript\";t.async=!0;t.src=\"https://cdn.segment.com/analytics.js/v1/\"+key+\"/analytics.min.js\";var n=document.getElementsByTagName(\"script\")[0];n.parentNode.insertBefore(t,n);analytics._loadOptions=e};analytics._writeKey=\"${id}\";analytics.SNIPPET_VERSION=\"5.2.0\";analytics.load(\"${id}\");analytics.page();}}();`,\n },\n ],\n },\n Northbeam: {\n defaultConsent: [\"analytics\", \"marketing\"],\n buildTags: (id) => [\n {\n kind: \"inline\",\n body: `(function(){var t;(n=t=t||{}).A=\"identify\",n.B=\"trackPageView\",n.C=\"fireEmailCaptureEvent\",n.D=\"fireCustomGoal\",n.E=\"firePurchaseEvent\",n.F=\"trackPageViewInitial\",n.G=\"fireSlimPurchaseEvent\",n.H=\"identifyCustomerId\";var n=\"https://j.northbeam.io/ota-sp/${id}.js\";function r(n){for(var e=[],t=1;t<arguments.length;t++)e[t-1]=arguments[t];i.push({fnName:n,args:e})}var e,i=[],a=((e={})[t.F]=function(n){r(t.F,n)},(a={_q:i})[t.A]=function(n,e){return r(t.A,n,e)},a[t.B]=function(){return r(t.B)},a[t.C]=function(n,e){return r(t.C,n,e)},a[t.D]=function(n,e){return r(t.D,n,e)},a[t.E]=function(n){return r(t.E,n)},a[t.G]=function(n){return r(t.G,n)},a[t.H]=function(n,e){return r(t.H,n,e)},Object.assign(function(n){for(var e=[],t=1;t<arguments.length;t++)e.push(arguments[t]);return r.apply(null,[n].concat(e))},a));window.Northbeam=a,(a=document.createElement(\"script\")).async=!0,a.src=n,document.head.appendChild(a),e.trackPageViewInitial(window.location.href);})()`,\n },\n ],\n },\n Converge: {\n defaultConsent: [\"analytics\", \"marketing\"],\n buildTags: (id) => [\n {\n kind: \"external\",\n src: `https://static.runconverge.com/pixels/${id}.js`,\n attributes: { async: \"true\" },\n },\n {\n kind: \"inline\",\n body: `window.cvg||(c=window.cvg=function(){c.process?c.process.apply(c,arguments):c.queue.push(arguments)},c.queue=[]);cvg({method:\"track\",eventName:\"$page_load\"});`,\n },\n ],\n },\n Gorgias: {\n // Support chat stores the visitor's chat session, which is a functional\n // preference rather than analytics or ad tracking.\n defaultConsent: [\"preferences\"],\n // The id attribute matches Gorgias's official v3 install snippet; their\n // customization scripts and support tooling look the tag up by that id.\n buildTags: (id) => [\n {\n kind: \"external\",\n src: `https://config.gorgias.chat/bundle-loader/${id}`,\n attributes: { id: \"gorgias-chat-widget-install-v3\" },\n },\n ],\n },\n};\n\n/**\n * Snippet providers don't synthesize tags (the user pastes the provider-issued\n * snippet as the entry's `body`), but they still get a first-class default\n * consent categorization like identifier providers.\n */\n/**\n * Returns the structured tag descriptors for a named provider. A single provider\n * can produce multiple tags (e.g. GA4 = external loader + inline config), which\n * the runtime injects in order.\n */\nexport function buildScriptTags({\n type,\n identifier,\n}: {\n type: ProviderType;\n identifier: string;\n}): ScriptTagDescriptor[] {\n return PROVIDER_SNIPPETS[type].buildTags(identifier);\n}\n\n/**\n * The default consent categories that gate an identifier-provider script when an\n * entry does not specify `requiredConsent`. `snippet` and `custom` entries carry\n * `requiredConsent` explicitly (the runtime has no default for them), so this is\n * only consulted for identifier providers. Overridable per entry by the customer.\n */\nexport function defaultConsentFor(type: ProviderType): ConsentCategory[] {\n return PROVIDER_SNIPPETS[type].defaultConsent;\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": [],
4
- "sourcesContent": [],
5
- "mappings": "",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../consent/window-api.ts"],
4
- "sourcesContent": ["\"use client\";\n\nimport type { ConsentCategory } from \"schemas/generated/consent\";\n\nimport { consentStore, isConsentAllowed } from \"./consent-store\";\n\n/**\n * Global, framework-agnostic consent API exposed on `window.Replo.customerPrivacy`,\n * mirroring Shopify's Customer Privacy API surface so on-page (third-party)\n * scripts can query consent and react to changes without importing canopy.\n *\n * It deliberately lives under our own `window.Replo` namespace rather than\n * `window.Shopify` to avoid colliding with Shopify's real API on Shopify-backed\n * sites.\n */\n\ntype ToggleableCategory = Exclude<ConsentCategory, \"necessary\">;\n\n/** Shopify-style tri-state: declared-allowed, declared-denied, or undeclared. */\ntype ConsentSignal = \"yes\" | \"no\" | \"\";\n\nexport interface ReploCustomerPrivacyApi {\n /** Per-category declaration, or \"\" when the visitor has not decided. */\n currentVisitorConsent(): Record<ToggleableCategory, ConsentSignal>;\n analyticsProcessingAllowed(): boolean;\n marketingAllowed(): boolean;\n preferencesProcessingAllowed(): boolean;\n saleOfDataAllowed(): boolean;\n /**\n * Whether Replo's consent banner should still be shown (mode on + undecided).\n * Always false when an external CMP owns consent and shows its own banner.\n */\n shouldShowBanner(): boolean;\n /** Update a subset of categories; runs the optional callback once persisted. */\n setTrackingConsent(\n consent: Partial<Record<ToggleableCategory, boolean>>,\n callback?: () => void,\n ): void;\n}\n\ndeclare global {\n interface Window {\n Replo?: { customerPrivacy?: ReploCustomerPrivacyApi };\n }\n}\n\n/** DOM event dispatched on `document` whenever consent changes. */\nexport const CONSENT_CHANGE_EVENT = \"visitorConsentCollected\";\n\nfunction isAllowed(category: ConsentCategory): boolean {\n return isConsentAllowed({\n state: consentStore.getSnapshot(),\n requiredConsent: [category],\n });\n}\n\nfunction currentVisitorConsent(): Record<ToggleableCategory, ConsentSignal> {\n const state = consentStore.getSnapshot();\n const signal = (category: ToggleableCategory): ConsentSignal => {\n if (state.decidedAt === null) {\n return \"\";\n }\n return state.categories[category] ? \"yes\" : \"no\";\n };\n return {\n analytics: signal(\"analytics\"),\n marketing: signal(\"marketing\"),\n preferences: signal(\"preferences\"),\n sale_of_data: signal(\"sale_of_data\"),\n };\n}\n\nconst api: ReploCustomerPrivacyApi = {\n currentVisitorConsent,\n analyticsProcessingAllowed: () => isAllowed(\"analytics\"),\n marketingAllowed: () => isAllowed(\"marketing\"),\n preferencesProcessingAllowed: () => isAllowed(\"preferences\"),\n saleOfDataAllowed: () => isAllowed(\"sale_of_data\"),\n shouldShowBanner: () => {\n const state = consentStore.getSnapshot();\n // A delegated platform renders its own banner; showing Replo's too would\n // ask the visitor twice.\n if (state.cmp === \"cookiebot\" || state.cmp === \"external\") {\n return false;\n }\n return state.mode !== \"off\" && state.decidedAt === null;\n },\n setTrackingConsent: (consent, callback) => {\n consentStore.updateCategories({ next: consent });\n callback?.();\n },\n};\n\nfunction consentChangeDetail() {\n return {\n analyticsAllowed: isAllowed(\"analytics\"),\n marketingAllowed: isAllowed(\"marketing\"),\n preferencesAllowed: isAllowed(\"preferences\"),\n saleOfDataAllowed: isAllowed(\"sale_of_data\"),\n };\n}\n\nlet teardown: (() => void) | null = null;\n\n/**\n * Installs `window.Replo.customerPrivacy` and dispatches `visitorConsentCollected`\n * on `document` whenever consent changes. Idempotent; returns a teardown that\n * removes the change subscription. Safe to call on the server (no-op).\n */\nexport function installConsentWindowApi(): () => void {\n if (typeof window === \"undefined\") {\n return () => {\n // no-op on the server\n };\n }\n if (teardown) {\n return teardown;\n }\n window.Replo = { ...window.Replo, customerPrivacy: api };\n const unsubscribe = consentStore.subscribe(() => {\n document.dispatchEvent(\n new CustomEvent(CONSENT_CHANGE_EVENT, { detail: consentChangeDetail() }),\n );\n });\n teardown = () => {\n unsubscribe();\n teardown = null;\n };\n return teardown;\n}\n"],
5
- "mappings": ";AAIA,SAAS,cAAc,wBAAwB;AA2CxC,MAAM,uBAAuB;AAEpC,SAAS,UAAU,UAAoC;AACrD,SAAO,iBAAiB;AAAA,IACtB,OAAO,aAAa,YAAY;AAAA,IAChC,iBAAiB,CAAC,QAAQ;AAAA,EAC5B,CAAC;AACH;AAEA,SAAS,wBAAmE;AAC1E,QAAM,QAAQ,aAAa,YAAY;AACvC,QAAM,SAAS,CAAC,aAAgD;AAC9D,QAAI,MAAM,cAAc,MAAM;AAC5B,aAAO;AAAA,IACT;AACA,WAAO,MAAM,WAAW,QAAQ,IAAI,QAAQ;AAAA,EAC9C;AACA,SAAO;AAAA,IACL,WAAW,OAAO,WAAW;AAAA,IAC7B,WAAW,OAAO,WAAW;AAAA,IAC7B,aAAa,OAAO,aAAa;AAAA,IACjC,cAAc,OAAO,cAAc;AAAA,EACrC;AACF;AAEA,MAAM,MAA+B;AAAA,EACnC;AAAA,EACA,4BAA4B,MAAM,UAAU,WAAW;AAAA,EACvD,kBAAkB,MAAM,UAAU,WAAW;AAAA,EAC7C,8BAA8B,MAAM,UAAU,aAAa;AAAA,EAC3D,mBAAmB,MAAM,UAAU,cAAc;AAAA,EACjD,kBAAkB,MAAM;AACtB,UAAM,QAAQ,aAAa,YAAY;AAGvC,QAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,YAAY;AACzD,aAAO;AAAA,IACT;AACA,WAAO,MAAM,SAAS,SAAS,MAAM,cAAc;AAAA,EACrD;AAAA,EACA,oBAAoB,CAAC,SAAS,aAAa;AACzC,iBAAa,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC/C,eAAW;AAAA,EACb;AACF;AAEA,SAAS,sBAAsB;AAC7B,SAAO;AAAA,IACL,kBAAkB,UAAU,WAAW;AAAA,IACvC,kBAAkB,UAAU,WAAW;AAAA,IACvC,oBAAoB,UAAU,aAAa;AAAA,IAC3C,mBAAmB,UAAU,cAAc;AAAA,EAC7C;AACF;AAEA,IAAI,WAAgC;AAO7B,SAAS,0BAAsC;AACpD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,MAAM;AAAA,IAEb;AAAA,EACF;AACA,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AACA,SAAO,QAAQ,EAAE,GAAG,OAAO,OAAO,iBAAiB,IAAI;AACvD,QAAM,cAAc,aAAa,UAAU,MAAM;AAC/C,aAAS;AAAA,MACP,IAAI,YAAY,sBAAsB,EAAE,QAAQ,oBAAoB,EAAE,CAAC;AAAA,IACzE;AAAA,EACF,CAAC;AACD,aAAW,MAAM;AACf,gBAAY;AACZ,eAAW;AAAA,EACb;AACA,SAAO;AACT;",
6
- "names": []
7
- }
package/content.js.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 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_ENTRY_PUBLISHED_FILE_EXTENSION,\n CONTENT_RELATED_VALUE_STORAGE_KEY,\n contentCollectionFieldSchema,\n contentCollectionFileSchema,\n getContentEntryFileSlug,\n getPublishedContentFields,\n isSafeContentUrl,\n isValidContentSlug,\n parseContentEntryRef,\n} from \"schemas/contentCollection\";\nimport {\n getContentEntryFieldValidationIssues,\n getContentRenderableEntryValues,\n parseContentEntryDocument,\n} from \"schemas/contentEntry\";\nimport {\n getContentManifestCollectionSlugs,\n getContentManifestEntrySlugs,\n} from \"schemas/contentManifest\";\nimport { z } from \"zod\";\n\ntype ContentCollectionFile = z.infer<typeof contentCollectionFileSchema>;\n\n/** Public name for the shared collection-field contract. */\nexport type ContentField = z.infer<typeof contentCollectionFieldSchema>;\n\nexport interface ContentEntry {\n collection: {\n name: string;\n description: string;\n slug: string;\n };\n fields: ContentField[];\n slug: string;\n body: string;\n values: Record<string, unknown>;\n}\n\n/** One entry's slot in a collection listing. */\nexport interface ContentCollectionEntry {\n slug: string;\n title: string;\n excerpt: string | null;\n coverImage: string | null;\n coverImageAlt: string | null;\n publishedAt: string | null;\n status: \"draft\" | \"published\";\n}\n\nexport interface ContentCollection {\n slug: string;\n name: string;\n description: string;\n entries: ContentCollectionEntry[];\n}\n\n/** An ordered content field paired with this entry's value for it. */\nexport interface ContentFieldWithValue {\n field: ContentField;\n value: unknown;\n}\n\nexport interface ContentSitemapEntry {\n url: string;\n lastModified?: Date;\n}\n\nexport interface ContentSource {\n read(relativePath: string): Promise<string | null>;\n listCollections(): Promise<string[]>;\n listEntries(collection: string): Promise<string[]>;\n}\n\ninterface ContentOptions {\n contentSource?: ContentSource;\n contentDirectory?: string;\n contentManifest?: ContentManifest;\n}\n\nexport function createContentSource({\n contentDirectory = getDefaultContentDirectory(),\n contentManifest,\n}: {\n contentDirectory?: string;\n contentManifest?: ContentManifest;\n} = {}): ContentSource {\n if (contentManifest) {\n return {\n read: async (relativePath) => {\n return contentManifest.files[relativePath] ?? null;\n },\n listCollections: async () => {\n return getContentManifestCollectionSlugs(contentManifest);\n },\n listEntries: async (collection) => {\n return getContentManifestEntrySlugs({ collection, contentManifest });\n },\n };\n }\n return {\n read: (relativePath) => {\n return readTextFileOrNull(join(contentDirectory, relativePath));\n },\n listCollections: async () => {\n return (await readDirectoryOrEmpty(contentDirectory)).flatMap((entry) => {\n return entry.isDirectory() ? [entry.name] : [];\n });\n },\n listEntries: async (collection) => {\n return (\n await readDirectoryOrEmpty(join(contentDirectory, collection))\n ).flatMap((entry) => {\n const parsed = entry.isFile()\n ? getContentEntryFileSlug(entry.name)\n : null;\n return parsed?.kind === \"draft\" ? [parsed.slug] : [];\n });\n },\n };\n}\n\nexport async function getContentEntry({\n collection,\n slug,\n ...contentOptions\n}: {\n collection: string;\n slug: string;\n} & ContentOptions): Promise<ContentEntry> {\n const entry = await findContentEntry({\n collection,\n slug,\n contentSource: getContentSource(contentOptions),\n });\n if (!entry) {\n notFound();\n }\n return entry;\n}\n\n/** Static params for one collection's `[slug]` detail route. */\nexport async function getContentCollectionStaticParams({\n collection,\n ...contentOptions\n}: { collection: string } & ContentOptions): Promise<{ slug: string }[]> {\n const contentSource = getContentSource(contentOptions);\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return [];\n }\n const entries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n return entries\n .map((entry) => entry.slug)\n .sort()\n .map((slug) => ({ slug }));\n}\n\n/**\n * A collection and its renderable entries for a listing page. Uses the\n * not-found contract when the collection is missing or archived.\n */\nexport async function getContentCollection({\n collection,\n ...contentOptions\n}: { collection: string } & ContentOptions): Promise<ContentCollection> {\n const loaded = await findContentCollection({\n collection,\n contentSource: getContentSource(contentOptions),\n });\n if (!loaded) {\n notFound();\n }\n return loaded;\n}\n\n/**\n * Per-entry head metadata (title/description/OpenGraph/Twitter) from the\n * entry's SEO fields with content fallbacks. Returns an empty object when the\n * entry is not renderable, so `generateMetadata` can call it unconditionally.\n */\nexport async function getContentEntryMetadata({\n collection,\n slug,\n ...contentOptions\n}: {\n collection: string;\n slug: string;\n} & ContentOptions): Promise<Metadata> {\n const entry = await findContentEntry({\n collection,\n slug,\n contentSource: getContentSource(contentOptions),\n });\n if (!entry) {\n return {};\n }\n\n const title =\n readNonEmptyString(entry.values.seoTitle) ??\n readNonEmptyString(entry.values.title) ??\n entry.collection.name;\n const description =\n readNonEmptyString(entry.values.seoDescription) ??\n readNonEmptyString(entry.values.excerpt);\n const image = readSafeImageUrl(entry.values.coverImage);\n\n return {\n title,\n ...(description === null ? {} : { description }),\n openGraph: {\n title,\n ...(description === null ? {} : { description }),\n type: \"article\",\n ...(image === null ? {} : { images: [image] }),\n },\n twitter: {\n card: image === null ? \"summary\" : \"summary_large_image\",\n title,\n ...(description === null ? {} : { description }),\n ...(image === null ? {} : { images: [image] }),\n },\n };\n}\n\n/**\n * Structured data for an entry as a JSON-LD script tag. Serialization is\n * script-safe (`<` escaped) so entry content can never break out of the tag.\n * The type is a deliberately neutral `WebPage`; collections have no schema\n * type of their own.\n */\nexport function ContentEntryJsonLd({ entry }: { entry: ContentEntry }) {\n const title =\n readNonEmptyString(entry.values.seoTitle) ??\n readNonEmptyString(entry.values.title) ??\n entry.collection.name;\n const description =\n readNonEmptyString(entry.values.seoDescription) ??\n readNonEmptyString(entry.values.excerpt);\n const image = readSafeImageUrl(entry.values.coverImage);\n const datePublished = readIsoDate(entry.values.publishedAt);\n const dateModified = readIsoDate(entry.values.updatedAt);\n\n const jsonLd = {\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebPage\",\n name: title,\n ...(description === null ? {} : { description }),\n ...(image === null ? {} : { image }),\n ...(datePublished === null ? {} : { datePublished }),\n ...(dateModified === null ? {} : { dateModified }),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger -- NOTE (Ryan, 2026-07-30): JSON-LD must be raw script text; serializeJsonLd escapes `<` so values cannot close the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(jsonLd) }}\n />\n );\n}\n\n/**\n * Absolute sitemap entries for every collection listing and renderable entry.\n * `origin` comes from the caller (e.g. the request's platform-validated Host)\n * because site origins are not fixed at build time.\n */\nexport async function getContentSitemapEntries({\n origin,\n ...contentOptions\n}: { origin: string } & ContentOptions): Promise<ContentSitemapEntry[]> {\n const base = origin.replace(/\\/+$/u, \"\");\n const entries: ContentSitemapEntry[] = [];\n const contentSource = getContentSource(contentOptions);\n\n for (const collection of await listCollectionSlugs({ contentSource })) {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n continue;\n }\n\n // Collections can opt out of a public listing page; its URL would 404.\n if (collectionSchema.hasListingPage !== false) {\n entries.push({ url: `${base}/${collection}` });\n }\n const visibleEntries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n visibleEntries.sort((left, right) => left.slug.localeCompare(right.slug));\n for (const entry of visibleEntries) {\n const lastModified = readValidDate(entry.values.updatedAt);\n entries.push({\n url: `${base}/${collection}/${entry.slug}`,\n ...(lastModified === null ? {} : { lastModified }),\n });\n }\n }\n\n return entries;\n}\n\n/**\n * The full sitemap XML document for a site's content (home, collection\n * listings, entries). Kept here so the generated sitemap route stays a tiny,\n * restylable-free shell around one call.\n */\nexport async function getContentSitemapXml({\n origin,\n ...contentOptions\n}: { origin: string } & ContentOptions): Promise<string> {\n const base = origin.replace(/\\/+$/u, \"\");\n const entries: ContentSitemapEntry[] = [\n { url: base },\n ...(await getContentSitemapEntries({\n origin: base,\n contentSource: getContentSource(contentOptions),\n })),\n ];\n const items = entries.map(({ url, lastModified }) => {\n const lastmod = lastModified\n ? `<lastmod>${lastModified.toISOString()}</lastmod>`\n : \"\";\n return `<url><loc>${escapeXml(url)}</loc>${lastmod}</url>`;\n });\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">${items.join(\n \"\",\n )}</urlset>\\n`;\n}\n\n/**\n * The entry's renderable content fields in collection-settings order, paired\n * with values. This is the published half of the shared ordering contract in\n * `schemas/contentCollection`; empty values are omitted so templates never render\n * bare labels.\n */\nexport function getContentFields(entry: ContentEntry): ContentFieldWithValue[] {\n return getPublishedContentFields(entry.fields)\n .map((field) => ({ field, value: entry.values[field.id] }))\n .filter(({ value }) => {\n return value !== undefined && value !== null && value !== \"\";\n });\n}\n\nexport function ContentFieldValue({\n field,\n value,\n}: {\n field: ContentField;\n value: unknown;\n}): ReactNode {\n if (field.type === \"rich-text\" && typeof value === \"string\") {\n return <SafeMarkdown source={value} />;\n }\n if (field.type === \"image\" && typeof value === \"string\") {\n return (\n <img\n alt={field.label}\n src={value}\n style={{ display: \"block\", height: \"auto\", maxWidth: \"100%\" }}\n />\n );\n }\n if (field.type === \"url\" && typeof value === \"string\") {\n return (\n <a href={value} style={CONTENT_LINK_STYLE}>\n {value}\n </a>\n );\n }\n if (field.type === \"date\" && typeof value === \"string\") {\n const parsed = new Date(value);\n // Explicit locale so the server render matches the client's hydration.\n const label = Number.isNaN(parsed.getTime())\n ? value\n : parsed.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n return <time dateTime={value}>{label}</time>;\n }\n if (field.type === \"boolean\" && typeof value === \"boolean\") {\n return value ? \"Yes\" : \"No\";\n }\n if (field.type === \"related\" && Array.isArray(value)) {\n const items = value.filter(\n (item) => isContentRelatedLink(item) || typeof item === \"string\",\n );\n if (items.length === 0) {\n return null;\n }\n return items.map((item, index) => (\n <Fragment key={isContentRelatedLink(item) ? item.href : String(item)}>\n {index > 0 && \", \"}\n {isContentRelatedLink(item) ? (\n <a href={item.href} style={CONTENT_LINK_STYLE}>\n {item.title}\n </a>\n ) : (\n String(item)\n )}\n </Fragment>\n ));\n }\n if (field.type === \"tags\" && Array.isArray(value)) {\n return value.join(\", \");\n }\n return typeof value === \"string\" ? value : null;\n}\n\nasync function findContentCollection({\n collection,\n contentSource,\n}: {\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentCollection | null> {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return null;\n }\n\n const visibleEntries = await listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n });\n const entries = await Promise.all(\n visibleEntries.map(async (entry): Promise<ContentCollectionEntry> => {\n return {\n slug: entry.slug,\n title: readNonEmptyString(entry.values.title) ?? entry.slug,\n excerpt: readNonEmptyString(entry.values.excerpt),\n coverImage: readSafeImageUrl(entry.values.coverImage),\n coverImageAlt: readNonEmptyString(entry.values.coverImageAlt),\n publishedAt: readIsoDate(entry.values.publishedAt),\n status: await getEntryListingStatus({\n collection,\n slug: entry.slug,\n contentSource,\n }),\n };\n }),\n );\n entries.sort((left, right) => {\n const leftTime = left.publishedAt ? Date.parse(left.publishedAt) : 0;\n const rightTime = right.publishedAt ? Date.parse(right.publishedAt) : 0;\n return rightTime - leftTime || left.title.localeCompare(right.title);\n });\n\n return {\n slug: collection,\n name: collectionSchema.name,\n description: collectionSchema.description,\n entries,\n };\n}\n\nasync function findContentEntry({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry | null> {\n const collectionSchema = await readActiveCollectionSchema({\n collection,\n contentSource,\n });\n if (!collectionSchema) {\n return null;\n }\n return readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n });\n}\n\nasync function listCollectionSlugs({\n contentSource,\n}: {\n contentSource: ContentSource;\n}): Promise<string[]> {\n return (await contentSource.listCollections())\n .filter(isValidContentSlug)\n .sort();\n}\n\n/**\n * Every renderable entry of a collection, each file read and parsed exactly\n * once. Callers pass the already-read collection schema so no helper in this\n * chain ever re-reads it.\n */\nasync function listVisibleEntries({\n collectionSchema,\n collection,\n contentSource,\n}: {\n collectionSchema: ContentCollectionFile;\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry[]> {\n const slugs = await contentSource.listEntries(collection);\n const entries = await Promise.all(\n slugs.map((slug) => {\n return readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n });\n }),\n );\n return entries.filter(isNotNullish);\n}\n\n// In the dev preview an entry is \"published\" when its draft matches its\n// published copy; production sources hold only published copies.\nasync function getEntryListingStatus({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<\"draft\" | \"published\"> {\n if (process.env.NODE_ENV !== \"development\") {\n return \"published\";\n }\n const [draft, publishedCopy] = await Promise.all([\n contentSource.read(`${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`),\n contentSource.read(\n `${collection}/${slug}${CONTENT_ENTRY_PUBLISHED_FILE_EXTENSION}`,\n ),\n ]);\n return draft !== null && draft === publishedCopy ? \"published\" : \"draft\";\n}\n\n/** The collection's parsed schema, or null when missing, invalid, or archived. */\nasync function readActiveCollectionSchema({\n collection,\n contentSource,\n}: {\n collection: string;\n contentSource: ContentSource;\n}): Promise<ContentCollectionFile | null> {\n if (!isValidContentSlug(collection)) {\n return null;\n }\n const rawSchema = await contentSource.read(\n `${collection}/${CONTENT_COLLECTION_SCHEMA_FILENAME}`,\n );\n if (!rawSchema) {\n return null;\n }\n const parsedJson = parseJsonOrNull(rawSchema);\n if (parsedJson === null) {\n return null;\n }\n const result = contentCollectionFileSchema.safeParse(parsedJson);\n if (!result.success || isNotNullish(result.data.archivedAt)) {\n return null;\n }\n return result.data;\n}\n\n// The parsed document when the entry exists and is publicly visible, else\n// null. The one visibility gate for full entry loads and shallow reads alike.\nasync function readVisibleEntryDocument({\n collection,\n slug,\n contentSource,\n}: {\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}) {\n if (!isValidContentSlug(slug)) {\n return null;\n }\n const rawEntry = await contentSource.read(\n `${collection}/${slug}${CONTENT_ENTRY_FILE_EXTENSION}`,\n );\n if (!rawEntry) {\n return null;\n }\n const parsedDocument = parseContentEntryDocument(rawEntry);\n if (parsedDocument.status === \"invalid\") {\n return null;\n }\n // Visibility is structural: the sandbox preview (`next dev`) reads draft\n // files so everything renders there, while production reads the manifest,\n // which the publish tool builds from published copies only.\n return parsedDocument.document;\n}\n\nasync function readVisibleEntry({\n collectionSchema,\n collection,\n slug,\n contentSource,\n}: {\n collectionSchema: ContentCollectionFile;\n collection: string;\n slug: string;\n contentSource: ContentSource;\n}): Promise<ContentEntry | null> {\n const document = await readVisibleEntryDocument({\n collection,\n slug,\n contentSource,\n });\n if (!document) {\n return null;\n }\n\n const values = getContentRenderableEntryValues({\n document,\n slug,\n });\n // NOTE (Ryan, 2026-07-28): Drafts are work-in-progress, so the dev preview\n // renders them even when required fields are still incomplete.\n if (\n getContentEntryFieldValidationIssues({\n fields: collectionSchema.fields,\n values,\n requireRequiredFields: process.env.NODE_ENV !== \"development\",\n }).length > 0\n ) {\n return null;\n }\n\n // The editor's built-in Related Content section stores refs under `related`\n // without a schema field; surface them through a synthetic trailing field so\n // related links render for every collection, not only ones whose schema\n // declares a related field.\n const fields: ContentField[] = [...collectionSchema.fields];\n const hasRelatedField = fields.some((field) => field.type === \"related\");\n const storedRelated = values[CONTENT_RELATED_VALUE_STORAGE_KEY];\n if (\n !hasRelatedField &&\n Array.isArray(storedRelated) &&\n storedRelated.length > 0\n ) {\n fields.push({\n id: CONTENT_RELATED_VALUE_STORAGE_KEY,\n label: \"Related content\",\n type: \"related\",\n required: false,\n enabled: true,\n builtin: true,\n });\n }\n return {\n collection: {\n name: collectionSchema.name,\n description: collectionSchema.description,\n slug: collection,\n },\n fields,\n slug,\n body: document.body,\n values: await resolveRelatedFieldValues({\n fields,\n values,\n contentSource,\n }),\n };\n}\n\n/** A related entry resolved to a public link; unresolvable refs stay plain text. */\nexport interface ContentRelatedLink {\n title: string;\n href: string;\n}\n\n// Resolves each `related` field's stored content paths into public links with\n// the target entry's title. Targets that aren't publicly visible (drafts in\n// production, archived, deleted) stay out entirely rather than rendering a\n// dead link.\nasync function resolveRelatedFieldValues({\n fields,\n values,\n contentSource,\n}: {\n fields: ContentField[];\n values: Record<string, unknown>;\n contentSource: ContentSource;\n}): Promise<Record<string, unknown>> {\n const relatedFields = fields.filter((field) => field.type === \"related\");\n if (relatedFields.length === 0) {\n return values;\n }\n const resolved = { ...values };\n for (const field of relatedFields) {\n const raw = resolved[field.id];\n if (!Array.isArray(raw)) {\n continue;\n }\n const links: ContentRelatedLink[] = [];\n for (const item of raw) {\n if (typeof item !== \"string\") {\n continue;\n }\n const ref = parseContentEntryRef(item);\n if (!ref) {\n continue;\n }\n const { collectionSlug: targetCollection, entrySlug: targetSlug } = ref;\n const collectionSchema = await readActiveCollectionSchema({\n collection: targetCollection,\n contentSource,\n });\n if (!collectionSchema) {\n continue;\n }\n // A shallow document read on purpose: resolving the target through the\n // full entry loader would recurse through ITS related fields, looping\n // on mutually-related entries. Only visibility and the title matter.\n const target = await readVisibleEntryDocument({\n collection: targetCollection,\n slug: targetSlug,\n contentSource,\n });\n if (!target) {\n continue;\n }\n links.push({\n title: readNonEmptyString(target.values.title) ?? targetSlug,\n href: `/${targetCollection}/${targetSlug}`,\n });\n }\n resolved[field.id] = links;\n }\n return resolved;\n}\n\nconst contentRelatedLinkSchema = z.object({\n title: z.string(),\n href: z.string(),\n});\n\nfunction isContentRelatedLink(value: unknown): value is ContentRelatedLink {\n return contentRelatedLinkSchema.safeParse(value).success;\n}\n\n// Sites commonly ship a CSS reset (Tailwind's preflight sets\n// `text-decoration: inherit`) that strips link underlines, leaving body links\n// visually indistinguishable from text. State it inline so a link reads as a\n// link on any site, the same way images carry their own layout styles.\nconst CONTENT_LINK_STYLE = { textDecoration: \"underline\" } as const;\n\nfunction SafeMarkdown({ source }: { source: string }) {\n return (\n <ReactMarkdown\n remarkPlugins={[remarkGfm]}\n skipHtml\n components={{\n a: ({ children, ...props }) => (\n <a {...props} style={CONTENT_LINK_STYLE}>\n {children}\n </a>\n ),\n }}\n >\n {source}\n </ReactMarkdown>\n );\n}\n\nfunction readNonEmptyString(value: unknown): string | null {\n const parsed = z.string().safeParse(value);\n return parsed.success && parsed.data.trim().length > 0 ? parsed.data : null;\n}\n\nfunction readSafeImageUrl(value: unknown): string | null {\n const url = readNonEmptyString(value);\n return url !== null && isSafeContentUrl(url, { image: true }) ? url : null;\n}\n\nfunction readIsoDate(value: unknown): string | null {\n const raw = readNonEmptyString(value);\n return raw !== null && !Number.isNaN(Date.parse(raw)) ? raw : null;\n}\n\nfunction readValidDate(value: unknown): Date | null {\n const iso = readIsoDate(value);\n return iso === null ? null : new Date(iso);\n}\n\nfunction serializeJsonLd(value: Record<string, unknown>): string {\n return JSON.stringify(value)\n .replaceAll(\"<\", \"\\\\u003c\")\n .replaceAll(\"\\u2028\", \"\\\\u2028\")\n .replaceAll(\"\\u2029\", \"\\\\u2029\");\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&apos;\");\n}\n\nasync function readTextFileOrNull(path: string) {\n try {\n return await readFile(path, \"utf8\");\n } catch {\n return null;\n }\n}\n\nasync function readDirectoryOrEmpty(path: string) {\n try {\n return await readdir(path, { withFileTypes: true });\n } catch {\n return [];\n }\n}\n\nfunction getContentSource({\n contentSource,\n contentDirectory,\n contentManifest,\n}: ContentOptions): ContentSource {\n return (\n contentSource ?? createContentSource({ contentDirectory, contentManifest })\n );\n}\n\nfunction getDefaultContentDirectory() {\n return join(process.cwd(), CONTENT_DIRNAME);\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
package/env.js.map DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../env.ts"],
4
- "sourcesContent": ["import { getCloudflareContext } from \"@opennextjs/cloudflare\";\nimport JSONC from \"tiny-jsonc\";\nimport { z } from \"zod\";\n\nimport { CanopyError } from \"./lib/canopy-error\";\n\ntype Env = {\n CANOPY_API_HOST: string;\n PROJECT_ID: string;\n\n // Base URL of analytics-fire (e.g. https://data.replo.app). Read server-side by\n // the consent action (`consent/consent-actions.ts`) to forward proof-of-consent\n // records. Optional: absent in environments where the audit pipeline isn't\n // wired (e.g. local dev), where consent recording simply no-ops.\n ANALYTICS_FIRE_API_URL?: string;\n\n // Base URL of the Replo first-party analytics pixel CDN (e.g.\n // https://pixel.replo.app). `<ReploFirstPartyPixel>` loads\n // `${REPLO_ANALYTICS_PIXEL_URL}/latest.js` into the page once analytics consent\n // is granted; the browser fetches it directly. Optional: absent when the pixel\n // isn't configured (e.g. lean local dev), where the pixel simply isn't injected.\n REPLO_ANALYTICS_PIXEL_URL?: string;\n\n CHECKOUT_PROVIDER?: \"shopify\" | \"stripe\" | \"none\";\n};\n\nconst envSchema = z.object({\n CANOPY_API_HOST: z.string().min(1),\n PROJECT_ID: z.uuid(),\n ANALYTICS_FIRE_API_URL: z.string().optional(),\n REPLO_ANALYTICS_PIXEL_URL: z.string().optional(),\n CHECKOUT_PROVIDER: z.enum([\"shopify\", \"stripe\", \"none\"]).optional(),\n});\n\nconst wranglerConfigSchema = z.object({ vars: envSchema });\n\nclass MissingWranglerConfigError extends CanopyError {}\n\n// NOTE (Cole, 2026-07-28, REPL-29501): outside the deployed worker, read wrangler.jsonc directly \u2014 concurrent workerd boots race on the shared .wrangler/state SQLite (SQLITE_BUSY).\nconst isDeployedWorker = () => {\n return (\n process.env.NODE_ENV === \"production\" &&\n process.env.BUILD_ENV !== \"build\" &&\n process.env.NEXT_PHASE !== \"phase-production-build\"\n );\n};\n\nexport const getEnv = async (): Promise<Env> => {\n if (isDeployedWorker()) {\n const { env } = await getCloudflareContext({ async: true });\n return envSchema.parse(env);\n }\n // NOTE (Cole, 2026-07-29, REPL-29501): client chunks bundle this module (integration-utils has client consumers). A static node:fs broke those chunks in 0.16.3, and a computed specifier fails Turbopack too (\"expression is too dynamic\") \u2014 only the ignore-annotated literal import compiles; postbuild.mts asserts the annotation survives into dist.\n const { promises: fs }: typeof import(\"node:fs\") = await import(\n /* turbopackIgnore: true */ /* webpackIgnore: true */ \"node:fs\"\n );\n let raw: string;\n try {\n raw = await fs.readFile(`${process.cwd()}/wrangler.jsonc`, \"utf8\");\n } catch (error) {\n throw new MissingWranglerConfigError({\n message:\n `wrangler.jsonc not found in ${process.cwd()}. It holds this site's Replo project config ` +\n `(project id, API host) and is required to run outside the deployed worker. ` +\n `If this clone predates the committed config, open the site in Replo once \u2014 the dev ` +\n `server regenerates and commits it \u2014 then git pull.`,\n cause: error,\n });\n }\n return wranglerConfigSchema.parse(JSONC.parse(raw)).vars;\n};\n"],
5
- "mappings": "AAAA,SAAS,4BAA4B;AACrC,OAAO,WAAW;AAClB,SAAS,SAAS;AAElB,SAAS,mBAAmB;AAsB5B,MAAM,YAAY,EAAE,OAAO;AAAA,EACzB,iBAAiB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACjC,YAAY,EAAE,KAAK;AAAA,EACnB,wBAAwB,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5C,2BAA2B,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/C,mBAAmB,EAAE,KAAK,CAAC,WAAW,UAAU,MAAM,CAAC,EAAE,SAAS;AACpE,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEzD,MAAM,mCAAmC,YAAY;AAAC;AAGtD,MAAM,mBAAmB,MAAM;AAC7B,SACE,QAAQ,IAAI,aAAa,gBACzB,QAAQ,IAAI,cAAc,WAC1B,QAAQ,IAAI,eAAe;AAE/B;AAEO,MAAM,SAAS,YAA0B;AAC9C,MAAI,iBAAiB,GAAG;AACtB,UAAM,EAAE,IAAI,IAAI,MAAM,qBAAqB,EAAE,OAAO,KAAK,CAAC;AAC1D,WAAO,UAAU,MAAM,GAAG;AAAA,EAC5B;AAEA,QAAM,EAAE,UAAU,GAAG,IAA8B,MAAM;AAAA;AAAA;AAAA,IACD;AAAA,EACxD;AACA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,GAAG,SAAS,GAAG,QAAQ,IAAI,CAAC,mBAAmB,MAAM;AAAA,EACnE,SAAS,OAAO;AACd,UAAM,IAAI,2BAA2B;AAAA,MACnC,SACE,+BAA+B,QAAQ,IAAI,CAAC;AAAA,MAI9C,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAO,qBAAqB,MAAM,MAAM,MAAM,GAAG,CAAC,EAAE;AACtD;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../gateways/canopy-api.ts"],
4
- "sourcesContent": ["import type { Cart } from \"../cart/cart-schema\";\n\nimport { z } from \"zod\";\n\nimport { cartSchema } from \"../cart/cart-schema\";\nimport { getEnv } from \"../env\";\nimport { CanopyError } from \"../lib/canopy-error\";\nimport { buildBaseUrl } from \"../lib/url-utils\";\n\ntype CanopyApiGatewayErrorConfig = {\n message: string;\n statusCode?: number;\n cause?: unknown;\n additionalData?: Record<string, unknown>;\n};\n\nexport class CanopyApiGatewayError extends CanopyError {\n statusCode: number;\n\n constructor(config: CanopyApiGatewayErrorConfig) {\n const resolvedStatusCode = config.statusCode ?? 500;\n super({\n message: config.message,\n cause: config.cause,\n additionalData: {\n ...config.additionalData,\n statusCode: resolvedStatusCode,\n },\n });\n this.statusCode = resolvedStatusCode;\n }\n}\n\nclass UnknownCanopyApiError extends CanopyApiGatewayError {\n constructor(config: Omit<CanopyApiGatewayErrorConfig, \"statusCode\">) {\n super({ ...config, statusCode: 502 });\n }\n}\n\nconst loaderInvokeResponseSchema = z.object({\n data: z.unknown(),\n meta: z\n .object({\n cache: z.object({\n hit: z.boolean(),\n ttlSeconds: z.number(),\n }),\n })\n .optional(),\n});\n\nconst reconciledCartLineSchema = z.discriminatedUnion(\"status\", [\n z.object({\n variantId: z.string(),\n status: z.literal(\"purchasable\"),\n unitPriceMinor: z.number().int(),\n compareAtPriceMinor: z.number().int().nullable(),\n currencyCode: z.string(),\n purchasableQuantity: z.number().int().min(0),\n }),\n z.object({\n variantId: z.string(),\n status: z.literal(\"unavailable\"),\n }),\n]);\n\nconst reconcileCartLinesResponseSchema = z.object({\n lines: z.array(reconciledCartLineSchema),\n});\n\nexport type ReconciledCartLine = z.infer<typeof reconciledCartLineSchema>;\n\n// The builder preview must render a product the merchant hasn't launched\n// yet; a published storefront must not. Only the sandbox dev server sets\n// IS_REPLO_SANDBOX, so it is what separates the two.\nexport function shouldIncludeInactiveProducts() {\n return Boolean(process.env.IS_REPLO_SANDBOX);\n}\n\nclass CanopyApiGateway {\n private apiHost: string;\n private baseUrl: string;\n constructor(apiHost: string) {\n this.apiHost = apiHost;\n this.baseUrl = buildBaseUrl(apiHost);\n }\n\n private static readonly REQUEST_TIMEOUT_MILLISECONDS = 10_000;\n\n static async setup() {\n const env = await getEnv();\n\n return new CanopyApiGateway(env.CANOPY_API_HOST);\n }\n\n private async _post(url: string, body: unknown) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: abortController.signal,\n });\n } catch (error) {\n let message = `Failed to POST to canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n private async _get(url: string) {\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n try {\n return await fetch(url, { signal: abortController.signal });\n } catch (error) {\n let message = `Failed to GET from canopy-api at ${url}: ${error}`;\n if (process.env.NODE_ENV !== \"production\") {\n message += `\\n\\napiHost: ${this.apiHost}\\nIf you're running locally, make sure the canopy-api cloudflare worker is running. You may need to restart the dev server.`;\n }\n throw new UnknownCanopyApiError({ message, cause: error });\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n async invokeLoader({\n loaderKey,\n args,\n }: {\n loaderKey: string;\n args: Record<string, unknown>;\n }) {\n const env = await getEnv();\n const response = await this._post(`${this.baseUrl}/api/v1/loaders/invoke`, {\n projectId: env.PROJECT_ID,\n loaderKey,\n args,\n includeInactiveProducts: shouldIncludeInactiveProducts(),\n });\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to invoke loader ${loaderKey}: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return loaderInvokeResponseSchema.parse(await response.json());\n }\n\n /**\n * The catalog's current answer for a set of cookie-cart lines: per line,\n * the live price and how many units are actually purchasable. Used to\n * reconcile the client-persisted cart, which otherwise never re-reads the\n * catalog facts it snapshotted at add-to-cart time.\n */\n async reconcileCartLines({\n lines,\n }: {\n lines: { variantId: string; quantity: number }[];\n }): Promise<ReconciledCartLine[]> {\n const env = await getEnv();\n const response = await this._post(\n `${this.baseUrl}/api/v1/product-catalog/reconcile-cart-lines`,\n { projectId: env.PROJECT_ID, lines },\n );\n\n if (!response.ok) {\n const errorBody = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Failed to reconcile cart lines: ${response.status} ${response.statusText}${errorBody ? ` \u2014 ${errorBody}` : \"\"}`,\n statusCode: response.status,\n });\n }\n\n return reconcileCartLinesResponseSchema.parse(await response.json()).lines;\n }\n\n // ---------------------------------------------------------------------------\n // Cart operations (canopy-api-backed ExternalCart)\n // ---------------------------------------------------------------------------\n\n private async _cartPost(\n path: string,\n body: Record<string, unknown>,\n ): Promise<Cart> {\n const env = await getEnv();\n const url = `${this.baseUrl}/api/v1/cart${path}`;\n\n const abortController = new AbortController();\n const timeoutId = setTimeout(() => {\n abortController.abort();\n }, CanopyApiGateway.REQUEST_TIMEOUT_MILLISECONDS);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ...body, projectId: env.PROJECT_ID }),\n signal: abortController.signal,\n });\n } catch (error) {\n throw new UnknownCanopyApiError({\n message: `Failed to POST cart operation to ${url}: ${error}`,\n cause: error,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n const text = await response.text().catch(() => \"\");\n throw new CanopyApiGatewayError({\n message: `Cart operation ${path} failed: ${response.status} ${response.statusText} \u2014 ${text}`,\n statusCode: response.status,\n });\n }\n\n // Legacy cast - may or may not be necessary\n // eslint-disable-next-line replo/no-as-cast\n const json = (await response.json()) as { data: unknown };\n return cartSchema.parse(json.data);\n }\n\n async cartGet({ cartId }: { cartId: string }): Promise<Cart | null> {\n try {\n return await this._cartPost(\"/get\", { cartId });\n } catch (error) {\n if (error instanceof CanopyApiGatewayError && error.statusCode === 404) {\n return null;\n }\n throw error;\n }\n }\n\n async cartCreate(options?: {\n lines?: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n discountCodes?: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/create\", {\n lines: options?.lines,\n discountCodes: options?.discountCodes,\n });\n }\n\n async cartAddLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n merchandiseId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/add-lines\", { cartId, lines });\n }\n\n async cartUpdateLines({\n cartId,\n lines,\n }: {\n cartId: string;\n lines: {\n lineId: string;\n quantity: number;\n attributes?: { key: string; value: string }[];\n sellingPlanId?: string | null;\n }[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-lines\", { cartId, lines });\n }\n\n async cartRemoveLines({\n cartId,\n lineIds,\n }: {\n cartId: string;\n lineIds: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/remove-lines\", { cartId, lineIds });\n }\n\n async cartUpdateDiscountCodes({\n cartId,\n discountCodes,\n }: {\n cartId: string;\n discountCodes: string[];\n }): Promise<Cart> {\n return this._cartPost(\"/update-discount-codes\", { cartId, discountCodes });\n }\n}\n\nexport async function getCanopyApiGateway() {\n return CanopyApiGateway.setup();\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../hooks/use-client-value.ts"],
4
- "sourcesContent": ["import * as React from \"react\";\n\n/**\n * NOTE (Max, 2025-09-15): This hook is to be used for values that aren't available on the server (such as window.location).\n *\n * This hook will use a dummy value both on the server & on hydration. Then,\n * after hydration, this useEffect will update the value to the actual value.\n *\n * The useEffect doesn't have any dependencies - it's designed to only run once. So if you expect your read() function to return\n * different values through the lifecycle of the component, you should not use this hook.\n */\n\nexport function useClientValue<ValueType>(\n readValue: () => ValueType,\n initialValue: ValueType,\n) {\n const [value, setValue] = React.useState<ValueType>(initialValue);\n\n // eslint-disable-next-line replo/no-use-effect -- (Noah, 2026-07-20): Legacy effect - may or may not be necessary\n React.useEffect(() => {\n setValue(readValue());\n }, []);\n return value;\n}\n"],
5
- "mappings": "AAAA,YAAY,WAAW;AAYhB,SAAS,eACd,WACA,cACA;AACA,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAoB,YAAY;AAGhE,QAAM,UAAU,MAAM;AACpB,aAAS,UAAU,CAAC;AAAA,EACtB,GAAG,CAAC,CAAC;AACL,SAAO;AACT;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../hooks/use-formatted-price.ts"],
4
- "sourcesContent": ["/**\n * Use useFormattedPrice() hook to display prices with proper currency symbols and formatting. Pass in a price in integer minor units (Stripe convention) and a currency code (e.g., 1999 and \"USD\") and it returns a formatted string like \"$19.99\".\n * @module\n */\nimport { formatAmount } from \"schemas/money\";\n\nimport { useClientValue } from \"./use-client-value\";\n\n/**\n * Use `useFormattedPrice` to format prices with proper currency and locale.\n * @returns A formatted price string based on the price and currency code. It\n * already takes into account the user's locale/browser.\n * @example\n * ```tsx\n * import { useFormattedPrice } from \"@replohq/sdk/hooks/use-formatted-price\";\n *\n * const originalPrice = 1999; // integer minor units\n * const originalPriceFormatted = useFormattedPrice(originalPrice, \"USD\");\n * // Display the formatted price\n * <p>{originalPriceFormatted}</p>;\n * ```\n */\nexport const useFormattedPrice = (\n price: number | null,\n currency: string | null,\n) => {\n const navigatorLanguage = useClientValue(\n () => window.navigator.language,\n \"en-US\",\n );\n\n // NOTE (Max, 2026-08-03): guard on null, not falsiness \u2014 0 is a real amount\n // (a free item), so `if (!price)` would render no price at all for it.\n if (price === null) {\n return null;\n }\n\n return formatAmount({\n amount: price,\n currencyCode: currency ?? \"USD\",\n locale: navigatorLanguage,\n });\n};\n"],
5
- "mappings": "AAIA,SAAS,oBAAoB;AAE7B,SAAS,sBAAsB;AAgBxB,MAAM,oBAAoB,CAC/B,OACA,aACG;AACH,QAAM,oBAAoB;AAAA,IACxB,MAAM,OAAO,UAAU;AAAA,IACvB;AAAA,EACF;AAIA,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,aAAa;AAAA,IAClB,QAAQ;AAAA,IACR,cAAc,YAAY;AAAA,IAC1B,QAAQ;AAAA,EACV,CAAC;AACH;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../lib/buildMetadata.ts"],
4
- "sourcesContent": ["type BuildMetadata = {\n packageName: string;\n version: string;\n branch: string | null;\n commit: string | null;\n builtAt: string | null;\n};\n\nexport const BUILD_METADATA: BuildMetadata = {\n packageName: \"@replohq/sdk\",\n version: \"0.0.0-dev\",\n branch: null,\n commit: null,\n builtAt: null,\n};\n"],
5
- "mappings": "AAQO,MAAM,iBAAgC;AAAA,EAC3C,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../lib/canopy-error.ts"],
4
- "sourcesContent": ["// NOTE (Ryan, 2026-07-23, REPL-27611): Local error base for the SDK's\n// published-page code. Mirrors the minimal surface of `schemas`' `ReploError`\n// (a `config` bag, a stable `id`, and cause chaining) that canopy's scoped\n// error classes actually use, so nothing shipped to a generated site imports\n// from `schemas`.\n\nexport type CanopyErrorConfig = {\n /**\n * Message of the error. If not provided, defaults to the error's class name.\n */\n message?: string;\n /**\n * Additional context about the error (ids, contextual variables, etc.),\n * surfaced when the error is logged.\n */\n additionalData?: Record<string, unknown>;\n /**\n * Original cause of the error, useful when catching and re-throwing with more\n * context.\n */\n cause?: unknown;\n};\n\n/**\n * Base error that all SDK errors inherit from. Deliberately minimal and\n * self-contained; it does not depend on the `schemas` workspace package.\n */\nexport abstract class CanopyError extends Error {\n config: CanopyErrorConfig;\n id: string;\n\n constructor(config: CanopyErrorConfig) {\n super(config.message);\n this.message = config.message ?? this.constructor.name;\n this.config = config;\n // crypto.randomUUID is natively available in browsers, Node, and the\n // Cloudflare Workers runtime \u2014 the three environments the SDK runs in.\n this.id = crypto.randomUUID();\n\n const cause = config.cause;\n if (cause) {\n this.cause = cause;\n if (cause instanceof CanopyError) {\n this.config.additionalData = {\n ...this.config.additionalData,\n ...cause.config.additionalData,\n };\n }\n }\n }\n}\n"],
5
- "mappings": "AA2BO,MAAe,oBAAoB,MAAM;AAAA,EAC9C;AAAA,EACA;AAAA,EAEA,YAAY,QAA2B;AACrC,UAAM,OAAO,OAAO;AACpB,SAAK,UAAU,OAAO,WAAW,KAAK,YAAY;AAClD,SAAK,SAAS;AAGd,SAAK,KAAK,OAAO,WAAW;AAE5B,UAAM,QAAQ,OAAO;AACrB,QAAI,OAAO;AACT,WAAK,QAAQ;AACb,UAAI,iBAAiB,aAAa;AAChC,aAAK,OAAO,iBAAiB;AAAA,UAC3B,GAAG,KAAK,OAAO;AAAA,UACf,GAAG,MAAM,OAAO;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../lib/external-navigation.ts"],
4
- "sourcesContent": ["/**\n * NOTE (Max, 2026-08-26): A site can be hosted somewhere that owns outbound\n * navigation. The Replo sandbox is the case that forces this: its preview runs\n * in an iframe that may not navigate away, and checkout providers refuse to be\n * framed, so the host opens the destination in a new tab instead. Sites that\n * are not hosted register nothing and navigate normally.\n *\n * The handler is read off a well-known `window` key rather than imported,\n * because the host that registers it (`@replohq/sandbox-runtime`) publishes\n * independently of this package and the two must not depend on each other.\n * @module\n */\nexport const EXTERNAL_NAVIGATION_HANDLER_KEY =\n \"__reploExternalNavigationHandler\";\n\n/** Returns true when the host took ownership of the navigation. */\nexport type ExternalNavigationHandler = (url: string) => boolean;\n\nexport function navigateExternal(url: string) {\n const handler = Reflect.get(window, EXTERNAL_NAVIGATION_HANDLER_KEY);\n if (typeof handler === \"function\" && handler(url) === true) {\n return;\n }\n\n window.location.href = url;\n}\n"],
5
- "mappings": "AAYO,MAAM,kCACX;AAKK,SAAS,iBAAiB,KAAa;AAC5C,QAAM,UAAU,QAAQ,IAAI,QAAQ,+BAA+B;AACnE,MAAI,OAAO,YAAY,cAAc,QAAQ,GAAG,MAAM,MAAM;AAC1D;AAAA,EACF;AAEA,SAAO,SAAS,OAAO;AACzB;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../lib/get-query-client.ts"],
4
- "sourcesContent": ["import {\n defaultShouldDehydrateQuery,\n isServer,\n QueryClient,\n} from \"@tanstack/react-query\";\n\nconst STALE_TIME_MILLISECONDS = 60 * 1000;\n\nconst makeQueryClient = () => {\n return new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: STALE_TIME_MILLISECONDS,\n },\n dehydrate: {\n shouldDehydrateQuery: (query) => {\n return (\n defaultShouldDehydrateQuery(query) ||\n query.state.status === \"pending\"\n );\n },\n },\n },\n });\n};\n\nlet browserQueryClient: QueryClient | undefined;\n\nexport function getQueryClient() {\n if (isServer) {\n return makeQueryClient();\n }\n\n if (!browserQueryClient) {\n browserQueryClient = makeQueryClient();\n }\n\n return browserQueryClient;\n}\n"],
5
- "mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,0BAA0B,KAAK;AAErC,MAAM,kBAAkB,MAAM;AAC5B,SAAO,IAAI,YAAY;AAAA,IACrB,gBAAgB;AAAA,MACd,SAAS;AAAA,QACP,WAAW;AAAA,MACb;AAAA,MACA,WAAW;AAAA,QACT,sBAAsB,CAAC,UAAU;AAC/B,iBACE,4BAA4B,KAAK,KACjC,MAAM,MAAM,WAAW;AAAA,QAE3B;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,IAAI;AAEG,SAAS,iBAAiB;AAC/B,MAAI,UAAU;AACZ,WAAO,gBAAgB;AAAA,EACzB;AAEA,MAAI,CAAC,oBAAoB;AACvB,yBAAqB,gBAAgB;AAAA,EACvC;AAEA,SAAO;AACT;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../lib/integration-utils.ts"],
4
- "sourcesContent": ["import { getEnv } from \"../env\";\n\nexport type CheckoutProvider = \"shopify\" | \"stripe\" | \"none\";\n\nexport async function getCheckoutProvider(): Promise<CheckoutProvider> {\n const env = await getEnv();\n return env.CHECKOUT_PROVIDER ?? \"none\";\n}\n"],
5
- "mappings": "AAAA,SAAS,cAAc;AAIvB,eAAsB,sBAAiD;AACrE,QAAM,MAAM,MAAM,OAAO;AACzB,SAAO,IAAI,qBAAqB;AAClC;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../lib/shopify-utils.ts"],
4
- "sourcesContent": ["/**\n * Utility functions for working with Shopify IDs and GIDs\n */\n\n/**\n * Converts a legacy resource ID to a Shopify GraphQL ID\n * @param id - The legacy resource ID (e.g., \"123456\")\n * @param type - The Shopify resource type\n * @returns The full Shopify GID (e.g., \"gid://shopify/ProductVariant/123456\")\n */\nexport function convertLegacyResourceIdToGraphQLId(\n id: string,\n type: \"Product\" | \"ProductVariant\",\n): string {\n return `gid://shopify/${type}/${id}`;\n}\n"],
5
- "mappings": "AAUO,SAAS,mCACd,IACA,MACQ;AACR,SAAO,iBAAiB,IAAI,IAAI,EAAE;AACpC;",
6
- "names": []
7
- }