@rebuy/rebuy 3.58.0 → 3.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -310,6 +310,10 @@ var OfferProductVariant = z3.object({
310
310
  price: z3.string(),
311
311
  title: z3.string()
312
312
  });
313
+ var OfferProductReviews = z3.object({
314
+ rating: z3.number().positive().max(5),
315
+ reviewCount: z3.number().int().nonnegative()
316
+ });
313
317
  var OfferProduct = z3.object({
314
318
  description: z3.string().optional(),
315
319
  discount: OfferProductDiscount.optional(),
@@ -325,6 +329,7 @@ var OfferProduct = z3.object({
325
329
  link: z3.string().optional(),
326
330
  metafields: z3.array(z3.object({ key: z3.string(), namespace: z3.string(), value: z3.string() })).optional(),
327
331
  options: z3.array(OfferProductOption),
332
+ reviews: OfferProductReviews.nullish().catch(void 0),
328
333
  sellingPlanGroups: z3.array(z3.object({ requiresSellingPlan: z3.boolean().optional() })).optional(),
329
334
  title: z3.string(),
330
335
  /** At least one variant — a zero-variant product renders an offer card with no addable merchandise (no Add CTA). `keepValidProduct` drops it with a `variants` drift signal rather than shipping a dead card. */
@@ -333,6 +338,27 @@ var OfferProduct = z3.object({
333
338
 
334
339
  // src/server/b2bAvailability.ts
335
340
  import { z as z4 } from "zod";
341
+
342
+ // src/server/shared.ts
343
+ var REBUY_ENGINE_HOST = "rebuyengine.com";
344
+ var unwrapData = (body) => body && typeof body === "object" && "data" in body ? body.data : body;
345
+ var isEmptyObject = (x) => x == null || typeof x === "object" && !Array.isArray(x) && Object.keys(x).length === 0;
346
+ var UpstreamError = class extends Error {
347
+ constructor(status, body, message) {
348
+ super(message ?? `Upstream returned ${status}`);
349
+ this.body = body;
350
+ this.name = "UpstreamError";
351
+ this.status = status;
352
+ }
353
+ };
354
+ var NotFoundError = class extends Error {
355
+ constructor(message) {
356
+ super(message);
357
+ this.name = "NotFoundError";
358
+ }
359
+ };
360
+
361
+ // src/server/b2bAvailability.ts
336
362
  var B2B_AVAILABILITY_PATH = "/admin/api/v1/shop/b2b-catalog-availability";
337
363
  var productGid = (product) => `gid://shopify/Product/${String(product.id)}`;
338
364
  var Verdict = z4.record(z4.string(), z4.boolean());
@@ -340,9 +366,18 @@ var B2bAvailabilityResponse = z4.object({
340
366
  availability: Verdict.optional(),
341
367
  data: z4.object({ availability: Verdict.optional() }).optional()
342
368
  }).transform(({ availability, data }) => availability ?? data?.availability ?? null);
369
+ var MAX_REASON_LENGTH = 120;
370
+ var failureReason = (error) => {
371
+ if (!(error instanceof Error)) return "unknown error";
372
+ const cut = error.message.indexOf(": ");
373
+ const label2 = (cut === -1 ? error.message : error.message.slice(0, cut)).slice(0, MAX_REASON_LENGTH);
374
+ if (!(error instanceof UpstreamError)) return label2 || error.name;
375
+ return label2 && label2 !== `Upstream returned ${error.status}` ? `upstream ${error.status}: ${label2}` : `upstream ${error.status}`;
376
+ };
343
377
  var filterByB2bAvailability = async (products, input, ctx) => {
344
378
  if (!input.companyLocationId || products.length === 0) return products;
345
379
  let verdict;
380
+ let reason = "unreadable verdict";
346
381
  try {
347
382
  const response = await ctx.fetchUpstream(
348
383
  B2B_AVAILABILITY_PATH,
@@ -353,18 +388,21 @@ var filterByB2bAvailability = async (products, input, ctx) => {
353
388
  productIds: products.map(productGid),
354
389
  shopifyShopDomain: input.shop
355
390
  },
391
+ /** admin-api verifies it (REB-25081); absent, the call is unauthenticated (log-only there until enforced). */
392
+ ...ctx.sessionToken && { headers: { Authorization: `Bearer ${ctx.sessionToken}` } },
356
393
  host: ctx.host,
357
394
  method: "POST"
358
395
  }
359
396
  );
360
397
  const parsed = B2bAvailabilityResponse.safeParse(response);
361
398
  verdict = parsed.success ? parsed.data : null;
362
- } catch {
399
+ } catch (error) {
363
400
  verdict = null;
401
+ reason = failureReason(error);
364
402
  }
365
403
  if (verdict === null) {
366
404
  ctx.reportDrift?.({
367
- detail: "b2b availability check failed \u2014 products served unfiltered",
405
+ detail: `b2b availability check failed (${reason}) \u2014 products served unfiltered`,
368
406
  kind: "b2bAvailability"
369
407
  });
370
408
  return products;
@@ -372,265 +410,48 @@ var filterByB2bAvailability = async (products, input, ctx) => {
372
410
  return products.filter((product) => verdict[productGid(product)] !== false);
373
411
  };
374
412
 
375
- // src/server/shared.ts
376
- var REBUY_ENGINE_HOST = "rebuyengine.com";
377
- var unwrapData = (body) => body && typeof body === "object" && "data" in body ? body.data : body;
378
- var isEmptyObject = (x) => x == null || typeof x === "object" && !Array.isArray(x) && Object.keys(x).length === 0;
379
- var UpstreamError = class extends Error {
380
- constructor(status, body, message) {
381
- super(message ?? `Upstream returned ${status}`);
382
- this.body = body;
383
- this.name = "UpstreamError";
384
- this.status = status;
385
- }
386
- };
387
- var NotFoundError = class extends Error {
388
- constructor(message) {
389
- super(message);
390
- this.name = "NotFoundError";
391
- }
392
- };
393
-
394
- // src/server/shopSubtotal.ts
395
- var shopCurrencyRate = (cart) => {
396
- const rate = cart.currencyRate ?? 1;
397
- return Number.isFinite(rate) && rate > 0 ? rate : 1;
398
- };
399
- var shopSubtotalCents = (cart) => Math.round((cart.subtotal - (cart.discountTotal ?? 0)) / shopCurrencyRate(cart));
400
- var toShopCents = (majorPresentment, rate) => Math.round(majorPresentment * 100 / rate);
413
+ // src/server/postPurchaseReviewData.ts
414
+ import { z as z17 } from "zod";
401
415
 
402
- // src/schema/cabShopConfig.ts
403
- import { z as z6 } from "zod";
416
+ // src/schema/postPurchaseProductReviews.ts
417
+ import { z as z12 } from "zod";
404
418
 
405
- // src/schema/driftedMarkets.ts
406
- var DRIFTED_MARKETS = Object.freeze({ enabled: false });
419
+ // src/schema/widgets/post-purchase-offer/postPurchaseSettings.ts
420
+ import { z as z11 } from "zod";
407
421
 
408
- // src/schema/smartCart.ts
422
+ // src/schema/widgets/post-purchase-offer/common.ts
423
+ import { replace, toNumber, toString } from "es-toolkit/compat";
409
424
  import { z as z5 } from "zod";
410
- var SmartCartTierProduct = z5.looseObject({
411
- handle: z5.string().optional(),
412
- id: z5.coerce.number().optional(),
413
- productId: z5.coerce.number().optional(),
414
- title: z5.string().optional(),
415
- variantIds: z5.array(z5.coerce.number()).optional()
416
- });
417
- var SmartCartTierExclusion = z5.looseObject({
418
- collectionIds: z5.array(z5.coerce.number()).optional().catch(void 0),
419
- productIds: z5.array(z5.coerce.number()).optional().catch(void 0),
420
- tags: z5.array(z5.string()).optional().catch(void 0),
421
- type: z5.string().optional().catch(void 0)
422
- });
423
- var SmartCartTier = z5.looseObject({
424
- allProducts: z5.array(SmartCartTierProduct).optional(),
425
- /** Merchant-set tier label (e.g. "Free gift", "$100 reward"), shown on the step row when enabled. */
426
- customTierLabel: z5.boolean().optional(),
427
- customTierLabelText: z5.string().optional(),
428
- decorativeAmountReachedLabel: z5.boolean().optional(),
429
- decorativeAmountReachedLabelText: z5.string().optional(),
430
- decorativeAmountRemainingLabel: z5.boolean().optional(),
431
- decorativeAmountRemainingLabelText: z5.string().optional(),
432
- /** Order-discount reward: `'fixedAmount'` (shop-currency dollars) or `'percentage'` (whole percent). */
433
- discountAmountFromTotal: z5.coerce.number().optional(),
434
- discountAmountReachedLabel: z5.boolean().optional(),
435
- discountAmountReachedLabelText: z5.string().optional(),
436
- discountAmountRemainingLabel: z5.boolean().optional(),
437
- discountAmountRemainingLabelText: z5.string().optional(),
438
- discountType: z5.string().optional(),
439
- /**
440
- * onsite `exclusions` + `exclusions_enabled` (REB-22305): per-tier rules the converter carries onto
441
- * `CABProgressTier.exclusions`. `.catch(undefined)` for the same blast-radius reason as `productTierImage`:
442
- * one drifted value must degrade to "no tier exclusions", never drop every progress bar for the shop.
443
- *
444
- * `exclusionsEnabled` is a STRICT boolean on purpose, even though failing open here changes the tier's math
445
- * (a non-boolean reads as disabled) rather than just an icon. onsite reads its sibling tier flags strictly —
446
- * `shouldFilterOutOfStockVariantsFromTier` is `tier?.filter_oos_variants === true`, and its `progressBar.ts`
447
- * types every tier flag as `boolean` — so a stored `1` / `"1"` / `"true"` is "disabled" on the onsite bar too.
448
- * A truthiness preprocess would make CAB exclude where onsite does not: the opposite parity break.
449
- */
450
- exclusions: z5.array(SmartCartTierExclusion).optional().catch(void 0),
451
- exclusionsEnabled: z5.boolean().optional().catch(void 0),
452
- filterOosVariants: z5.boolean().optional(),
453
- id: z5.string().optional(),
454
- /**
455
- * A `null`/absent minimum stays `undefined` (NOT coerced to `0`): a threshold-0 tier is always reached
456
- * and would auto-add its gift to every cart. onsite's `isTierDataValid` drops a non-numeric minimum for
457
- * exactly this reason; the converter's `renderableTiers` mirrors that (`Number.isFinite`). A literal `0`
458
- * is kept — onsite keeps it too. DELIBERATE DIVERGENCE from onsite for string minimums: onsite's
459
- * `isTierDataValid` is a strict `typeof === 'number'`, so it drops `'50'`; the converter instead coerces
460
- * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
461
- */
462
- minimum: z5.preprocess((value) => value == null ? void 0 : value, z5.coerce.number().optional()),
463
- presentmentCurrencies: z5.looseObject({ enabled: z5.boolean().optional() }).optional(),
464
- productAmountReachedLabel: z5.boolean().optional(),
465
- productAmountReachedLabelText: z5.string().optional(),
466
- productAmountRemainingLabel: z5.boolean().optional(),
467
- productAmountRemainingLabelText: z5.string().optional(),
468
- products: z5.array(SmartCartTierProduct).optional(),
469
- /**
470
- * onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
471
- * `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
472
- * SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
473
- * chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
474
- *
475
- * `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
476
- * previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
477
- * `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
478
- * `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
479
- * `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
480
- * converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
481
- */
482
- productTierElementSize: z5.string().optional().catch(void 0),
483
- /**
484
- * onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
485
- * slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
486
- * an image resolves). Per-tier; the client resolves the image (config carries only product IDs).
487
- *
488
- * `.catch(undefined)` is load-bearing. This key was previously unknown-and-ignored by the `looseObject`;
489
- * declaring it as a real boolean means a stored non-boolean (`1` / `"1"` / `"true"`) would now FAIL the
490
- * tier — and since `SmartCartConfig` is parsed `.nullish().catch(undefined)` (userConfig/cabShopConfig)
491
- * with no intermediate catch, one drifted flag would drop EVERY progress bar for the shop, not just the
492
- * flag. The converter only reads it for truthiness (`gift && tier.productTierImage`), so failing open to
493
- * `undefined` (bag-icon fallback, the documented default) is strictly safer than that blast radius.
494
- */
495
- productTierImage: z5.boolean().optional().catch(void 0),
496
- type: z5.string().optional(),
497
- /**
498
- * onsite's PER-TIER unit switch (`unlock_condition_type`): `'item_quantity'` makes `minimum` an
499
- * item count (checked before any money handling — no ×100, no FX, no presentment overrides,
500
- * mirroring onsite `getTierMinimum`); anything else (or absent) keeps the money semantics.
501
- */
502
- unlockConditionType: z5.string().optional()
503
- });
504
- var SmartCartBar = z5.looseObject({
505
- countryCodes: z5.array(z5.string()).optional(),
506
- /**
507
- * onsite `exclude_discount_in_subtotal`: measure progress against the PRE-discount subtotal so an
508
- * applied code/order discount doesn't lower progress. The client adds cart discount allocations back.
509
- */
510
- excludeDiscountInSubtotal: z5.boolean().optional(),
511
- /** Products excluded from the progress subtotal; the client subtracts their variants' cart cost. */
512
- excludedProducts: z5.array(z5.looseObject({ variantIds: z5.array(z5.coerce.number()).optional() })).optional(),
513
- freeShippingOnSubscription: z5.boolean().optional(),
514
- geolocationEnabled: z5.boolean().optional(),
515
- id: z5.string().optional(),
516
- language: z5.looseObject({
517
- freeShippingReached: z5.string().optional(),
518
- freeShippingRemaining: z5.string().optional(),
519
- giftAreaTitle: z5.string().optional()
520
- }).optional(),
521
- name: z5.string().optional(),
522
- tiers: z5.array(SmartCartTier).optional()
523
- });
524
- var SmartCartComponent = z5.looseObject({
525
- settings: z5.looseObject({ bars: z5.array(SmartCartBar).optional() }).optional(),
526
- type: z5.string().optional()
527
- });
528
- var SmartCartConfig = z5.looseObject({
529
- components: z5.array(SmartCartComponent).optional()
530
- });
425
+ var strictNumber = z5.union([
426
+ z5.number(),
427
+ z5.string().transform((value) => toNumber(replace(toString(value), /[^0-9.]/g, "")) || 0),
428
+ z5.unknown().transform(() => 0)
429
+ ]);
531
430
 
532
- // src/schema/cabShopConfig.ts
533
- var CabActiveExperiment = z6.object({
534
- data: z6.array(
535
- z6.object({
536
- elementId: z6.coerce.number(),
537
- id: z6.coerce.number(),
538
- traffic: z6.coerce.number()
539
- })
540
- ),
541
- id: z6.coerce.number(),
542
- placeholderId: z6.coerce.number().nullable(),
543
- type: z6.string()
544
- });
545
- var CabShopConfig = z6.object({
546
- /**
547
- * Per-element resilient: parse as `unknown[]` (non-array → `[]`), then keep only the experiments that
548
- * validate. A single drifted experiment drops itself instead of `.catch`-ing the WHOLE array to `[]` and
549
- * silently disabling every live A/B test fleet-wide.
550
- */
551
- activeExperiments: z6.array(z6.unknown()).catch([]).default([]).transform(
552
- (experiments) => experiments.flatMap((experiment) => {
553
- const result = CabActiveExperiment.safeParse(experiment);
554
- return result.success ? [result.data] : [];
555
- })
556
- ),
557
- /**
558
- * Only the two fields the smart-cart entitlement gate reads (`shortName`/`isActive`). Per-element
559
- * resilient like `activeExperiments`: a drifted package entry degrades to an inert `''`/false one that
560
- * matches nothing, so a real active `cart-merchandising` package still gates correctly and a sibling
561
- * drift never fails the shared config parse. Note the salvage is fail-CLOSED, not fail-open, for an
562
- * entitled v6 shop: a malformed whole array (⇒ `[]`) or an inerted `cart-merchandising` entry (e.g.
563
- * `is_active: 1` ⇒ inactive) makes the gate find no active package and drop every bar. That matches
564
- * React's "no proof of entitlement ⇒ render null", but it silently removes bars — so the server gate
565
- * ({@link file://../server/widgetSettings.ts} `fetchProgressBarContext`) emits a `shopConfig` drift
566
- * when it sees the inert-entry sentinel (`shortName === ''`). The whole-array `[]` case leaves no
567
- * sentinel and is not separately signalable here.
568
- */
569
- activePackages: z6.array(z6.object({ isActive: z6.boolean(), shortName: z6.string() }).catch({ isActive: false, shortName: "" })).catch([]).default([]),
570
- apiKey: z6.string(),
571
- /** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
572
- billingVersion: z6.string().nullable().catch(null).default(null),
573
- /**
574
- * The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
575
- * and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
576
- * wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
577
- * sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
578
- * Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
579
- * immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
580
- * string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
581
- * coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
582
- * as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
583
- * positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
584
- * to un-busted (origin-served — slower, never stale), never a parse fail.
585
- */
586
- cacheKey: z6.union([z6.number(), z6.string()]).transform((value) => Number(value)).pipe(z6.number().int().positive()).optional().catch(void 0),
587
- /**
588
- * The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
589
- * (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
590
- * converts to the buyer's currency exactly in a cross-decimal market. `.catch()` like its siblings: a
591
- * missing/renamed currency degrades to "unknown" (client falls back to buyer decimals), never a parse fail.
592
- */
593
- currency: z6.string().optional().catch(void 0),
594
- /**
595
- * The merchant's "Use Markets in Data Sources" toggle. The engine (CI2) never reads it on the serve
596
- * path, so the proxy honouring it before sending market context is what makes the setting mean
597
- * anything (REB-21688). Fail-CLOSED, unlike the fail-open siblings: a missing/null/drifted field reads
598
- * as markets OFF — market context silently dropped (engine serves shop-default market), never sent
599
- * against the merchant's setting. The OFF-reading salvages stay distinguishable: missing AND null are
600
- * the engine's normal absent vocabulary (`.nullish()`, like `monetize`/`billingVersion`) and read as
601
- * the plain default — not drift — while only a drifted SHAPE `.catch`es, to the {@link DRIFTED_MARKETS}
602
- * identity sentinel, so `fetchUserConfig` can emit a `shopConfig` drift for the one case that means an
603
- * engine rename/retype; otherwise a markets-enabled merchant degrading to shop-default pricing would
604
- * be indistinguishable from markets genuinely off. The missing-key case is EXPLICIT via `.default()`
605
- * (a factory, so parses never share a mutable object) rather than inferred from zod running an
606
- * optional key's transform on `undefined`; `null` flows past the default through `.nullish()` into the
607
- * transform. `Readonly` on the output type because the drifted salvage IS frozen — an assignment to
608
- * `markets.enabled` downstream would throw only for drifted shops in production, so surface direct
609
- * writes to tsc (a guard for the direct case only: TS doesn't check `readonly` in assignability, so
610
- * laundering through a mutable-shaped alias still compiles).
611
- * The inner `enabled` stays strict on purpose: `{ enabled: null }` is a genuine retype and drifting is
612
- * the correct answer (the editor-path `ShopConfig.markets` would hard-fail the same payload).
613
- */
614
- markets: z6.object({ enabled: z6.boolean() }).nullish().catch(DRIFTED_MARKETS).default(() => ({ enabled: false })).transform((markets) => markets ?? { enabled: false }),
615
- monetize: z6.object({ publisherKey: z6.string().nullable() }).nullish().catch(void 0),
616
- shopId: z6.number().optional().catch(void 0),
617
- /**
618
- * The merchant's display name ("Gift Basket Store") — the Monetize header copy reads it.
619
- * `.catch()` like its siblings: a missing/drifted name degrades the header to the
620
- * myshopify-domain slug (the pre-CAB React fallback), never a parse fail. An empty string is a
621
- * VALID parse — the slug fallback for `''` (and whitespace/tag-only names) lives in
622
- * composeOffer's `||` chain, not here.
623
- */
624
- shopName: z6.string().nullish().catch(void 0)
625
- });
626
- var CabUserConfig = z6.object({
627
- shop: CabShopConfig,
628
- smartCart: SmartCartConfig.nullish().catch(void 0)
629
- });
431
+ // src/schema/widgets/post-purchase-offer/constants.ts
432
+ import { z as z6 } from "zod";
433
+ var blockTypes = [
434
+ "callout_banner",
435
+ "image",
436
+ "image_content",
437
+ "pay_button",
438
+ "product_information",
439
+ "separator",
440
+ "text",
441
+ "timer"
442
+ ];
443
+ var blockType = z6.enum(blockTypes);
444
+ var BlockType = blockType.enum;
630
445
 
631
- // src/schema/widgets/checkout-and-beyond/image.ts
632
- import { v7 as uuidv713 } from "uuid";
633
- import { z as z28 } from "zod";
446
+ // src/schema/widgets/post-purchase-offer/postPurchaseBlocks.ts
447
+ import { get as get2, isEqual } from "es-toolkit/compat";
448
+ import { v4 as uuidv4 } from "uuid";
449
+ import { z as z10 } from "zod";
450
+
451
+ // src/schema/widgets/checkout-and-beyond/text.ts
452
+ import { forEach, isString as isString2 } from "es-toolkit/compat";
453
+ import { v7 as uuidv7 } from "uuid";
454
+ import { z as z9 } from "zod";
634
455
 
635
456
  // src/schema/widgets/checkout-and-beyond/common.ts
636
457
  var freezeEnum = (values2) => Object.freeze(Object.fromEntries(values2.map((v) => [v, v])));
@@ -784,6 +605,7 @@ var sectionTypes = [
784
605
  "variants"
785
606
  ];
786
607
  var SectionType = freezeEnum(sectionTypes);
608
+ var SALVAGED_SECTION_TYPE = "unknown";
787
609
  var spacings = ["none", "extraTight", "tight", "base", "loose", "extraLoose"];
788
610
  var Spacing = freezeEnum(spacings);
789
611
  var textAlignments = ["start", "center", "end"];
@@ -834,6 +656,1064 @@ var widgetTypes = [
834
656
  ];
835
657
  var WidgetType = freezeEnum(widgetTypes);
836
658
 
659
+ // src/schema/widgets/checkout-and-beyond/regex.ts
660
+ var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
661
+ var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
662
+ var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
663
+ var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
664
+
665
+ // src/schema/widgets/checkout-and-beyond/utils.ts
666
+ import { isPlainObject as isPlainObject2 } from "es-toolkit";
667
+ import { get, isString } from "es-toolkit/compat";
668
+
669
+ // src/transforms/convertNumericObjects.ts
670
+ import { isPlainObject } from "es-toolkit";
671
+ var ARRAY_INDEX_STRING = /^(?:0|[1-9]\d*)$/;
672
+ var hasOnlyNumericKeys = (input, requireCompleteRun) => {
673
+ const keys = Object.keys(input);
674
+ if (keys.length === 0) return false;
675
+ if (!keys.every((key) => ARRAY_INDEX_STRING.test(key) && Number.isSafeInteger(Number(key)))) return false;
676
+ if (!requireCompleteRun) return true;
677
+ const sorted = keys.map(Number).sort((a, b) => a - b);
678
+ return sorted.every((n, i) => n === i);
679
+ };
680
+ var numericObjectToArray = (input) => Object.entries(input).sort(([a], [b]) => Number(a) - Number(b)).map(([, value]) => value);
681
+ var convert = (input, requireCompleteRun) => {
682
+ if (Array.isArray(input)) return input.map((item) => convert(item, requireCompleteRun));
683
+ if (isPlainObject(input)) {
684
+ if (hasOnlyNumericKeys(input, requireCompleteRun)) {
685
+ return numericObjectToArray(input).map((item) => convert(item, requireCompleteRun));
686
+ }
687
+ const out = {};
688
+ for (const [key, value] of Object.entries(input)) out[key] = convert(value, requireCompleteRun);
689
+ return out;
690
+ }
691
+ return input;
692
+ };
693
+ var convertNumericObjects = (input) => convert(input, true);
694
+
695
+ // src/schema/widgets/checkout-and-beyond/utils.ts
696
+ var isHTML = (html = "") => {
697
+ if (typeof window !== "undefined") {
698
+ const parser = new DOMParser();
699
+ const doc = parser.parseFromString(html, "text/html");
700
+ const hasHTML = (nodeList) => Array.from(nodeList).some((node) => node.nodeType === 1);
701
+ return hasHTML(doc.body.childNodes) || hasHTML(doc.head.childNodes);
702
+ }
703
+ return html.search(HTML_TAGS_REGEX) !== -1;
704
+ };
705
+ var hasHTMLInDoc = (doc) => doc.content.some(({ content = [] }) => content.some(({ text: text2 }) => isHTML(text2)));
706
+ var checkForHTML = (input) => !(isString(input) && isHTML(input) || isPlainObject2(input) && hasHTMLInDoc(input));
707
+ var NO_HTML = { message: "HTML is not supported" };
708
+ var isSectionType = (sectionType) => (section) => get(section, "sectionType") === sectionType;
709
+ var isBannerType = isSectionType(SectionType.banner);
710
+ var isButtonType = isSectionType(SectionType.button);
711
+ var isCarouselType = isSectionType(SectionType.carousel);
712
+ var isCartLineType = isSectionType(SectionType.cartLine);
713
+ var isDataSourceType = isSectionType(
714
+ SectionType.dataSource
715
+ );
716
+ var isDiscountType = isSectionType(SectionType.discount);
717
+ var isImageType = isSectionType(SectionType.image);
718
+ var isLayoutType = isSectionType(SectionType.layout);
719
+ var isMonetizeType = isSectionType(SectionType.monetize);
720
+ var isOffersType = isSectionType(SectionType.offers);
721
+ var isProductType = isSectionType(SectionType.product);
722
+ var isProductsType = isSectionType(SectionType.products);
723
+ var isProgressBarType = isSectionType(
724
+ SectionType.progressBar
725
+ );
726
+ var isQuantityType = isSectionType(SectionType.quantity);
727
+ var isReviewsType = isSectionType(SectionType.reviews);
728
+ var isTextType = isSectionType(SectionType.text);
729
+ var isVariantsType = isSectionType(SectionType.variants);
730
+
731
+ // src/schema/widgets/checkout-and-beyond/variables.ts
732
+ import { z as z8 } from "zod";
733
+
734
+ // src/schema/widgets/checkout-and-beyond/rule.ts
735
+ import { z as z7 } from "zod";
736
+ var ruleTypes = [
737
+ "cart",
738
+ "cart_count",
739
+ "cart_item_count",
740
+ "cart_line_count",
741
+ "cart_subtotal",
742
+ "collection",
743
+ "customer",
744
+ "customer_order_count",
745
+ "customer_tag",
746
+ "customer_total_spent",
747
+ "date",
748
+ "product",
749
+ "product_tag",
750
+ "product_title",
751
+ "product_type",
752
+ "product_vendor"
753
+ ];
754
+ var ruleType = z7.enum(ruleTypes);
755
+ var RuleType = ruleType.enum;
756
+ var ruleOperators = [
757
+ "after",
758
+ "anything",
759
+ "before",
760
+ "contains",
761
+ "contains_any",
762
+ "does_not_contain",
763
+ "does_not_contain_any",
764
+ "equals",
765
+ "does_not_equal",
766
+ "greater_than",
767
+ "less_than"
768
+ ];
769
+ var ruleOperator = z7.enum(ruleOperators);
770
+ var RuleOperator = ruleOperator.enum;
771
+ var RuleCondition = z7.object({
772
+ operator: z7.enum(ruleOperators).default(RuleOperator.anything),
773
+ products: z7.union([z7.array(z7.unknown()), z7.strictObject({}).transform(() => [])]).default(() => []),
774
+ type: z7.enum(ruleTypes).default(RuleType.cart_subtotal),
775
+ value: z7.coerce.string().default("")
776
+ });
777
+ var RuleLogicBlock = z7.object({
778
+ rules: z7.array(RuleCondition).default(() => [])
779
+ });
780
+ var CABRulesetEntry = z7.object({
781
+ exitIfMatched: z7.boolean().default(true),
782
+ logic: z7.array(RuleLogicBlock).default(() => []),
783
+ output: z7.array(
784
+ z7.object({
785
+ key: z7.string().default("should_show"),
786
+ type: z7.literal("data_json").default("data_json"),
787
+ value: z7.string().default("true")
788
+ })
789
+ ).default(() => [{ key: "should_show", type: "data_json", value: "true" }])
790
+ });
791
+ var CABRuleset = z7.union([z7.array(CABRulesetEntry), z7.strictObject({}).transform(() => [])]).default(() => []);
792
+
793
+ // src/schema/widgets/checkout-and-beyond/variables.ts
794
+ var variableEffect = z8.enum(variableEffects);
795
+ var CABVariableSeed = z8.object({
796
+ dataSourceId: z8.number().nullable().default(null),
797
+ ruleset: CABRuleset
798
+ });
799
+ var CABBooleanVariable = z8.object({
800
+ /**
801
+ * Holds until the first seed verdict lands (and forever, for unseeded variables). Omitted, it derives
802
+ * from seededness — `false` with a `seed` (visibility is EARNED), `true` without — via the transform
803
+ * below; a cross-field default has no `.default()` spelling. An explicit value always wins.
804
+ */
805
+ defaultValue: z8.boolean().optional(),
806
+ name: z8.string().min(1),
807
+ seed: CABVariableSeed.optional(),
808
+ /** Defaulted so every stored declaration (all boolean before `text` existed) parses unchanged. */
809
+ type: z8.literal(VariableType.boolean).default(VariableType.boolean)
810
+ });
811
+ var CABTextVariable = z8.object({
812
+ defaultValue: z8.string(),
813
+ name: z8.string().min(1),
814
+ seed: z8.never().optional(),
815
+ type: z8.literal(VariableType.text)
816
+ });
817
+ var CABNumberVariable = z8.object({
818
+ defaultValue: z8.number(),
819
+ name: z8.string().min(1),
820
+ seed: z8.never().optional(),
821
+ type: z8.literal(VariableType.number)
822
+ });
823
+ var CABVariable = z8.union([CABTextVariable, CABNumberVariable, CABBooleanVariable]).transform(
824
+ (variable) => variable.type === VariableType.boolean ? { ...variable, defaultValue: variable.defaultValue ?? variable.seed === void 0 } : variable
825
+ );
826
+ var CABVariableEffect = z8.object({
827
+ effect: variableEffect.default(VariableEffect.toggle),
828
+ variable: z8.string().min(1)
829
+ });
830
+ var CABVisibility = z8.object({
831
+ variable: z8.string().min(1),
832
+ visibleWhen: z8.boolean().default(true)
833
+ });
834
+ var contentOperator = z8.enum(contentOperators);
835
+ var resultCountOperator = z8.enum(resultCountOperators);
836
+ var resultTagOperator = z8.enum(resultTagOperators);
837
+ var mutatorEvent = z8.enum(mutatorEvents);
838
+ var CABContentTrigger = z8.object({
839
+ operator: contentOperator.default(ContentOperator.is_empty),
840
+ type: z8.literal("content")
841
+ });
842
+ var CABResultCountTrigger = z8.object({
843
+ operator: resultCountOperator.default(ResultCountOperator.equals),
844
+ type: z8.literal("result_count"),
845
+ value: z8.number().default(0)
846
+ });
847
+ var CABResultTagTrigger = z8.object({
848
+ operator: resultTagOperator.default(ResultTagOperator.contains),
849
+ type: z8.literal("result_tag"),
850
+ value: z8.string().min(1)
851
+ });
852
+ var CABEventTrigger = z8.object({
853
+ event: mutatorEvent,
854
+ type: z8.literal("event")
855
+ });
856
+ var CABVariableTrigger = z8.discriminatedUnion("type", [
857
+ CABContentTrigger,
858
+ CABEventTrigger,
859
+ CABResultCountTrigger,
860
+ CABResultTagTrigger
861
+ ]);
862
+ var CABVariableMutator = z8.object({
863
+ effect: variableEffect.default(VariableEffect.set_false),
864
+ variable: z8.string().min(1),
865
+ when: CABVariableTrigger
866
+ });
867
+ var CABVariableFields = {
868
+ mutators: z8.array(CABVariableMutator).optional(),
869
+ variables: z8.array(CABVariable).refine((variables) => new Set(variables.map(({ name }) => name)).size === variables.length, {
870
+ message: "variable names must be unique within a section"
871
+ }).optional(),
872
+ visibility: CABVisibility.optional()
873
+ };
874
+
875
+ // src/schema/widgets/checkout-and-beyond/text.ts
876
+ var enumOrOmit = (...vals) => z9.string().transform((val) => vals.includes(val) ? val : void 0).optional();
877
+ var TiptapText = z9.object({
878
+ marks: z9.union([
879
+ z9.array(
880
+ z9.discriminatedUnion("type", [
881
+ z9.object({ type: z9.literal("bold") }),
882
+ z9.object({ type: z9.literal("italic") }),
883
+ z9.object({
884
+ attrs: z9.object({
885
+ class: z9.string().nullable(),
886
+ href: z9.union([
887
+ z9.url(),
888
+ z9.literal("#"),
889
+ z9.string().regex(DYNAMIC_TOKEN_REGEX),
890
+ z9.string().regex(ROOT_RELATIVE_PATH_REGEX)
891
+ ]),
892
+ rel: z9.string().default("noopener noreferrer nofollow"),
893
+ target: z9.string().default("_blank")
894
+ }),
895
+ type: z9.literal("link")
896
+ }),
897
+ z9.object({ type: z9.literal("strike") }),
898
+ z9.object({
899
+ attrs: z9.object({
900
+ color: enumOrOmit(...textColorNames).default(TextColorName.base),
901
+ fontSize: enumOrOmit(...textSizeNames).default(TextSizeName.base)
902
+ }),
903
+ type: z9.literal("textStyle")
904
+ })
905
+ ])
906
+ ),
907
+ z9.strictObject({}).transform(() => [])
908
+ ]).optional(),
909
+ text: z9.string().default(""),
910
+ type: z9.literal("text").default("text")
911
+ });
912
+ var TiptapParagraph = z9.object({
913
+ attrs: z9.object({
914
+ textAlign: enumOrOmit(...textAlignments)
915
+ }).default({ textAlign: TextAlignment.start }),
916
+ content: z9.union([z9.array(TiptapText), z9.strictObject({}).transform(() => [])]).default(() => []),
917
+ type: z9.literal("paragraph").default("paragraph")
918
+ });
919
+ var TiptapDocument = z9.object({
920
+ attrs: z9.object({
921
+ blockSpacing: enumOrOmit(...spacings)
922
+ }).default({ blockSpacing: Spacing.base }),
923
+ content: z9.union([z9.array(TiptapParagraph), z9.strictObject({}).transform(() => [])]).default(() => [TiptapParagraph.parse({})]),
924
+ type: z9.literal("doc").default("doc")
925
+ });
926
+ var CABTextTier = z9.object({
927
+ threshold: z9.number().int().nonnegative(),
928
+ thresholds: z9.record(z9.string(), z9.number().int().nonnegative()).default(() => ({})),
929
+ tierId: z9.string().optional(),
930
+ tierType: z9.enum(progressTierTypes).default(ProgressTierType.shipping)
931
+ });
932
+ var CABTextSection = z9.object({
933
+ ...CABVariableFields,
934
+ buttonField: z9.enum(buttonFields).optional(),
935
+ content: z9.record(z9.string(), z9.union([z9.string(), TiptapDocument]).refine(checkForHTML, NO_HTML).optional()).default({ en: TiptapDocument.parse({}) }).optional(),
936
+ name: z9.string().optional(),
937
+ optionId: z9.number().optional(),
938
+ sectionId: z9.uuid().default(() => uuidv7()),
939
+ sectionType: z9.literal(SectionType.text).default(SectionType.text),
940
+ tier: CABTextTier.optional()
941
+ }).superRefine(({ buttonField, content }, ctx) => {
942
+ if (buttonField === ButtonField.destinationUrl && content) {
943
+ forEach(content, (value, lang) => {
944
+ if (value && isString2(value)) {
945
+ if (DYNAMIC_TOKEN_REGEX.test(value)) return;
946
+ try {
947
+ new URL(value);
948
+ } catch {
949
+ ctx.addIssue({
950
+ code: "custom",
951
+ message: "Invalid URL",
952
+ path: ["content", lang]
953
+ });
954
+ }
955
+ }
956
+ });
957
+ }
958
+ });
959
+
960
+ // src/schema/widgets/post-purchase-offer/postPurchaseBlocks.ts
961
+ var TextAlignment2 = z10.union([z10.enum(["leading", "center", "trailing"]), z10.unknown().transform(() => "leading")]);
962
+ var TextSize = z10.union([
963
+ z10.enum(["xsmall", "small", "base", "default", "medium", "large", "xlarge"]),
964
+ z10.unknown().transform(() => "medium")
965
+ ]);
966
+ var CalloutBannerBlock = z10.object({
967
+ id: z10.string().optional().default(() => uuidv4()),
968
+ name: z10.string().optional(),
969
+ settings: z10.object({
970
+ superTitle: z10.union([z10.string(), TiptapDocument]).default(() => "Limited-time Offer"),
971
+ title: z10.union([z10.string(), TiptapDocument]).default(() => "Add this customer favorite to your order")
972
+ }),
973
+ styles: z10.object({
974
+ superTitleEmphasized: z10.boolean().default(false),
975
+ superTitleSize: TextSize.default("large"),
976
+ textAlignment: TextAlignment2.default("center"),
977
+ titleEmphasized: z10.boolean().default(true),
978
+ titleSize: TextSize.default("large")
979
+ }),
980
+ type: z10.literal(BlockType.callout_banner).default(BlockType.callout_banner)
981
+ });
982
+ var ImageBlock = z10.object({
983
+ id: z10.string().optional().default(() => uuidv4()),
984
+ name: z10.string().optional(),
985
+ settings: z10.union([
986
+ z10.object({
987
+ /** This isn't used in the post-purchase offer widget */
988
+ alt: z10.string().optional().default(""),
989
+ image: z10.union([z10.literal(""), z10.url()]).default(""),
990
+ imageSize: z10.number().default(20),
991
+ imageType: z10.enum(["URL", "UPLOAD"]).default("URL")
992
+ }),
993
+ z10.array(z10.never()).transform(() => ({}))
994
+ ]),
995
+ type: z10.literal(BlockType.image).default(BlockType.image)
996
+ });
997
+ var ImageContentBlock = z10.object({
998
+ id: z10.string().optional().default(() => uuidv4()),
999
+ name: z10.string().optional(),
1000
+ settings: z10.object({
1001
+ alt: z10.string().optional().default(""),
1002
+ description: z10.union([z10.string(), TiptapDocument, z10.null()]).default(
1003
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
1004
+ ),
1005
+ descriptionSize: TextSize.default("medium"),
1006
+ horizontalAlignment: TextAlignment2.default("leading"),
1007
+ image: z10.union([z10.literal(""), z10.url()]).default(""),
1008
+ imagePlacement: z10.enum(["bottom", "left", "right", "top"]).optional().default("left"),
1009
+ imageSize: z10.number().default(20),
1010
+ superTitle: z10.union([z10.string(), TiptapDocument]).default("Lorem Ipsum!"),
1011
+ superTitleSize: TextSize.default("medium"),
1012
+ /** This isn't configurable in the admin-nextjs editor */
1013
+ swapContent: z10.boolean().optional().default(false),
1014
+ title: z10.union([z10.string(), TiptapDocument]).nullable().default("Lorem ipsum dolor sit amet"),
1015
+ titleSize: TextSize.default("medium")
1016
+ }),
1017
+ type: z10.literal(BlockType.image_content).default(BlockType.image_content)
1018
+ });
1019
+ var PayButtonBlock = z10.object({
1020
+ id: z10.string().optional().default(() => uuidv4()),
1021
+ name: z10.string().optional(),
1022
+ settings: z10.union([
1023
+ z10.object({
1024
+ label: z10.string().default("Pay Now")
1025
+ }),
1026
+ z10.array(z10.never()).transform(() => ({
1027
+ label: "Pay Now"
1028
+ }))
1029
+ ]),
1030
+ styles: z10.union([
1031
+ z10.object({
1032
+ buttonWidth: z10.number().min(10).max(100).default(50)
1033
+ }),
1034
+ z10.array(z10.never()).transform(() => ({
1035
+ buttonWidth: 50
1036
+ }))
1037
+ ]),
1038
+ type: z10.literal(BlockType.pay_button).default(BlockType.pay_button)
1039
+ });
1040
+ var ProductInformationBlock = z10.object({
1041
+ /** This isn't configurable in the admin-nextjs editor */
1042
+ hideProductImage: z10.boolean().optional().default(false),
1043
+ id: z10.string().optional().default(() => uuidv4()),
1044
+ name: z10.string().optional(),
1045
+ type: z10.literal(BlockType.product_information).default(BlockType.product_information)
1046
+ });
1047
+ var SeparatorBlock = z10.object({
1048
+ id: z10.string().optional().default(() => uuidv4()),
1049
+ name: z10.string().optional(),
1050
+ type: z10.literal(BlockType.separator).default(BlockType.separator)
1051
+ });
1052
+ var TextBlock = z10.object({
1053
+ id: z10.string().optional().default(() => uuidv4()),
1054
+ name: z10.string().optional(),
1055
+ settings: z10.object({
1056
+ description: z10.union([z10.string(), TiptapDocument]).optional().default(
1057
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
1058
+ ),
1059
+ /** This isn't used in the post-purchase offer widget */
1060
+ descriptionSize: TextSize.default("medium"),
1061
+ horizontalAlignment: TextAlignment2.default("leading"),
1062
+ superTitle: z10.union([z10.string(), TiptapDocument]).default("Super Title"),
1063
+ /** This isn't used in the post-purchase offer widget */
1064
+ superTitleSize: TextSize.default("medium"),
1065
+ title: z10.union([z10.string(), TiptapDocument]).nullable().default("Lorem ipsum dolor sit amet."),
1066
+ /** This isn't used in the post-purchase offer widget */
1067
+ titleSize: TextSize.default("medium")
1068
+ }),
1069
+ type: z10.literal(BlockType.text).default(BlockType.text)
1070
+ });
1071
+ var TimerBlock = z10.object({
1072
+ id: z10.string().optional().default(() => uuidv4()),
1073
+ name: z10.string().optional(),
1074
+ settings: z10.object({
1075
+ durationMinutes: z10.number().min(0).default(10),
1076
+ durationSeconds: z10.union([z10.number().min(0), z10.unknown().transform(() => 0)]).default(0),
1077
+ endOffer: z10.boolean().default(true),
1078
+ title: z10.union([z10.string(), TiptapDocument]).default("Hurry \u2014 your special offer ends in:")
1079
+ }),
1080
+ styles: z10.object({
1081
+ textAlignment: TextAlignment2.default("center"),
1082
+ timerBackgroundColor: z10.enum(["info", "warning", "success", "critical"]).default("warning"),
1083
+ timerEmphasized: z10.union([z10.boolean(), z10.unknown().transform(() => true)]).default(true),
1084
+ timerSize: TextSize.default("medium"),
1085
+ timerWidth: z10.number().min(10).max(100).optional().default(100),
1086
+ titleEmphasized: z10.union([z10.boolean(), z10.unknown().transform(() => true)]).default(false),
1087
+ titleSize: TextSize.default("medium")
1088
+ }),
1089
+ type: z10.literal(BlockType.timer).default(BlockType.timer)
1090
+ });
1091
+ var PostPurchaseBlock = z10.discriminatedUnion("type", [
1092
+ CalloutBannerBlock,
1093
+ ImageBlock,
1094
+ ImageContentBlock,
1095
+ PayButtonBlock,
1096
+ ProductInformationBlock,
1097
+ SeparatorBlock,
1098
+ TextBlock,
1099
+ TimerBlock
1100
+ ]);
1101
+
1102
+ // src/schema/widgets/post-purchase-offer/postPurchaseSettings.ts
1103
+ var PostPurchaseSettings = z11.object({
1104
+ advance: z11.object({ removePartialOrder: z11.boolean().default(true) }).optional().default(() => ({ removePartialOrder: true })),
1105
+ blocks: z11.union([z11.array(PostPurchaseBlock), z11.unknown().transform(() => [])]).default(() => [
1106
+ PostPurchaseBlock.parse({ settings: {}, styles: {}, type: BlockType.timer }),
1107
+ PostPurchaseBlock.parse({ settings: {}, styles: {}, type: BlockType.callout_banner }),
1108
+ PostPurchaseBlock.parse({ type: BlockType.product_information })
1109
+ ]),
1110
+ columnLayout: z11.boolean().optional(),
1111
+ discount: z11.object({
1112
+ amount: strictNumber.default(30),
1113
+ shouldShowSavingText: z11.boolean().optional().default(true),
1114
+ type: z11.enum(["none", "fixed_amount", "percentage"]).default("none")
1115
+ }),
1116
+ discountedFrom: z11.enum(["price", "compare_at_price"]).optional().default("price"),
1117
+ endpoint: z11.string().optional(),
1118
+ integrations: z11.object({
1119
+ judgeme: z11.boolean().default(false),
1120
+ junip: z11.boolean().optional().default(false),
1121
+ klaviyo: z11.boolean().default(false),
1122
+ loox: z11.boolean().default(false),
1123
+ okendo: z11.boolean().default(false),
1124
+ opinew: z11.boolean().optional().default(false),
1125
+ reviewsio: z11.boolean().optional().default(false),
1126
+ stamped: z11.boolean().default(false),
1127
+ yotpo: z11.boolean().default(false)
1128
+ }),
1129
+ key: z11.string().optional(),
1130
+ language: z11.object({
1131
+ description: z11.union([z11.string(), z11.unknown().transform(() => "")]).default(""),
1132
+ descriptionEnabled: z11.boolean().optional().default(false),
1133
+ descriptionRte: z11.object({
1134
+ content: z11.array(z11.unknown()),
1135
+ type: z11.string()
1136
+ }).optional(),
1137
+ descriptionSpacing: z11.enum(["none", "xtight", "tight", "default", "loose", "xloose"]).optional(),
1138
+ displayCtaPrice: z11.boolean().default(true),
1139
+ global: z11.object({
1140
+ declineAll: z11.string().optional().default("Decline All"),
1141
+ declineThisOffer: z11.string().default("Decline this offer"),
1142
+ free: z11.string().optional().default("Free"),
1143
+ notAvailable: z11.string().optional().default("Not available"),
1144
+ /** Label for the one-time choice beside `subscriptionOption` in the purchase-type selector */
1145
+ oneTimePurchase: z11.string().optional().default("One-time Purchase"),
1146
+ payNow: z11.string().default("Pay Now"),
1147
+ payNowUpgradeToSubscription: z11.string().optional().default("Upgrade to Subscription"),
1148
+ quantity: z11.string().default("Quantity"),
1149
+ /** This isn't configurable in the admin-nextjs editor */
1150
+ recurringShipping: z11.string().optional().default("Recurring shipping"),
1151
+ recurringSubtotal: z11.string().default("Recurring subtotal"),
1152
+ reviewsPlural: z11.string().optional().default("Reviews"),
1153
+ reviewsSingular: z11.string().optional().default("Review"),
1154
+ save: z11.union([z11.string(), z11.unknown().transform(() => "Save")]).optional().default("Save"),
1155
+ shipping: z11.string().default("Shipping"),
1156
+ subscriptionOption: z11.string().optional().default("Subscribe and Save"),
1157
+ subtotal: z11.string().default("Subtotal"),
1158
+ taxes: z11.string().default("Taxes"),
1159
+ total: z11.string().default("Total")
1160
+ }).optional().default(() => ({
1161
+ declineAll: "Decline All",
1162
+ declineThisOffer: "Decline this offer",
1163
+ free: "Free",
1164
+ notAvailable: "Not available",
1165
+ oneTimePurchase: "One-time Purchase",
1166
+ payNow: "Pay Now",
1167
+ payNowUpgradeToSubscription: "Upgrade to Subscription",
1168
+ quantity: "Quantity",
1169
+ /** This isn't configurable in the admin-nextjs editor */
1170
+ recurringShipping: "Recurring shipping",
1171
+ recurringSubtotal: "Recurring subtotal",
1172
+ reviewsPlural: "Reviews",
1173
+ reviewsSingular: "Review",
1174
+ save: "Save",
1175
+ shipping: "Shipping",
1176
+ subscriptionOption: "Subscribe and Save",
1177
+ subtotal: "Subtotal",
1178
+ taxes: "Taxes",
1179
+ total: "Total"
1180
+ })),
1181
+ globalTranslation: z11.string().optional().default("english")
1182
+ }),
1183
+ metafields: z11.boolean().default(false),
1184
+ /** This isn't configurable in the admin-nextjs editor */
1185
+ offer: z11.object({
1186
+ count: z11.number().default(1)
1187
+ }),
1188
+ offersLayout: z11.object({
1189
+ itemsPerPage: z11.number().optional(),
1190
+ type: z11.enum(["list", "grid", "carousel"]).default("list")
1191
+ }).optional().default({
1192
+ type: "list"
1193
+ }),
1194
+ /** This isn't configurable in the admin-nextjs editor */
1195
+ previewMode: z11.boolean().default(false),
1196
+ productLimit: z11.number().optional().default(1),
1197
+ productOptions: z11.object({
1198
+ /** This isn't configurable in the admin-nextjs editor */
1199
+ hideOutOfStockVariants: z11.boolean().optional().default(false),
1200
+ matchVariant: z11.boolean().optional().default(true),
1201
+ showDeclineAll: z11.enum(["above", "below", "both", "none"]).optional(),
1202
+ showShipping: z11.boolean().optional().default(true),
1203
+ showSubtotal: z11.boolean().optional().default(true),
1204
+ showTaxes: z11.boolean().optional().default(true),
1205
+ showTotal: z11.boolean().optional().default(true),
1206
+ showVariantSelectors: z11.boolean().optional().default(true),
1207
+ variantSelectorDisplayMode: z11.enum(["separate", "single"]).optional().default("separate")
1208
+ }),
1209
+ productType: z11.union([z11.enum(["one-time", "subscription", "both"]), z11.literal("").transform(() => "one-time")]).default("one-time"),
1210
+ productTypeSelector: z11.enum(["dropdown", "radio"]).optional().default("radio"),
1211
+ quantityInputs: z11.object({
1212
+ defaultQuantity: z11.union([z11.number(), z11.null().transform(() => 1)]).default(1),
1213
+ enabled: z11.boolean().default(true),
1214
+ maxValue: z11.union([z11.number(), z11.null().transform(() => 15)]).default(15),
1215
+ minValue: z11.union([z11.number().min(0), z11.unknown().transform(() => 0)]).default(1)
1216
+ }),
1217
+ shipping: z11.object({
1218
+ amount: strictNumber.default(10),
1219
+ /** This isn't configurable in the admin-nextjs editor */
1220
+ enabled: z11.boolean().default(true),
1221
+ title: z11.string().optional().default("Shipping Fees"),
1222
+ type: z11.enum(["free", "fixed_amount"]).default("free")
1223
+ }),
1224
+ styles: z11.object({
1225
+ starColor: z11.string().optional().default("#fadb14"),
1226
+ starSize: z11.union([z11.enum(["small", "medium", "large"]), z11.unknown().transform(() => "large")]).optional().default("large"),
1227
+ starStrokeColor: z11.string().optional().default("#fadb14"),
1228
+ strokeWidth: z11.string().optional().default("1")
1229
+ }).optional(),
1230
+ subImages: z11.number().nullish().default(8),
1231
+ subscription: z11.object({
1232
+ discount: z11.object({
1233
+ amount: strictNumber.default(30),
1234
+ shouldShowSavingText: z11.boolean().optional().default(true),
1235
+ type: z11.enum(["none", "fixed_amount", "percentage"]).default("none")
1236
+ }),
1237
+ shipping: z11.object({
1238
+ amount: strictNumber.default(1),
1239
+ /** This isn't configurable in the admin-nextjs editor */
1240
+ enabled: z11.boolean().default(true),
1241
+ /** This isn't configurable in the admin-nextjs editor */
1242
+ recurringAmount: strictNumber.default(2),
1243
+ shippingTitle: z11.string().default("Shipping Fees"),
1244
+ type: z11.enum(["free", "fixed_amount"]).default("free")
1245
+ })
1246
+ }).optional().default({
1247
+ discount: {
1248
+ amount: 30,
1249
+ shouldShowSavingText: true,
1250
+ type: "none"
1251
+ },
1252
+ shipping: {
1253
+ amount: 1,
1254
+ /** This isn't configurable in the admin-nextjs editor */
1255
+ enabled: true,
1256
+ /** This isn't configurable in the admin-nextjs editor */
1257
+ recurringAmount: 2,
1258
+ shippingTitle: "Shipping Fees",
1259
+ type: "free"
1260
+ }
1261
+ }),
1262
+ subscriptionIsDefault: z11.boolean().optional(),
1263
+ /** This isn't configurable in the admin-nextjs editor */
1264
+ template: z11.string().optional().default(""),
1265
+ type: z11.literal("shopify_post_purchase_extension").default("shopify_post_purchase_extension"),
1266
+ upgradeToSubscription: z11.object({
1267
+ acceptButtonText: z11.string().optional().default("Upgrade"),
1268
+ allow: z11.boolean().optional().default(false),
1269
+ emailRefundNotice: z11.boolean().optional().default(false),
1270
+ errorBannerText: z11.string().optional().default("This subscription cannot be added - limit one per order")
1271
+ }).optional().default(() => ({
1272
+ acceptButtonText: "Upgrade",
1273
+ allow: false,
1274
+ emailRefundNotice: false,
1275
+ errorBannerText: "This subscription cannot be added - limit one per order"
1276
+ }))
1277
+ });
1278
+
1279
+ // src/schema/postPurchaseProductReviews.ts
1280
+ var decimal = z12.union([
1281
+ z12.number(),
1282
+ z12.string().regex(/^\d+(?:\.\d+)?$/).transform(Number)
1283
+ ]);
1284
+ var PostPurchaseCommonReviewRating = z12.object({ value: decimal.pipe(z12.number().min(0).max(5)) });
1285
+ var PostPurchaseKlaviyoReviewRating = PostPurchaseCommonReviewRating.extend({
1286
+ // Legacy validates key presence, then displays the raw value on five stars without rescaling.
1287
+ scale_max: z12.unknown().nonoptional(),
1288
+ scale_min: z12.unknown().nonoptional()
1289
+ });
1290
+ var PostPurchaseReviewCount = z12.string().regex(/^\d+(?:\.\d+)?$/).transform(Number).pipe(z12.number().int().nonnegative());
1291
+ var PostPurchaseReviewStyles = PostPurchaseSettings.shape.styles.unwrap().extend({
1292
+ strokeWidth: decimal.pipe(z12.number().nonnegative()).default(1)
1293
+ }).prefault({});
1294
+ var PostPurchaseProductReviews = z12.object({
1295
+ color: z12.string(),
1296
+ rating: z12.number().positive().max(5),
1297
+ reviewCount: z12.number().int().nonnegative(),
1298
+ reviewsPlural: z12.string(),
1299
+ reviewsSingular: z12.string(),
1300
+ size: z12.enum(["small", "medium", "large"]),
1301
+ strokeColor: z12.string(),
1302
+ strokeWidth: z12.number().nonnegative()
1303
+ });
1304
+
1305
+ // src/server/postPurchaseJudgeMeReviews.ts
1306
+ import { z as z14 } from "zod";
1307
+
1308
+ // src/server/postPurchaseReviewFields.ts
1309
+ import { z as z13 } from "zod";
1310
+ var Field = z13.object({ key: z13.string(), namespace: z13.string(), value: z13.unknown().optional() });
1311
+ var reviewFields = (raw) => {
1312
+ const fields4 = z13.array(z13.unknown()).parse(raw).flatMap((field) => {
1313
+ const parsed = Field.safeParse(field);
1314
+ return parsed.success ? [parsed.data] : [];
1315
+ });
1316
+ return (namespace, key) => fields4.find((field) => field.namespace === namespace && field.key === key);
1317
+ };
1318
+ var ReviewFieldError = class extends Error {
1319
+ constructor(field) {
1320
+ super(`Unreadable review field ${field}`);
1321
+ this.field = field;
1322
+ }
1323
+ };
1324
+ var readReviewField = (field, read) => {
1325
+ try {
1326
+ return read(field.value);
1327
+ } catch {
1328
+ throw new ReviewFieldError(`${field.namespace}.${field.key}`);
1329
+ }
1330
+ };
1331
+
1332
+ // src/server/postPurchaseJudgeMeReviews.ts
1333
+ var readJudgeMeReviews = (find) => {
1334
+ const field = find("judgeme", "badge");
1335
+ if (!field) return null;
1336
+ return readReviewField(field, (value) => {
1337
+ const html = z14.string().parse(value);
1338
+ const rating = html.match(/data-average-rating=['"](\d+(?:\.\d+)?)['"]/);
1339
+ const count = html.match(/data-number-of-reviews=['"](\d+)['"]/);
1340
+ if (!rating || !count) throw new Error("Unreadable Judge.me attributes");
1341
+ return {
1342
+ rating: PostPurchaseCommonReviewRating.parse({ value: rating[1] }).value,
1343
+ reviewCount: PostPurchaseReviewCount.parse(count[1])
1344
+ };
1345
+ });
1346
+ };
1347
+
1348
+ // src/server/postPurchaseOkendoReviews.ts
1349
+ import { z as z15 } from "zod";
1350
+ var Summary = z15.object({
1351
+ reviewAverageValue: PostPurchaseCommonReviewRating.shape.value,
1352
+ reviewCount: z15.union([PostPurchaseProductReviews.shape.reviewCount, PostPurchaseReviewCount])
1353
+ });
1354
+ var readOkendoReviews = (find) => {
1355
+ const field = find("okendo", "summaryData");
1356
+ if (!field) return null;
1357
+ const summary = readReviewField(field, (value) => Summary.parse(JSON.parse(z15.string().parse(value))));
1358
+ return { rating: summary.reviewAverageValue, reviewCount: summary.reviewCount };
1359
+ };
1360
+
1361
+ // src/server/postPurchaseScalarReviews.ts
1362
+ import { z as z16 } from "zod";
1363
+ var readScalarReviews = (find, namespace, ratingKey, countKey) => {
1364
+ const rating = find(namespace, ratingKey);
1365
+ const count = find(namespace, countKey);
1366
+ if (!rating || !count) return null;
1367
+ return {
1368
+ rating: readReviewField(
1369
+ rating,
1370
+ (value) => PostPurchaseCommonReviewRating.parse({ value: z16.string().parse(value) }).value
1371
+ ),
1372
+ reviewCount: readReviewField(count, (value) => PostPurchaseReviewCount.parse(value))
1373
+ };
1374
+ };
1375
+
1376
+ // src/server/postPurchaseReviewData.ts
1377
+ var readStandardReviews = (find) => {
1378
+ const rating = find("reviews", "rating");
1379
+ const count = find("reviews", "rating_count");
1380
+ if (!rating || !count) return null;
1381
+ return {
1382
+ rating: readReviewField(rating, (raw) => {
1383
+ const value = z17.string().parse(raw);
1384
+ return PostPurchaseCommonReviewRating.parse(/^\d+(?:\.\d+)?$/.test(value) ? { value } : JSON.parse(value)).value;
1385
+ }),
1386
+ reviewCount: readReviewField(count, (value) => PostPurchaseReviewCount.parse(value))
1387
+ };
1388
+ };
1389
+ var readPostPurchaseReviewData = (provider, find) => {
1390
+ if (provider === "yotpo") return readScalarReviews(find, "yotpo", "reviews_average", "reviews_count");
1391
+ if (provider === "stamped") return readScalarReviews(find, "stamped", "reviews_average", "reviews_count");
1392
+ if (provider === "judgeme") return readJudgeMeReviews(find);
1393
+ if (provider === "okendo") return readOkendoReviews(find);
1394
+ if (provider === "opinew") return readScalarReviews(find, "opinew_metafields", "reviews_average", "reviews_count");
1395
+ if (provider === "reviewsio")
1396
+ return readScalarReviews(find, "reviewscouk", "rating", "total") ?? readStandardReviews(find);
1397
+ if (provider === "loox") return readScalarReviews(find, "loox", "avg_rating", "num_reviews");
1398
+ if (provider === "junip") return readScalarReviews(find, "junip", "rating_average", "rating_count");
1399
+ const rating = find("reviews", "rating");
1400
+ const count = find("reviews", "rating_count");
1401
+ if (!rating || !count) return null;
1402
+ return {
1403
+ rating: readReviewField(
1404
+ rating,
1405
+ (value) => PostPurchaseKlaviyoReviewRating.parse(JSON.parse(z17.string().parse(value))).value
1406
+ ),
1407
+ reviewCount: readReviewField(count, (value) => PostPurchaseReviewCount.parse(value))
1408
+ };
1409
+ };
1410
+
1411
+ // src/server/postPurchaseReviewProvider.ts
1412
+ import { z as z18 } from "zod";
1413
+ var IntegrationInput = z18.record(z18.string(), z18.unknown()).prefault({});
1414
+ var precedence = [
1415
+ "klaviyo",
1416
+ "judgeme",
1417
+ "junip",
1418
+ "loox",
1419
+ "okendo",
1420
+ "opinew",
1421
+ "reviewsio",
1422
+ "stamped",
1423
+ "yotpo"
1424
+ ];
1425
+ var ReviewsIoFlag = z18.preprocess((value) => {
1426
+ if (typeof value === "string") {
1427
+ const flag = value.toLowerCase();
1428
+ if (flag === "true" || flag === "yes") return true;
1429
+ if (flag === "false" || flag === "no") return false;
1430
+ }
1431
+ return value;
1432
+ }, PostPurchaseSettings.shape.integrations.shape.reviewsio);
1433
+ var selectPostPurchaseReviewProvider = (raw) => {
1434
+ const integrations = IntegrationInput.parse(raw);
1435
+ for (const provider of precedence) {
1436
+ const schema = provider === "reviewsio" ? ReviewsIoFlag : PostPurchaseSettings.shape.integrations.shape[provider];
1437
+ if (schema.parse(integrations[provider])) return provider;
1438
+ }
1439
+ return null;
1440
+ };
1441
+
1442
+ // src/server/offerProductReviews.ts
1443
+ var readOrReport = (read, productId2, report) => {
1444
+ try {
1445
+ return read();
1446
+ } catch (error) {
1447
+ if (!(error instanceof ReviewFieldError)) throw error;
1448
+ report?.({
1449
+ detail: "Unreadable offer product reviews metafield",
1450
+ field: error.field,
1451
+ kind: "reviews",
1452
+ productId: productId2
1453
+ });
1454
+ return null;
1455
+ }
1456
+ };
1457
+ var readReviews = (provider, find, productId2, report) => (provider === "klaviyo" ? null : readOrReport(() => readPostPurchaseReviewData(provider, find), productId2, report)) ?? readOrReport(() => readStandardReviews(find), productId2, report);
1458
+ var projectOfferProductReviews = (integrations, { id: productId2, metafields }, report) => {
1459
+ let provider;
1460
+ try {
1461
+ provider = selectPostPurchaseReviewProvider(integrations);
1462
+ } catch {
1463
+ return void 0;
1464
+ }
1465
+ if (!provider) return void 0;
1466
+ try {
1467
+ if (!Array.isArray(metafields)) return null;
1468
+ const data = readReviews(provider, reviewFields(metafields), productId2, report);
1469
+ return data && data.rating > 0 ? data : null;
1470
+ } catch {
1471
+ report?.({ detail: "Unreadable offer product reviews", kind: "product" });
1472
+ return null;
1473
+ }
1474
+ };
1475
+
1476
+ // src/server/shopSubtotal.ts
1477
+ var shopCurrencyRate = (cart) => {
1478
+ const rate = cart.currencyRate ?? 1;
1479
+ return Number.isFinite(rate) && rate > 0 ? rate : 1;
1480
+ };
1481
+ var shopSubtotalCents = (cart) => Math.round((cart.subtotal - (cart.discountTotal ?? 0)) / shopCurrencyRate(cart));
1482
+ var toShopCents = (majorPresentment, rate) => Math.round(majorPresentment * 100 / rate);
1483
+
1484
+ // src/schema/cabShopConfig.ts
1485
+ import { z as z20 } from "zod";
1486
+
1487
+ // src/schema/driftedMarkets.ts
1488
+ var DRIFTED_MARKETS = Object.freeze({ enabled: false });
1489
+
1490
+ // src/schema/smartCart.ts
1491
+ import { z as z19 } from "zod";
1492
+ var SmartCartTierProduct = z19.looseObject({
1493
+ handle: z19.string().optional(),
1494
+ id: z19.coerce.number().optional(),
1495
+ productId: z19.coerce.number().optional(),
1496
+ title: z19.string().optional(),
1497
+ variantIds: z19.array(z19.coerce.number()).optional()
1498
+ });
1499
+ var SmartCartTierExclusion = z19.looseObject({
1500
+ collectionIds: z19.array(z19.coerce.number()).optional().catch(void 0),
1501
+ productIds: z19.array(z19.coerce.number()).optional().catch(void 0),
1502
+ tags: z19.array(z19.string()).optional().catch(void 0),
1503
+ type: z19.string().optional().catch(void 0)
1504
+ });
1505
+ var SmartCartTier = z19.looseObject({
1506
+ allProducts: z19.array(SmartCartTierProduct).optional(),
1507
+ /** Merchant-set tier label (e.g. "Free gift", "$100 reward"), shown on the step row when enabled. */
1508
+ customTierLabel: z19.boolean().optional(),
1509
+ customTierLabelText: z19.string().optional(),
1510
+ decorativeAmountReachedLabel: z19.boolean().optional(),
1511
+ decorativeAmountReachedLabelText: z19.string().optional(),
1512
+ decorativeAmountRemainingLabel: z19.boolean().optional(),
1513
+ decorativeAmountRemainingLabelText: z19.string().optional(),
1514
+ /** Order-discount reward: `'fixedAmount'` (shop-currency dollars) or `'percentage'` (whole percent). */
1515
+ discountAmountFromTotal: z19.coerce.number().optional(),
1516
+ discountAmountReachedLabel: z19.boolean().optional(),
1517
+ discountAmountReachedLabelText: z19.string().optional(),
1518
+ discountAmountRemainingLabel: z19.boolean().optional(),
1519
+ discountAmountRemainingLabelText: z19.string().optional(),
1520
+ discountType: z19.string().optional(),
1521
+ /**
1522
+ * onsite `exclusions` + `exclusions_enabled` (REB-22305): per-tier rules the converter carries onto
1523
+ * `CABProgressTier.exclusions`. `.catch(undefined)` for the same blast-radius reason as `productTierImage`:
1524
+ * one drifted value must degrade to "no tier exclusions", never drop every progress bar for the shop.
1525
+ *
1526
+ * `exclusionsEnabled` is a STRICT boolean on purpose, even though failing open here changes the tier's math
1527
+ * (a non-boolean reads as disabled) rather than just an icon. onsite reads its sibling tier flags strictly —
1528
+ * `shouldFilterOutOfStockVariantsFromTier` is `tier?.filter_oos_variants === true`, and its `progressBar.ts`
1529
+ * types every tier flag as `boolean` — so a stored `1` / `"1"` / `"true"` is "disabled" on the onsite bar too.
1530
+ * A truthiness preprocess would make CAB exclude where onsite does not: the opposite parity break.
1531
+ */
1532
+ exclusions: z19.array(SmartCartTierExclusion).optional().catch(void 0),
1533
+ exclusionsEnabled: z19.boolean().optional().catch(void 0),
1534
+ filterOosVariants: z19.boolean().optional(),
1535
+ id: z19.string().optional(),
1536
+ /**
1537
+ * A `null`/absent minimum stays `undefined` (NOT coerced to `0`): a threshold-0 tier is always reached
1538
+ * and would auto-add its gift to every cart. onsite's `isTierDataValid` drops a non-numeric minimum for
1539
+ * exactly this reason; the converter's `renderableTiers` mirrors that (`Number.isFinite`). A literal `0`
1540
+ * is kept — onsite keeps it too. DELIBERATE DIVERGENCE from onsite for string minimums: onsite's
1541
+ * `isTierDataValid` is a strict `typeof === 'number'`, so it drops `'50'`; the converter instead coerces
1542
+ * `'50' → 50` and keeps the tier (a numeric-string minimum is a valid threshold, not a data defect).
1543
+ */
1544
+ minimum: z19.preprocess((value) => value == null ? void 0 : value, z19.coerce.number().optional()),
1545
+ presentmentCurrencies: z19.looseObject({ enabled: z19.boolean().optional() }).optional(),
1546
+ productAmountReachedLabel: z19.boolean().optional(),
1547
+ productAmountReachedLabelText: z19.string().optional(),
1548
+ productAmountRemainingLabel: z19.boolean().optional(),
1549
+ productAmountRemainingLabelText: z19.string().optional(),
1550
+ products: z19.array(SmartCartTierProduct).optional(),
1551
+ /**
1552
+ * onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
1553
+ * `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
1554
+ * SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
1555
+ * chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
1556
+ *
1557
+ * `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
1558
+ * previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
1559
+ * `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
1560
+ * `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
1561
+ * `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
1562
+ * converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
1563
+ */
1564
+ productTierElementSize: z19.string().optional().catch(void 0),
1565
+ /**
1566
+ * onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
1567
+ * slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
1568
+ * an image resolves). Per-tier; the client resolves the image (config carries only product IDs).
1569
+ *
1570
+ * `.catch(undefined)` is load-bearing. This key was previously unknown-and-ignored by the `looseObject`;
1571
+ * declaring it as a real boolean means a stored non-boolean (`1` / `"1"` / `"true"`) would now FAIL the
1572
+ * tier — and since `SmartCartConfig` is parsed `.nullish().catch(undefined)` (userConfig/cabShopConfig)
1573
+ * with no intermediate catch, one drifted flag would drop EVERY progress bar for the shop, not just the
1574
+ * flag. The converter only reads it for truthiness (`gift && tier.productTierImage`), so failing open to
1575
+ * `undefined` (bag-icon fallback, the documented default) is strictly safer than that blast radius.
1576
+ */
1577
+ productTierImage: z19.boolean().optional().catch(void 0),
1578
+ type: z19.string().optional(),
1579
+ /**
1580
+ * onsite's PER-TIER unit switch (`unlock_condition_type`): `'item_quantity'` makes `minimum` an
1581
+ * item count (checked before any money handling — no ×100, no FX, no presentment overrides,
1582
+ * mirroring onsite `getTierMinimum`); anything else (or absent) keeps the money semantics.
1583
+ */
1584
+ unlockConditionType: z19.string().optional()
1585
+ });
1586
+ var SmartCartBar = z19.looseObject({
1587
+ countryCodes: z19.array(z19.string()).optional(),
1588
+ /**
1589
+ * onsite `exclude_discount_in_subtotal`: measure progress against the PRE-discount subtotal so an
1590
+ * applied code/order discount doesn't lower progress. The client adds cart discount allocations back.
1591
+ */
1592
+ excludeDiscountInSubtotal: z19.boolean().optional(),
1593
+ /** Products excluded from the progress subtotal; the client subtracts their variants' cart cost. */
1594
+ excludedProducts: z19.array(z19.looseObject({ variantIds: z19.array(z19.coerce.number()).optional() })).optional(),
1595
+ freeShippingOnSubscription: z19.boolean().optional(),
1596
+ geolocationEnabled: z19.boolean().optional(),
1597
+ id: z19.string().optional(),
1598
+ language: z19.looseObject({
1599
+ freeShippingReached: z19.string().optional(),
1600
+ freeShippingRemaining: z19.string().optional(),
1601
+ giftAreaTitle: z19.string().optional()
1602
+ }).optional(),
1603
+ name: z19.string().optional(),
1604
+ tiers: z19.array(SmartCartTier).optional()
1605
+ });
1606
+ var SmartCartComponent = z19.looseObject({
1607
+ settings: z19.looseObject({ bars: z19.array(SmartCartBar).optional() }).optional(),
1608
+ type: z19.string().optional()
1609
+ });
1610
+ var SmartCartConfig = z19.looseObject({
1611
+ components: z19.array(SmartCartComponent).optional()
1612
+ });
1613
+
1614
+ // src/schema/cabShopConfig.ts
1615
+ var CabActiveExperiment = z20.object({
1616
+ data: z20.array(
1617
+ z20.object({
1618
+ elementId: z20.coerce.number(),
1619
+ id: z20.coerce.number(),
1620
+ traffic: z20.coerce.number()
1621
+ })
1622
+ ),
1623
+ id: z20.coerce.number(),
1624
+ placeholderId: z20.coerce.number().nullable(),
1625
+ type: z20.string()
1626
+ });
1627
+ var CabShopConfig = z20.object({
1628
+ /**
1629
+ * Per-element resilient: parse as `unknown[]` (non-array → `[]`), then keep only the experiments that
1630
+ * validate. A single drifted experiment drops itself instead of `.catch`-ing the WHOLE array to `[]` and
1631
+ * silently disabling every live A/B test fleet-wide.
1632
+ */
1633
+ activeExperiments: z20.array(z20.unknown()).catch([]).default([]).transform(
1634
+ (experiments) => experiments.flatMap((experiment) => {
1635
+ const result = CabActiveExperiment.safeParse(experiment);
1636
+ return result.success ? [result.data] : [];
1637
+ })
1638
+ ),
1639
+ /**
1640
+ * Only the two fields the smart-cart entitlement gate reads (`shortName`/`isActive`). Per-element
1641
+ * resilient like `activeExperiments`: a drifted package entry degrades to an inert `''`/false one that
1642
+ * matches nothing, so a real active `cart-merchandising` package still gates correctly and a sibling
1643
+ * drift never fails the shared config parse. Note the salvage is fail-CLOSED, not fail-open, for an
1644
+ * entitled v6 shop: a malformed whole array (⇒ `[]`) or an inerted `cart-merchandising` entry (e.g.
1645
+ * `is_active: 1` ⇒ inactive) makes the gate find no active package and drop every bar. That matches
1646
+ * React's "no proof of entitlement ⇒ render null", but it silently removes bars — so the server gate
1647
+ * ({@link file://../server/widgetSettings.ts} `fetchProgressBarContext`) emits a `shopConfig` drift
1648
+ * when it sees the inert-entry sentinel (`shortName === ''`). The whole-array `[]` case leaves no
1649
+ * sentinel and is not separately signalable here.
1650
+ */
1651
+ activePackages: z20.array(z20.object({ isActive: z20.boolean(), shortName: z20.string() }).catch({ isActive: false, shortName: "" })).catch([]).default([]),
1652
+ apiKey: z20.string(),
1653
+ /** The shop's billing generation; the smart-cart entitlement gate only fires for `'v6'`. Missing/drift ⇒ `null` (fail open, ungated). */
1654
+ billingVersion: z20.string().nullable().catch(null).default(null),
1655
+ /**
1656
+ * The shop's rotating cache-buster: `onsite_cache_key` internally — a unix timestamp the engine bumps,
1657
+ * and the edge copy of this config purges, on every widget/settings/theme edit — but serialized on the
1658
+ * wire as `cache_key`, which `deepCamelCase` maps to this field (renaming it `onsiteCacheKey` would
1659
+ * sever the mapping silently; the wire-level assertion in `userConfig.test.ts` is what fails loudly).
1660
+ * Threaded onto the widgets/settings read as `cache_key` so the engine marks the response
1661
+ * immutable-cacheable and the `cached.*` edge serves it fresh-by-URL. The engine sends a numeric
1662
+ * string — coerce — but admit only number/string inputs and constrain to a positive integer: a bare
1663
+ * coercion folds `null`/`''`/`false` to `0` and `true` to `1` — "valid" numbers that would go upstream
1664
+ * as a constant never-rotating buster and pin the edge copy permanently stale. A real key is always a
1665
+ * positive int sent as number or string, so everything else lands on `.catch()` and degrades that read
1666
+ * to un-busted (origin-served — slower, never stale), never a parse fail.
1667
+ */
1668
+ cacheKey: z20.union([z20.number(), z20.string()]).transform((value) => Number(value)).pipe(z20.number().int().positive()).optional().catch(void 0),
1669
+ /**
1670
+ * The shop's base-currency ISO code (e.g. `'USD'`). CAB derives the shop minor-unit exponent from it
1671
+ * (`currencyDecimals`) so a Functions-applied fixed discount — whose amount is in shop minor units —
1672
+ * converts to the buyer's currency exactly in a cross-decimal market. `.catch()` like its siblings: a
1673
+ * missing/renamed currency degrades to "unknown" (client falls back to buyer decimals), never a parse fail.
1674
+ */
1675
+ currency: z20.string().optional().catch(void 0),
1676
+ /**
1677
+ * The merchant's "Use Markets in Data Sources" toggle. The engine (CI2) never reads it on the serve
1678
+ * path, so the proxy honouring it before sending market context is what makes the setting mean
1679
+ * anything (REB-21688). Fail-CLOSED, unlike the fail-open siblings: a missing/null/drifted field reads
1680
+ * as markets OFF — market context silently dropped (engine serves shop-default market), never sent
1681
+ * against the merchant's setting. The OFF-reading salvages stay distinguishable: missing AND null are
1682
+ * the engine's normal absent vocabulary (`.nullish()`, like `monetize`/`billingVersion`) and read as
1683
+ * the plain default — not drift — while only a drifted SHAPE `.catch`es, to the {@link DRIFTED_MARKETS}
1684
+ * identity sentinel, so `fetchUserConfig` can emit a `shopConfig` drift for the one case that means an
1685
+ * engine rename/retype; otherwise a markets-enabled merchant degrading to shop-default pricing would
1686
+ * be indistinguishable from markets genuinely off. The missing-key case is EXPLICIT via `.default()`
1687
+ * (a factory, so parses never share a mutable object) rather than inferred from zod running an
1688
+ * optional key's transform on `undefined`; `null` flows past the default through `.nullish()` into the
1689
+ * transform. `Readonly` on the output type because the drifted salvage IS frozen — an assignment to
1690
+ * `markets.enabled` downstream would throw only for drifted shops in production, so surface direct
1691
+ * writes to tsc (a guard for the direct case only: TS doesn't check `readonly` in assignability, so
1692
+ * laundering through a mutable-shaped alias still compiles).
1693
+ * The inner `enabled` stays strict on purpose: `{ enabled: null }` is a genuine retype and drifting is
1694
+ * the correct answer (the editor-path `ShopConfig.markets` would hard-fail the same payload).
1695
+ */
1696
+ markets: z20.object({ enabled: z20.boolean() }).nullish().catch(DRIFTED_MARKETS).default(() => ({ enabled: false })).transform((markets) => markets ?? { enabled: false }),
1697
+ monetize: z20.object({ publisherKey: z20.string().nullable() }).nullish().catch(void 0),
1698
+ shopId: z20.number().optional().catch(void 0),
1699
+ /**
1700
+ * The merchant's display name ("Gift Basket Store") — the Monetize header copy reads it.
1701
+ * `.catch()` like its siblings: a missing/drifted name degrades the header to the
1702
+ * myshopify-domain slug (the pre-CAB React fallback), never a parse fail. An empty string is a
1703
+ * VALID parse — the slug fallback for `''` (and whitespace/tag-only names) lives in
1704
+ * composeOffer's `||` chain, not here.
1705
+ */
1706
+ shopName: z20.string().nullish().catch(void 0)
1707
+ });
1708
+ var CabUserConfig = z20.object({
1709
+ shop: CabShopConfig,
1710
+ smartCart: SmartCartConfig.nullish().catch(void 0)
1711
+ });
1712
+
1713
+ // src/schema/widgets/checkout-and-beyond/image.ts
1714
+ import { v7 as uuidv713 } from "uuid";
1715
+ import { z as z39 } from "zod";
1716
+
837
1717
  // src/schema/widgets/checkout-and-beyond/icons.ts
838
1718
  var iconNames = [
839
1719
  "alert-circle",
@@ -919,283 +1799,65 @@ var iconTones = ["auto", "critical", "custom", "info", "neutral", "success", "wa
919
1799
  var IconTone = freezeEnum(iconTones);
920
1800
 
921
1801
  // src/schema/widgets/checkout-and-beyond/imageThumbnails.ts
922
- import { z as z7 } from "zod";
923
- var CABImageThumbnails = z7.object({
924
- limit: z7.number().int().min(1).default(8),
925
- perLine: z7.number().int().min(1).max(8).default(4)
1802
+ import { z as z21 } from "zod";
1803
+ var CABImageThumbnails = z21.object({
1804
+ limit: z21.number().int().min(1).default(8),
1805
+ perLine: z21.number().int().min(1).max(8).default(4)
926
1806
  });
927
1807
 
928
- // src/schema/widgets/checkout-and-beyond/regex.ts
929
- var DYNAMIC_TOKEN_REGEX = /^\{\{\s*[A-Za-z]+\s*\}\}$/;
930
- var HEX_COLOR_REGEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
931
- var ROOT_RELATIVE_PATH_REGEX = /^\/(?!\/)/;
932
- var HTML_TAGS_REGEX = /<\/?[a-zA-Z][^>]*>/g;
933
-
934
1808
  // src/schema/widgets/checkout-and-beyond/shared.ts
935
- import { z as z27 } from "zod";
1809
+ import { z as z38 } from "zod";
936
1810
 
937
1811
  // src/schema/widgets/checkout-and-beyond/banner.ts
938
- import { z as z11 } from "zod";
939
-
940
- // src/schema/widgets/checkout-and-beyond/layout.ts
941
- import { v7 as uuidv7 } from "uuid";
942
- import { z as z10 } from "zod";
943
-
944
- // src/schema/widgets/checkout-and-beyond/variables.ts
945
- import { z as z9 } from "zod";
946
-
947
- // src/schema/widgets/checkout-and-beyond/rule.ts
948
- import { z as z8 } from "zod";
949
- var ruleTypes = [
950
- "cart",
951
- "cart_count",
952
- "cart_item_count",
953
- "cart_line_count",
954
- "cart_subtotal",
955
- "collection",
956
- "customer",
957
- "customer_order_count",
958
- "customer_tag",
959
- "customer_total_spent",
960
- "date",
961
- "product",
962
- "product_tag",
963
- "product_title",
964
- "product_type",
965
- "product_vendor"
966
- ];
967
- var ruleType = z8.enum(ruleTypes);
968
- var RuleType = ruleType.enum;
969
- var ruleOperators = [
970
- "after",
971
- "anything",
972
- "before",
973
- "contains",
974
- "contains_any",
975
- "does_not_contain",
976
- "does_not_contain_any",
977
- "equals",
978
- "does_not_equal",
979
- "greater_than",
980
- "less_than"
981
- ];
982
- var ruleOperator = z8.enum(ruleOperators);
983
- var RuleOperator = ruleOperator.enum;
984
- var RuleCondition = z8.object({
985
- operator: z8.enum(ruleOperators).default(RuleOperator.anything),
986
- products: z8.union([z8.array(z8.unknown()), z8.strictObject({}).transform(() => [])]).default(() => []),
987
- type: z8.enum(ruleTypes).default(RuleType.cart_subtotal),
988
- value: z8.coerce.string().default("")
989
- });
990
- var RuleLogicBlock = z8.object({
991
- rules: z8.array(RuleCondition).default(() => [])
992
- });
993
- var CABRulesetEntry = z8.object({
994
- exitIfMatched: z8.boolean().default(true),
995
- logic: z8.array(RuleLogicBlock).default(() => []),
996
- output: z8.array(
997
- z8.object({
998
- key: z8.string().default("should_show"),
999
- type: z8.literal("data_json").default("data_json"),
1000
- value: z8.string().default("true")
1001
- })
1002
- ).default(() => [{ key: "should_show", type: "data_json", value: "true" }])
1003
- });
1004
- var CABRuleset = z8.union([z8.array(CABRulesetEntry), z8.strictObject({}).transform(() => [])]).default(() => []);
1005
-
1006
- // src/schema/widgets/checkout-and-beyond/variables.ts
1007
- var variableEffect = z9.enum(variableEffects);
1008
- var CABVariableSeed = z9.object({
1009
- dataSourceId: z9.number().nullable().default(null),
1010
- ruleset: CABRuleset
1011
- });
1012
- var CABBooleanVariable = z9.object({
1013
- /**
1014
- * Holds until the first seed verdict lands (and forever, for unseeded variables). Omitted, it derives
1015
- * from seededness — `false` with a `seed` (visibility is EARNED), `true` without — via the transform
1016
- * below; a cross-field default has no `.default()` spelling. An explicit value always wins.
1017
- */
1018
- defaultValue: z9.boolean().optional(),
1019
- name: z9.string().min(1),
1020
- seed: CABVariableSeed.optional(),
1021
- /** Defaulted so every stored declaration (all boolean before `text` existed) parses unchanged. */
1022
- type: z9.literal(VariableType.boolean).default(VariableType.boolean)
1023
- });
1024
- var CABTextVariable = z9.object({
1025
- defaultValue: z9.string(),
1026
- name: z9.string().min(1),
1027
- seed: z9.never().optional(),
1028
- type: z9.literal(VariableType.text)
1029
- });
1030
- var CABNumberVariable = z9.object({
1031
- defaultValue: z9.number(),
1032
- name: z9.string().min(1),
1033
- seed: z9.never().optional(),
1034
- type: z9.literal(VariableType.number)
1035
- });
1036
- var CABVariable = z9.union([CABTextVariable, CABNumberVariable, CABBooleanVariable]).transform(
1037
- (variable) => variable.type === VariableType.boolean ? { ...variable, defaultValue: variable.defaultValue ?? variable.seed === void 0 } : variable
1038
- );
1039
- var CABVariableEffect = z9.object({
1040
- effect: variableEffect.default(VariableEffect.toggle),
1041
- variable: z9.string().min(1)
1042
- });
1043
- var CABVisibility = z9.object({
1044
- variable: z9.string().min(1),
1045
- visibleWhen: z9.boolean().default(true)
1046
- });
1047
- var contentOperator = z9.enum(contentOperators);
1048
- var resultCountOperator = z9.enum(resultCountOperators);
1049
- var resultTagOperator = z9.enum(resultTagOperators);
1050
- var mutatorEvent = z9.enum(mutatorEvents);
1051
- var CABContentTrigger = z9.object({
1052
- operator: contentOperator.default(ContentOperator.is_empty),
1053
- type: z9.literal("content")
1054
- });
1055
- var CABResultCountTrigger = z9.object({
1056
- operator: resultCountOperator.default(ResultCountOperator.equals),
1057
- type: z9.literal("result_count"),
1058
- value: z9.number().default(0)
1059
- });
1060
- var CABResultTagTrigger = z9.object({
1061
- operator: resultTagOperator.default(ResultTagOperator.contains),
1062
- type: z9.literal("result_tag"),
1063
- value: z9.string().min(1)
1064
- });
1065
- var CABEventTrigger = z9.object({
1066
- event: mutatorEvent,
1067
- type: z9.literal("event")
1068
- });
1069
- var CABVariableTrigger = z9.discriminatedUnion("type", [
1070
- CABContentTrigger,
1071
- CABEventTrigger,
1072
- CABResultCountTrigger,
1073
- CABResultTagTrigger
1074
- ]);
1075
- var CABVariableMutator = z9.object({
1076
- effect: variableEffect.default(VariableEffect.set_false),
1077
- variable: z9.string().min(1),
1078
- when: CABVariableTrigger
1079
- });
1080
- var CABVariableFields = {
1081
- mutators: z9.array(CABVariableMutator).optional(),
1082
- variables: z9.array(CABVariable).refine((variables) => new Set(variables.map(({ name }) => name)).size === variables.length, {
1083
- message: "variable names must be unique within a section"
1084
- }).optional(),
1085
- visibility: CABVisibility.optional()
1086
- };
1812
+ import { z as z23 } from "zod";
1087
1813
 
1088
1814
  // src/schema/widgets/checkout-and-beyond/layout.ts
1089
- var GridItem = z10.union([z10.literal("auto"), z10.literal("fill"), z10.string().regex(/^\d+%$/), z10.number()]);
1090
- var CABLayoutWrapping = z10.object({
1091
- breakAt: z10.number().positive(),
1092
- maxPerLine: z10.number().int().positive()
1815
+ import { v7 as uuidv72 } from "uuid";
1816
+ import { z as z22 } from "zod";
1817
+ var GridItem = z22.union([z22.literal("auto"), z22.literal("fill"), z22.string().regex(/^\d+%$/), z22.number()]);
1818
+ var CABLayoutWrapping = z22.object({
1819
+ breakAt: z22.number().positive(),
1820
+ maxPerLine: z22.number().int().positive()
1093
1821
  });
1094
- var CABLayoutSection = z10.object({
1822
+ var CABLayoutSection = z22.object({
1095
1823
  ...CABVariableFields,
1096
- alignment: z10.object({
1097
- horizontal: z10.enum(horizontalAlignments).default(HorizontalAlignment.start),
1098
- vertical: z10.enum(verticalAlignments).default(VerticalAlignment.top)
1824
+ alignment: z22.object({
1825
+ horizontal: z22.enum(horizontalAlignments).default(HorizontalAlignment.start),
1826
+ vertical: z22.enum(verticalAlignments).default(VerticalAlignment.top)
1099
1827
  }).default({
1100
1828
  horizontal: HorizontalAlignment.start,
1101
1829
  vertical: VerticalAlignment.top
1102
1830
  }),
1103
- border: z10.lazy(() => CABBorder),
1104
- buttonField: z10.enum(buttonFields).optional(),
1105
- direction: z10.enum(directions).default(Direction.rows),
1831
+ border: z22.lazy(() => CABBorder),
1832
+ buttonField: z22.enum(buttonFields).optional(),
1833
+ direction: z22.enum(directions).default(Direction.rows),
1106
1834
  /**
1107
1835
  * Platform chrome around the section, not a layout behavior: `callout` is a subdued box with block borders
1108
1836
  * (the legacy post-purchase callout banner), `offer-content` the post-purchase offer's own content card.
1109
1837
  */
1110
- frame: z10.enum(layoutFrames).optional(),
1111
- grid: z10.object({
1112
- columns: z10.union([z10.array(GridItem), z10.undefined()]).default(() => ["auto"]),
1113
- rows: z10.union([z10.array(GridItem), z10.undefined()]).default(() => ["auto"])
1838
+ frame: z22.enum(layoutFrames).optional(),
1839
+ grid: z22.object({
1840
+ columns: z22.union([z22.array(GridItem), z22.undefined()]).default(() => ["auto"]),
1841
+ rows: z22.union([z22.array(GridItem), z22.undefined()]).default(() => ["auto"])
1114
1842
  }).default({ columns: ["auto"], rows: ["auto"] }).optional(),
1115
- name: z10.string().optional(),
1116
- padding: z10.enum(spacings).default(Spacing.none),
1117
- sectionId: z10.uuid().default(() => uuidv7()),
1118
- sections: z10.union([z10.array(z10.lazy(() => CABSection)), z10.strictObject({}).transform(() => [])]).default(() => []),
1119
- sectionType: z10.literal(SectionType.layout).default(SectionType.layout),
1120
- spacing: z10.enum(spacings).default(Spacing.base),
1843
+ name: z22.string().optional(),
1844
+ padding: z22.enum(spacings).default(Spacing.none),
1845
+ sectionId: z22.uuid().default(() => uuidv72()),
1846
+ sections: z22.union([z22.array(z22.lazy(() => CABSection)), z22.strictObject({}).transform(() => [])]).default(() => []),
1847
+ sectionType: z22.literal(SectionType.layout).default(SectionType.layout),
1848
+ spacing: z22.enum(spacings).default(Spacing.base),
1121
1849
  /** Fire-and-forget impression pixel POSTed once when the section is shown (e.g. a Monetize offer view). */
1122
- viewUrl: z10.string().optional(),
1123
- width: z10.number().default(100),
1850
+ viewUrl: z22.string().optional(),
1851
+ width: z22.number().default(100),
1124
1852
  wrapping: CABLayoutWrapping.optional()
1125
1853
  });
1126
1854
 
1127
- // src/schema/widgets/checkout-and-beyond/utils.ts
1128
- import { isPlainObject as isPlainObject2 } from "es-toolkit";
1129
- import { get, isString } from "es-toolkit/compat";
1130
-
1131
- // src/transforms/convertNumericObjects.ts
1132
- import { isPlainObject } from "es-toolkit";
1133
- var ARRAY_INDEX_STRING = /^(?:0|[1-9]\d*)$/;
1134
- var hasOnlyNumericKeys = (input, requireCompleteRun) => {
1135
- const keys = Object.keys(input);
1136
- if (keys.length === 0) return false;
1137
- if (!keys.every((key) => ARRAY_INDEX_STRING.test(key) && Number.isSafeInteger(Number(key)))) return false;
1138
- if (!requireCompleteRun) return true;
1139
- const sorted = keys.map(Number).sort((a, b) => a - b);
1140
- return sorted.every((n, i) => n === i);
1141
- };
1142
- var numericObjectToArray = (input) => Object.entries(input).sort(([a], [b]) => Number(a) - Number(b)).map(([, value]) => value);
1143
- var convert = (input, requireCompleteRun) => {
1144
- if (Array.isArray(input)) return input.map((item) => convert(item, requireCompleteRun));
1145
- if (isPlainObject(input)) {
1146
- if (hasOnlyNumericKeys(input, requireCompleteRun)) {
1147
- return numericObjectToArray(input).map((item) => convert(item, requireCompleteRun));
1148
- }
1149
- const out = {};
1150
- for (const [key, value] of Object.entries(input)) out[key] = convert(value, requireCompleteRun);
1151
- return out;
1152
- }
1153
- return input;
1154
- };
1155
- var convertNumericObjects = (input) => convert(input, true);
1156
-
1157
- // src/schema/widgets/checkout-and-beyond/utils.ts
1158
- var isHTML = (html = "") => {
1159
- if (typeof window !== "undefined") {
1160
- const parser = new DOMParser();
1161
- const doc = parser.parseFromString(html, "text/html");
1162
- const hasHTML = (nodeList) => Array.from(nodeList).some((node) => node.nodeType === 1);
1163
- return hasHTML(doc.body.childNodes) || hasHTML(doc.head.childNodes);
1164
- }
1165
- return html.search(HTML_TAGS_REGEX) !== -1;
1166
- };
1167
- var hasHTMLInDoc = (doc) => doc.content.some(({ content = [] }) => content.some(({ text: text2 }) => isHTML(text2)));
1168
- var checkForHTML = (input) => !(isString(input) && isHTML(input) || isPlainObject2(input) && hasHTMLInDoc(input));
1169
- var NO_HTML = { message: "HTML is not supported" };
1170
- var isSectionType = (sectionType) => (section) => get(section, "sectionType") === sectionType;
1171
- var isBannerType = isSectionType(SectionType.banner);
1172
- var isButtonType = isSectionType(SectionType.button);
1173
- var isCarouselType = isSectionType(SectionType.carousel);
1174
- var isCartLineType = isSectionType(SectionType.cartLine);
1175
- var isDataSourceType = isSectionType(
1176
- SectionType.dataSource
1177
- );
1178
- var isDiscountType = isSectionType(SectionType.discount);
1179
- var isImageType = isSectionType(SectionType.image);
1180
- var isLayoutType = isSectionType(SectionType.layout);
1181
- var isMonetizeType = isSectionType(SectionType.monetize);
1182
- var isOffersType = isSectionType(SectionType.offers);
1183
- var isProductType = isSectionType(SectionType.product);
1184
- var isProductsType = isSectionType(SectionType.products);
1185
- var isProgressBarType = isSectionType(
1186
- SectionType.progressBar
1187
- );
1188
- var isQuantityType = isSectionType(SectionType.quantity);
1189
- var isReviewsType = isSectionType(SectionType.reviews);
1190
- var isTextType = isSectionType(SectionType.text);
1191
- var isVariantsType = isSectionType(SectionType.variants);
1192
-
1193
1855
  // src/schema/widgets/checkout-and-beyond/banner.ts
1194
- var CABBannerSection = z11.lazy(
1856
+ var CABBannerSection = z23.lazy(
1195
1857
  () => CABLayoutSection.omit({ sectionType: true }).extend({
1196
- buttonField: z11.enum(buttonFields).optional(),
1197
- color: z11.enum(bannerColors).default("info"),
1198
- dismissible: z11.boolean().default(true),
1858
+ buttonField: z23.enum(buttonFields).optional(),
1859
+ color: z23.enum(bannerColors).default("info"),
1860
+ dismissible: z23.boolean().default(true),
1199
1861
  /**
1200
1862
  * The `s-banner` heading — localized content (a buyer can see a merchant-composed banner). Absent OR
1201
1863
  * blank ⇒ no heading; the renderer emits no heading slot for `''` rather than an empty one.
@@ -1211,140 +1873,50 @@ var CABBannerSection = z11.lazy(
1211
1873
  * not a 500. Nothing emits `heading` yet; the first converter that maps merchant banner copy into it
1212
1874
  * either strips first or teaches `normalizeTextContent` to strip this field.
1213
1875
  */
1214
- heading: z11.record(z11.string(), z11.string().refine(checkForHTML, NO_HTML)).optional(),
1215
- sectionType: z11.literal(SectionType.banner).default(SectionType.banner)
1876
+ heading: z23.record(z23.string(), z23.string().refine(checkForHTML, NO_HTML)).optional(),
1877
+ sectionType: z23.literal(SectionType.banner).default(SectionType.banner)
1216
1878
  })
1217
1879
  );
1218
1880
 
1219
1881
  // src/schema/widgets/checkout-and-beyond/button.ts
1220
1882
  import { v7 as uuidv73 } from "uuid";
1221
- import { z as z13 } from "zod";
1222
-
1223
- // src/schema/widgets/checkout-and-beyond/text.ts
1224
- import { forEach, isString as isString2 } from "es-toolkit/compat";
1225
- import { v7 as uuidv72 } from "uuid";
1226
- import { z as z12 } from "zod";
1227
- var enumOrOmit = (...vals) => z12.string().transform((val) => vals.includes(val) ? val : void 0).optional();
1228
- var TiptapText = z12.object({
1229
- marks: z12.union([
1230
- z12.array(
1231
- z12.discriminatedUnion("type", [
1232
- z12.object({ type: z12.literal("bold") }),
1233
- z12.object({ type: z12.literal("italic") }),
1234
- z12.object({
1235
- attrs: z12.object({
1236
- class: z12.string().nullable(),
1237
- href: z12.union([
1238
- z12.url(),
1239
- z12.literal("#"),
1240
- z12.string().regex(DYNAMIC_TOKEN_REGEX),
1241
- z12.string().regex(ROOT_RELATIVE_PATH_REGEX)
1242
- ]),
1243
- rel: z12.string().default("noopener noreferrer nofollow"),
1244
- target: z12.string().default("_blank")
1245
- }),
1246
- type: z12.literal("link")
1247
- }),
1248
- z12.object({ type: z12.literal("strike") }),
1249
- z12.object({
1250
- attrs: z12.object({
1251
- color: enumOrOmit(...textColorNames).default(TextColorName.base),
1252
- fontSize: enumOrOmit(...textSizeNames).default(TextSizeName.base)
1253
- }),
1254
- type: z12.literal("textStyle")
1255
- })
1256
- ])
1257
- ),
1258
- z12.strictObject({}).transform(() => [])
1259
- ]).optional(),
1260
- text: z12.string().default(""),
1261
- type: z12.literal("text").default("text")
1262
- });
1263
- var TiptapParagraph = z12.object({
1264
- attrs: z12.object({
1265
- textAlign: enumOrOmit(...textAlignments)
1266
- }).default({ textAlign: TextAlignment.start }),
1267
- content: z12.union([z12.array(TiptapText), z12.strictObject({}).transform(() => [])]).default(() => []),
1268
- type: z12.literal("paragraph").default("paragraph")
1269
- });
1270
- var TiptapDocument = z12.object({
1271
- attrs: z12.object({
1272
- blockSpacing: enumOrOmit(...spacings)
1273
- }).default({ blockSpacing: Spacing.base }),
1274
- content: z12.union([z12.array(TiptapParagraph), z12.strictObject({}).transform(() => [])]).default(() => [TiptapParagraph.parse({})]),
1275
- type: z12.literal("doc").default("doc")
1276
- });
1277
- var CABTextTier = z12.object({
1278
- threshold: z12.number().int().nonnegative(),
1279
- thresholds: z12.record(z12.string(), z12.number().int().nonnegative()).default(() => ({})),
1280
- tierId: z12.string().optional(),
1281
- tierType: z12.enum(progressTierTypes).default(ProgressTierType.shipping)
1282
- });
1283
- var CABTextSection = z12.object({
1284
- ...CABVariableFields,
1285
- buttonField: z12.enum(buttonFields).optional(),
1286
- content: z12.record(z12.string(), z12.union([z12.string(), TiptapDocument]).refine(checkForHTML, NO_HTML).optional()).default({ en: TiptapDocument.parse({}) }).optional(),
1287
- name: z12.string().optional(),
1288
- optionId: z12.number().optional(),
1289
- sectionId: z12.uuid().default(() => uuidv72()),
1290
- sectionType: z12.literal(SectionType.text).default(SectionType.text),
1291
- tier: CABTextTier.optional()
1292
- }).superRefine(({ buttonField, content }, ctx) => {
1293
- if (buttonField === ButtonField.destinationUrl && content) {
1294
- forEach(content, (value, lang) => {
1295
- if (value && isString2(value)) {
1296
- if (DYNAMIC_TOKEN_REGEX.test(value)) return;
1297
- try {
1298
- new URL(value);
1299
- } catch {
1300
- ctx.addIssue({
1301
- code: "custom",
1302
- message: "Invalid URL",
1303
- path: ["content", lang]
1304
- });
1305
- }
1306
- }
1307
- });
1308
- }
1309
- });
1310
-
1311
- // src/schema/widgets/checkout-and-beyond/button.ts
1312
- var CABButtonContent = z13.lazy(
1313
- () => z13.discriminatedUnion("sectionType", [CABImageSection, CABLayoutSection, CABTextSection])
1883
+ import { z as z24 } from "zod";
1884
+ var CABButtonContent = z24.lazy(
1885
+ () => z24.discriminatedUnion("sectionType", [CABImageSection, CABLayoutSection, CABTextSection])
1314
1886
  );
1315
- var CABButtonSection = z13.object({
1887
+ var CABButtonSection = z24.object({
1316
1888
  ...CABVariableFields,
1317
- action: z13.union([z13.literal(""), z13.enum(buttonActions)]).default(""),
1318
- buttonStyle: z13.enum(buttonStyles).default(ButtonStyle.primary),
1319
- custom: z13.object({
1320
- color: z13.string().regex(HEX_COLOR_REGEX).default("#005bd3"),
1321
- height: z13.number().default(52),
1322
- width: z13.number().default(300)
1889
+ action: z24.union([z24.literal(""), z24.enum(buttonActions)]).default(""),
1890
+ buttonStyle: z24.enum(buttonStyles).default(ButtonStyle.primary),
1891
+ custom: z24.object({
1892
+ color: z24.string().regex(HEX_COLOR_REGEX).default("#005bd3"),
1893
+ height: z24.number().default(52),
1894
+ width: z24.number().default(300)
1323
1895
  }).default({
1324
1896
  color: "#005bd3",
1325
1897
  height: 52,
1326
1898
  width: 300
1327
1899
  }),
1328
- disableDowngrade: z13.boolean().default(false),
1900
+ disableDowngrade: z24.boolean().default(false),
1329
1901
  /**
1330
1902
  * What labels a `switchToSubscription` button's frequency options. `planName` shows the selling plan's
1331
1903
  * own name (legacy post-purchase, L115) and falls back to the `subscriptionOption` template for a plan
1332
1904
  * without one; absent or `template` is the template alone (the checkout card). Optional and additive
1333
1905
  * (REB-24846); an unknown value reads as absent.
1334
1906
  */
1335
- frequencyLabel: z13.enum(["planName", "template"]).optional().catch(void 0),
1336
- name: z13.string().optional(),
1337
- optionId: z13.number().optional(),
1338
- popover: z13.object({
1339
- alignment: z13.enum(textAlignments).default(TextAlignment.center),
1340
- position: z13.enum(popoverPositions).default(PopoverPosition.blockStart)
1907
+ frequencyLabel: z24.enum(["planName", "template"]).optional().catch(void 0),
1908
+ name: z24.string().optional(),
1909
+ optionId: z24.number().optional(),
1910
+ popover: z24.object({
1911
+ alignment: z24.enum(textAlignments).default(TextAlignment.center),
1912
+ position: z24.enum(popoverPositions).default(PopoverPosition.blockStart)
1341
1913
  }).optional().default(() => ({
1342
1914
  alignment: TextAlignment.center,
1343
1915
  position: PopoverPosition.blockStart
1344
1916
  })),
1345
- sectionId: z13.uuid().default(() => uuidv73()),
1346
- sections: z13.union([z13.array(CABButtonContent), z13.strictObject({}).transform(() => [])]).default(() => []),
1347
- sectionType: z13.literal(SectionType.button).default(SectionType.button),
1917
+ sectionId: z24.uuid().default(() => uuidv73()),
1918
+ sections: z24.union([z24.array(CABButtonContent), z24.strictObject({}).transform(() => [])]).default(() => []),
1919
+ sectionType: z24.literal(SectionType.button).default(SectionType.button),
1348
1920
  /**
1349
1921
  * The smart-cart tier this button acts on (`addGift`/`removeGift`): scopes the click to one tier's gift. The
1350
1922
  * client resolves the tier's gift (productId) from the progress-bar context by this `tierId` — the button node
@@ -1359,25 +1931,25 @@ var CABButtonSection = z13.object({
1359
1931
  * lookup finds no tier and the click resolves to nothing. Safe to bound — the field is new, so there are no
1360
1932
  * stored payloads to grandfather, and the producer is client code, not a merchant form.
1361
1933
  */
1362
- tier: z13.object({ tierId: z13.string().min(1) }).optional(),
1934
+ tier: z24.object({ tierId: z24.string().min(1) }).optional(),
1363
1935
  /** Fire-and-forget pixel POSTed when the button is clicked (e.g. a Monetize decline). */
1364
- trackingUrl: z13.string().optional(),
1936
+ trackingUrl: z24.string().optional(),
1365
1937
  /**
1366
1938
  * Secondary effects applied to in-scope variables on click, alongside (never instead of) the primary
1367
1939
  * `action` — an `addToOrder` button can also flip a variable. Applied in array order.
1368
1940
  */
1369
- variableEffects: z13.array(CABVariableEffect).optional()
1941
+ variableEffects: z24.array(CABVariableEffect).optional()
1370
1942
  });
1371
1943
 
1372
1944
  // src/schema/widgets/checkout-and-beyond/carousel.ts
1373
- import { z as z18 } from "zod";
1945
+ import { z as z29 } from "zod";
1374
1946
 
1375
1947
  // src/schema/widgets/checkout-and-beyond/offers.ts
1376
- import { z as z17 } from "zod";
1948
+ import { z as z28 } from "zod";
1377
1949
 
1378
1950
  // src/schema/widgets/checkout-and-beyond/dataSource.ts
1379
1951
  import { v7 as uuidv74 } from "uuid";
1380
- import { z as z14 } from "zod";
1952
+ import { z as z25 } from "zod";
1381
1953
 
1382
1954
  // src/schema/widgets/checkout-and-beyond/constants.ts
1383
1955
  import { uniqBy } from "es-toolkit";
@@ -1431,77 +2003,77 @@ var targetAreas = {
1431
2003
  };
1432
2004
 
1433
2005
  // src/schema/widgets/checkout-and-beyond/dataSource.ts
1434
- var CABDiscount = z14.object({
1435
- amount: z14.union([z14.number(), z14.string()]).catch(0),
1436
- discountedBy: z14.string().optional(),
1437
- discountedFrom: z14.enum(discountSources).optional().catch(void 0),
1438
- type: z14.enum(discountTypes).catch("none")
1439
- });
1440
- var CABIntegrations = z14.object({
1441
- judgeme: z14.boolean().default(false),
1442
- junip: z14.boolean().default(false),
1443
- klaviyo: z14.boolean().default(false),
1444
- loox: z14.boolean().default(false),
1445
- okendo: z14.boolean().default(false),
1446
- opinew: z14.boolean().default(false),
1447
- reviewsio: z14.boolean().default(false),
1448
- stamped: z14.boolean().default(false),
1449
- yotpo: z14.boolean().default(false)
1450
- });
1451
- var CABVariantLabels = z14.object({
1452
- variantOption: z14.string().optional().catch(void 0),
1453
- variantOptions: z14.string().optional().catch(void 0)
1454
- });
1455
- var CABDataSourceSection = z14.object({
1456
- dataSourceId: z14.number().nullable().default(null),
1457
- dataSourcePath: z14.string().default(DEFAULT_ENDPOINTS[0].value),
2006
+ var CABDiscount = z25.object({
2007
+ amount: z25.union([z25.number(), z25.string()]).catch(0),
2008
+ discountedBy: z25.string().optional(),
2009
+ discountedFrom: z25.enum(discountSources).optional().catch(void 0),
2010
+ type: z25.enum(discountTypes).catch("none")
2011
+ });
2012
+ var CABIntegrations = z25.object({
2013
+ judgeme: z25.boolean().default(false),
2014
+ junip: z25.boolean().default(false),
2015
+ klaviyo: z25.boolean().default(false),
2016
+ loox: z25.boolean().default(false),
2017
+ okendo: z25.boolean().default(false),
2018
+ opinew: z25.boolean().default(false),
2019
+ reviewsio: z25.boolean().default(false),
2020
+ stamped: z25.boolean().default(false),
2021
+ yotpo: z25.boolean().default(false)
2022
+ });
2023
+ var CABVariantLabels = z25.object({
2024
+ variantOption: z25.string().optional().catch(void 0),
2025
+ variantOptions: z25.string().optional().catch(void 0)
2026
+ });
2027
+ var CABDataSourceSection = z25.object({
2028
+ dataSourceId: z25.number().nullable().default(null),
2029
+ dataSourcePath: z25.string().default(DEFAULT_ENDPOINTS[0].value),
1458
2030
  discount: CABDiscount.optional(),
1459
2031
  integrations: CABIntegrations.optional(),
1460
- limit: z14.number().default(1),
1461
- matchVariant: z14.boolean().default(false),
1462
- matchVariantOutOfStock: z14.boolean().default(false),
1463
- name: z14.string().default(DEFAULT_ENDPOINTS[0].label),
1464
- productType: z14.enum(productTypes).optional(),
1465
- sectionId: z14.uuid().default(() => uuidv74()),
1466
- sectionType: z14.literal(SectionType.dataSource).default(SectionType.dataSource),
2032
+ limit: z25.number().default(1),
2033
+ matchVariant: z25.boolean().default(false),
2034
+ matchVariantOutOfStock: z25.boolean().default(false),
2035
+ name: z25.string().default(DEFAULT_ENDPOINTS[0].label),
2036
+ productType: z25.enum(productTypes).optional(),
2037
+ sectionId: z25.uuid().default(() => uuidv74()),
2038
+ sectionType: z25.literal(SectionType.dataSource).default(SectionType.dataSource),
1467
2039
  /** Default subscribe-vs-one-time for `productType: both` (legacy `subscription_is_default`). */
1468
- subscriptionIsDefault: z14.boolean().default(false),
1469
- treatAsGwp: z14.boolean().default(false),
2040
+ subscriptionIsDefault: z25.boolean().default(false),
2041
+ treatAsGwp: z25.boolean().default(false),
1470
2042
  variantLabels: CABVariantLabels.optional().catch(void 0)
1471
2043
  });
1472
2044
 
1473
2045
  // src/schema/widgets/checkout-and-beyond/products.ts
1474
2046
  import { v7 as uuidv76 } from "uuid";
1475
- import { z as z16 } from "zod";
2047
+ import { z as z27 } from "zod";
1476
2048
 
1477
2049
  // src/schema/widgets/checkout-and-beyond/product.ts
1478
2050
  import { v7 as uuidv75 } from "uuid";
1479
- import { z as z15 } from "zod";
1480
- var CABVariantContent = z15.lazy(
1481
- () => z15.discriminatedUnion("sectionType", [CABButtonSection, CABTextSection])
2051
+ import { z as z26 } from "zod";
2052
+ var CABVariantContent = z26.lazy(
2053
+ () => z26.discriminatedUnion("sectionType", [CABButtonSection, CABTextSection])
1482
2054
  );
1483
- var CABProductSection = z15.object({
2055
+ var CABProductSection = z26.object({
1484
2056
  ...CABVariableFields,
1485
- name: z15.string().optional(),
1486
- productId: z15.number().nullable().default(null),
1487
- sectionId: z15.uuid().default(() => uuidv75()),
1488
- sections: z15.union([z15.array(CABVariantContent), z15.strictObject({}).transform(() => [])]).default(() => []),
1489
- sectionType: z15.literal(SectionType.product).default(SectionType.product),
1490
- selectors: z15.record(z15.string(), z15.enum(variantSelectors).default(VariantSelector.menu)).default(() => ({})),
1491
- variantMode: z15.enum(variantModes).default(VariantMode.multiple)
2057
+ name: z26.string().optional(),
2058
+ productId: z26.number().nullable().default(null),
2059
+ sectionId: z26.uuid().default(() => uuidv75()),
2060
+ sections: z26.union([z26.array(CABVariantContent), z26.strictObject({}).transform(() => [])]).default(() => []),
2061
+ sectionType: z26.literal(SectionType.product).default(SectionType.product),
2062
+ selectors: z26.record(z26.string(), z26.enum(variantSelectors).default(VariantSelector.menu)).default(() => ({})),
2063
+ variantMode: z26.enum(variantModes).default(VariantMode.multiple)
1492
2064
  });
1493
2065
 
1494
2066
  // src/schema/widgets/checkout-and-beyond/products.ts
1495
- var CABProductsSection = z16.object({
2067
+ var CABProductsSection = z27.object({
1496
2068
  ...CABVariableFields,
1497
- name: z16.string().optional(),
1498
- sectionId: z16.uuid().default(() => uuidv76()),
1499
- sections: z16.union([z16.array(z16.lazy(() => CABProductSection)), z16.strictObject({}).transform(() => [])]).default(() => []),
1500
- sectionType: z16.literal(SectionType.products).default(SectionType.products)
2069
+ name: z27.string().optional(),
2070
+ sectionId: z27.uuid().default(() => uuidv76()),
2071
+ sections: z27.union([z27.array(z27.lazy(() => CABProductSection)), z27.strictObject({}).transform(() => [])]).default(() => []),
2072
+ sectionType: z27.literal(SectionType.products).default(SectionType.products)
1501
2073
  });
1502
2074
 
1503
2075
  // src/schema/widgets/checkout-and-beyond/offers.ts
1504
- var CABOffersSection = z17.lazy(
2076
+ var CABOffersSection = z28.lazy(
1505
2077
  () => CABLayoutSection.omit({ sections: true, sectionType: true }).extend({
1506
2078
  /**
1507
2079
  * A rule between consecutive cards in the vertical list — legacy checkout-offers' `<Divider />`,
@@ -1509,8 +2081,8 @@ var CABOffersSection = z17.lazy(
1509
2081
  * run of products). Optional, not defaulted: only the v1 converter sets it (REB-24265); a
1510
2082
  * hand-built tree keeps React CAB's dividerless rendering.
1511
2083
  */
1512
- divider: z17.boolean().optional(),
1513
- sections: z17.union([z17.array(z17.lazy(() => CABSection)), z17.strictObject({}).transform(() => [])]).default(() => [
2084
+ divider: z28.boolean().optional(),
2085
+ sections: z28.union([z28.array(z28.lazy(() => CABSection)), z28.strictObject({}).transform(() => [])]).default(() => [
1514
2086
  CABBannerSection.parse({
1515
2087
  buttonField: "undoAddToOrder",
1516
2088
  color: "success",
@@ -1524,77 +2096,77 @@ var CABOffersSection = z17.lazy(
1524
2096
  CABDataSourceSection.parse({}),
1525
2097
  CABProductsSection.parse({})
1526
2098
  ]),
1527
- sectionType: z17.literal(SectionType.offers).default(SectionType.offers)
2099
+ sectionType: z28.literal(SectionType.offers).default(SectionType.offers)
1528
2100
  })
1529
2101
  );
1530
2102
 
1531
2103
  // src/schema/widgets/checkout-and-beyond/carousel.ts
1532
- var CABCarouselSection = z18.lazy(
2104
+ var CABCarouselSection = z29.lazy(
1533
2105
  () => CABLayoutSection.omit({ sections: true, sectionType: true }).extend({
1534
- autoAdvanceInterval: z18.number().default(0),
1535
- itemsAtOnce: z18.number().min(1).default(1),
2106
+ autoAdvanceInterval: z29.number().default(0),
2107
+ itemsAtOnce: z29.number().min(1).default(1),
1536
2108
  /** Card arrangement for the offers child (`horizontal` = side-by-side) — NOT the carousel's own `direction`. */
1537
- offersDirection: z18.enum(offersDirections).default("vertical"),
1538
- sections: z18.union([z18.array(z18.lazy(() => CABSection)), z18.strictObject({}).transform(() => [])]).default(() => [CABOffersSection.parse({})]),
1539
- sectionType: z18.literal(SectionType.carousel).default(SectionType.carousel)
2109
+ offersDirection: z29.enum(offersDirections).default("vertical"),
2110
+ sections: z29.union([z29.array(z29.lazy(() => CABSection)), z29.strictObject({}).transform(() => [])]).default(() => [CABOffersSection.parse({})]),
2111
+ sectionType: z29.literal(SectionType.carousel).default(SectionType.carousel)
1540
2112
  })
1541
2113
  );
1542
2114
 
1543
2115
  // src/schema/widgets/checkout-and-beyond/cartLine.ts
1544
- import { z as z19 } from "zod";
1545
- var CABCartLineSection = z19.lazy(
2116
+ import { z as z30 } from "zod";
2117
+ var CABCartLineSection = z30.lazy(
1546
2118
  () => CABLayoutSection.omit({ sections: true, sectionType: true }).extend({
1547
- sections: z19.union([z19.array(z19.lazy(() => CABSection)), z19.strictObject({}).transform(() => [])]).default(() => [CABButtonSection.parse({ action: ButtonAction.switchToSubscription })]),
1548
- sectionType: z19.literal(SectionType.cartLine).default(SectionType.cartLine)
2119
+ sections: z30.union([z30.array(z30.lazy(() => CABSection)), z30.strictObject({}).transform(() => [])]).default(() => [CABButtonSection.parse({ action: ButtonAction.switchToSubscription })]),
2120
+ sectionType: z30.literal(SectionType.cartLine).default(SectionType.cartLine)
1549
2121
  })
1550
2122
  );
1551
2123
 
1552
2124
  // src/schema/widgets/checkout-and-beyond/discount.ts
1553
2125
  import { v7 as uuidv77 } from "uuid";
1554
- import { z as z20 } from "zod";
1555
- var CABDiscountSection = z20.object({
2126
+ import { z as z31 } from "zod";
2127
+ var CABDiscountSection = z31.object({
1556
2128
  ...CABVariableFields,
1557
- amount: z20.union([z20.number(), z20.string()]).catch(0),
1558
- discountedBy: z20.string().optional(),
1559
- discountedFrom: z20.enum(discountSources).optional().catch(void 0),
1560
- message: z20.string().optional(),
1561
- name: z20.string().optional(),
1562
- quantity: z20.number().default(1),
1563
- sectionId: z20.uuid().default(() => uuidv77()),
1564
- sectionType: z20.literal(SectionType.discount).default(SectionType.discount),
1565
- type: z20.enum(discountTypes).catch("none")
2129
+ amount: z31.union([z31.number(), z31.string()]).catch(0),
2130
+ discountedBy: z31.string().optional(),
2131
+ discountedFrom: z31.enum(discountSources).optional().catch(void 0),
2132
+ message: z31.string().optional(),
2133
+ name: z31.string().optional(),
2134
+ quantity: z31.number().default(1),
2135
+ sectionId: z31.uuid().default(() => uuidv77()),
2136
+ sectionType: z31.literal(SectionType.discount).default(SectionType.discount),
2137
+ type: z31.enum(discountTypes).catch("none")
1566
2138
  });
1567
2139
 
1568
2140
  // src/schema/widgets/checkout-and-beyond/monetize.ts
1569
2141
  import { v7 as uuidv78 } from "uuid";
1570
- import { z as z21 } from "zod";
1571
- var CABMonetizeAds = z21.object({
1572
- requestId: z21.string().optional(),
2142
+ import { z as z32 } from "zod";
2143
+ var CABMonetizeAds = z32.object({
2144
+ requestId: z32.string().optional(),
1573
2145
  /** Defaulted here, never in the beacon: absence of information is `ads` itself being absent. */
1574
- testing: z21.boolean().default(false)
2146
+ testing: z32.boolean().default(false)
1575
2147
  });
1576
- var CABMonetizeSection = z21.object({
2148
+ var CABMonetizeSection = z32.object({
1577
2149
  ...CABVariableFields,
1578
2150
  ads: CABMonetizeAds.optional(),
1579
- name: z21.string().optional(),
2151
+ name: z32.string().optional(),
1580
2152
  /**
1581
2153
  * Server-hydrated offer cards (inline hydration): when `/cab/sections` was asked with a `monetize`
1582
2154
  * context block, the composed cards ride the marker itself — `[]` means "hydrated, no fill" (render
1583
2155
  * nothing, skip the fetch); ABSENT means "not hydrated" (the client fetches via `/cab/monetize`).
1584
2156
  */
1585
- offers: z21.array(z21.lazy(() => CABSection)).optional(),
2157
+ offers: z32.array(z32.lazy(() => CABSection)).optional(),
1586
2158
  /** Privacy Policy footer href (the widget's `privacy_policy_url` setting); read server-side for hydration, relayed by the client only on the legacy `/cab/monetize` fallback. */
1587
- privacyPolicyUrl: z21.string().optional(),
1588
- sectionId: z21.uuid().default(() => uuidv78()),
1589
- sectionType: z21.literal(SectionType.monetize).default(SectionType.monetize),
2159
+ privacyPolicyUrl: z32.string().optional(),
2160
+ sectionId: z32.uuid().default(() => uuidv78()),
2161
+ sectionType: z32.literal(SectionType.monetize).default(SectionType.monetize),
1590
2162
  /** Ads-taxonomy sub-vertical id (the widget's `sub_vertical` setting); paired with `vertical`. */
1591
- subVertical: z21.string().optional(),
2163
+ subVertical: z32.string().optional(),
1592
2164
  /** Ads-taxonomy vertical id (the widget's `vertical` setting) — with `subVertical`, the admin Monetize editor's two configurables. */
1593
- vertical: z21.string().optional()
2165
+ vertical: z32.string().optional()
1594
2166
  });
1595
2167
 
1596
2168
  // src/schema/widgets/checkout-and-beyond/progressBar.ts
1597
- import { z as z22 } from "zod";
2169
+ import { z as z33 } from "zod";
1598
2170
 
1599
2171
  // src/schema/widgets/checkout-and-beyond/giftLabels.ts
1600
2172
  var GIFT_LABELS = {
@@ -1713,34 +2285,34 @@ var GIFT_LABELS = {
1713
2285
  };
1714
2286
 
1715
2287
  // src/schema/widgets/checkout-and-beyond/progressBar.ts
1716
- var CABProgressTierExclusion = z22.object({
1717
- collectionIds: z22.array(z22.number().int().positive()).default(() => []),
1718
- productIds: z22.array(z22.number().int().positive()).default(() => []),
1719
- tags: z22.array(z22.string().min(1)).default(() => []),
1720
- type: z22.enum(progressTierExclusionTypes)
2288
+ var CABProgressTierExclusion = z33.object({
2289
+ collectionIds: z33.array(z33.number().int().positive()).default(() => []),
2290
+ productIds: z33.array(z33.number().int().positive()).default(() => []),
2291
+ tags: z33.array(z33.string().min(1)).default(() => []),
2292
+ type: z33.enum(progressTierExclusionTypes)
1721
2293
  });
1722
- var CABProgressTier = z22.object({
2294
+ var CABProgressTier = z33.object({
1723
2295
  /**
1724
2296
  * Order-discount tiers only (onsite `discount`): the reward the order-discount Function applies at
1725
2297
  * cart time, carried so the client can resolve the `{{discountAmount}}` token in this tier's copy.
1726
2298
  * `fixedAmount`: shop-currency CENTS (FX-scaled by the client, like {@link CABProgressTier.threshold});
1727
2299
  * `percentage`: the whole-percent number rendered raw (`{n}%`). Absent on non-discount tiers.
1728
2300
  */
1729
- discount: z22.object({ amount: z22.number().nonnegative(), type: z22.enum(["fixedAmount", "percentage"]) }).optional(),
2301
+ discount: z33.object({ amount: z33.number().nonnegative(), type: z33.enum(["fixedAmount", "percentage"]) }).optional(),
1730
2302
  /**
1731
2303
  * Per-tier exclusion rules (onsite `exclusions` when `exclusions_enabled`): lines they match don't count
1732
2304
  * toward this tier. Empty (the default, and the converter's output for a disabled or ruleless tier) means
1733
2305
  * the tier measures the plain bar-level adjusted progress. Consumers read ONLY the list named by the rule's
1734
2306
  * `type` — the converter leaves the other two at `[]`, so a populated off-type list never reaches the tree.
1735
2307
  */
1736
- exclusions: z22.array(CABProgressTierExclusion).default(() => []),
2308
+ exclusions: z33.array(CABProgressTierExclusion).default(() => []),
1737
2309
  /**
1738
2310
  * Smart-cart product (gift) tiers only (onsite `filter_oos_variants`): when set, the client drops this
1739
2311
  * tier entirely once its gift has NO in-stock eligible variant — the milestone, fill slot and gift
1740
2312
  * controls all disappear, mirroring onsite removing the tier from `bar.tiers` before it renders. Absent
1741
2313
  * on shipping/discount/decorative tiers and left off by default (the tier renders regardless of stock).
1742
2314
  */
1743
- filterOosVariants: z22.boolean().default(false),
2315
+ filterOosVariants: z33.boolean().default(false),
1744
2316
  /**
1745
2317
  * Smart-cart product (gift) tiers only: the reward unlocked at this tier. The client enriches
1746
2318
  * `productId`/`variantIds` to live variants (availability, price) and adds the chosen one to the cart
@@ -1748,10 +2320,10 @@ var CABProgressTier = z22.object({
1748
2320
  * tiers and on tiers with no configured product. Cart add/remove is a later stage; carried here so the
1749
2321
  * tree is complete.
1750
2322
  */
1751
- gift: z22.object({
1752
- productId: z22.number().int().positive(),
1753
- title: z22.string().optional(),
1754
- variantIds: z22.array(z22.number().int().positive()).default(() => [])
2323
+ gift: z33.object({
2324
+ productId: z33.number().int().positive(),
2325
+ title: z33.string().optional(),
2326
+ variantIds: z33.array(z33.number().int().positive()).default(() => [])
1755
2327
  }).optional(),
1756
2328
  /**
1757
2329
  * The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
@@ -1771,7 +2343,7 @@ var CABProgressTier = z22.object({
1771
2343
  * renderer never spells a fallback), which also makes a tree authored before this field render at
1772
2344
  * React's size rather than the old hardcoded 20.
1773
2345
  */
1774
- iconSizePx: z22.number().int().min(1).max(64).default(18),
2346
+ iconSizePx: z33.number().int().min(1).max(64).default(18),
1775
2347
  /**
1776
2348
  * The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
1777
2349
  * type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
@@ -1781,35 +2353,35 @@ var CABProgressTier = z22.object({
1781
2353
  * turns a blank label into an absent key, and a client-authored blank means the same thing — the renderer
1782
2354
  * emits no caption slot rather than an empty one). Plain text — a compact icon+label.
1783
2355
  */
1784
- label: z22.record(z22.string(), z22.string()).optional(),
2356
+ label: z33.record(z33.string(), z33.string()).optional(),
1785
2357
  /**
1786
2358
  * Smart-cart product (gift) tiers only (onsite `product_tier_image`): show the gift's product image in
1787
2359
  * the step-row slot instead of the generic bag icon. The converter can't supply the image URL (config
1788
2360
  * carries only product IDs), so the client resolves it (authoritative Shopify featured image, React
1789
2361
  * parity) and falls back to the bag icon when the flag is off or no image resolves. Default false.
1790
2362
  */
1791
- productTierImage: z22.boolean().default(false),
2363
+ productTierImage: z33.boolean().default(false),
1792
2364
  /**
1793
2365
  * The tier's goal, measured per `unit`. `currency` (default): SHOP-currency cents — the canonical
1794
2366
  * fallback: when the buyer's presentment currency has no entry in `thresholds`, the client
1795
2367
  * multiplies this by the live FX rate (converter maps the v1 `unlock_price`, e.g. `75` → `7500`).
1796
2368
  * `item`: a raw item count — no cents, no FX, no `thresholds` overrides.
1797
2369
  */
1798
- threshold: z22.number().int().nonnegative(),
2370
+ threshold: z33.number().int().nonnegative(),
1799
2371
  /**
1800
2372
  * Per-presentment-currency goal overrides — lowercased ISO currency code → cents (e.g. `{ cad: 9900 }`).
1801
2373
  * When the buyer's currency has an entry, the client uses it verbatim INSTEAD of FX-converting
1802
2374
  * `threshold`, so merchants show intentional, clean per-market amounts (Shopify Markets) rather than a
1803
2375
  * converted base. Defaults to `{}` — no overrides means always fall back to `threshold`.
1804
2376
  */
1805
- thresholds: z22.record(z22.string(), z22.number().int().nonnegative()).default(() => ({})),
2377
+ thresholds: z33.record(z33.string(), z33.number().int().nonnegative()).default(() => ({})),
1806
2378
  /**
1807
2379
  * Smart-cart tier identifier, mirrored onto the gift cart line's `_tier` attribute so the client can
1808
2380
  * match an in-cart gift back to its tier (add on reach, remove on downgrade/decline). Absent on the
1809
2381
  * shipping bar (single unlabeled tier, no gift).
1810
2382
  */
1811
- tierId: z22.string().optional(),
1812
- tierType: z22.enum(progressTierTypes).default(ProgressTierType.shipping),
2383
+ tierId: z33.string().optional(),
2384
+ tierType: z33.enum(progressTierTypes).default(ProgressTierType.shipping),
1813
2385
  /**
1814
2386
  * What this tier's progress is measured in — PER TIER (onsite's `unlock_condition_type` model, NOT
1815
2387
  * the React port's bar-level `tiers[0]` shortcut). `currency` compares money cents against the
@@ -1820,9 +2392,9 @@ var CABProgressTier = z22.object({
1820
2392
  * count carries no injected noun (no English `item`/`items` inflection on localized copy); merchant
1821
2393
  * template text supplies grammatically-neutral wording around the token.
1822
2394
  */
1823
- unit: z22.enum(progressTierUnits).default(ProgressTierUnit.currency)
2395
+ unit: z33.enum(progressTierUnits).default(ProgressTierUnit.currency)
1824
2396
  });
1825
- var CABProgressBarSection = z22.lazy(
2397
+ var CABProgressBarSection = z33.lazy(
1826
2398
  () => CABLayoutSection.omit({ sectionType: true }).extend({
1827
2399
  /**
1828
2400
  * The onsite bar id (smart-cart TPB bars only). The client mirrors it to the cart's `_barId` attribute
@@ -1831,35 +2403,35 @@ var CABProgressBarSection = z22.lazy(
1831
2403
  * checkout reaches the extension without it, so the client writes it or the tier gift's 100% discount
1832
2404
  * (and any tier order discount) never applies. Absent on the v1 shipping bar (no gift tiers, no id).
1833
2405
  */
1834
- barId: z22.string().optional(),
2406
+ barId: z33.string().optional(),
1835
2407
  /** Empty = shown everywhere; otherwise uppercase ISO country codes. */
1836
- countryCodes: z22.array(z22.string().transform((code) => code.trim().toUpperCase())).default(() => []),
2408
+ countryCodes: z33.array(z33.string().transform((code) => code.trim().toUpperCase())).default(() => []),
1837
2409
  /**
1838
2410
  * Uppercase ISO codes where a higher-precedence geo bar applies, so this (default, non-geo) bar is
1839
2411
  * hidden there — replicates React `getbar`'s geo-match-over-default precedence. Empty = never excluded.
1840
2412
  */
1841
- excludeCountryCodes: z22.array(z22.string().transform((code) => code.trim().toUpperCase())).default(() => []),
2413
+ excludeCountryCodes: z33.array(z33.string().transform((code) => code.trim().toUpperCase())).default(() => []),
1842
2414
  /**
1843
2415
  * Measure progress against the PRE-discount subtotal (onsite `exclude_discount_in_subtotal`, and
1844
2416
  * forced on for bars with a discount tier so its own reward can't un-reach the tier). The client
1845
2417
  * adds the cart's discount allocations back to the live subtotal.
1846
2418
  */
1847
- excludeDiscountInSubtotal: z22.boolean().default(false),
2419
+ excludeDiscountInSubtotal: z33.boolean().default(false),
1848
2420
  /** Variant ids the client subtracts from the adjusted subtotal (React `bar.excluded_products`). */
1849
- excludedVariantIds: z22.array(z22.number().int().positive()).default(() => []),
2421
+ excludedVariantIds: z33.array(z33.number().int().positive()).default(() => []),
1850
2422
  /**
1851
2423
  * When the cart holds a subscription item, treat the bar as reached regardless of subtotal (React
1852
2424
  * `free_shipping_on_subscription(s)`). `freeShippingOnAll` is already a zero-threshold tier instead.
1853
2425
  */
1854
- freeShippingOnSubscription: z22.boolean().default(false),
2426
+ freeShippingOnSubscription: z33.boolean().default(false),
1855
2427
  /** Localized heading over the gift controls (onsite `gift_area_title`, default 'FREE GIFTS'). */
1856
- giftAreaTitle: z22.record(z22.string(), z22.string()).default(() => ({ en: "FREE GIFTS" })),
2428
+ giftAreaTitle: z33.record(z33.string(), z33.string()).default(() => ({ en: "FREE GIFTS" })),
1857
2429
  /** Localized gift-control labels (add/remove/heading); a tier's gift `title` beats `yourGift`. */
1858
- giftLabels: z22.object({
1859
- addGift: z22.record(z22.string(), z22.string()).default(() => ({ ...GIFT_LABELS.addGift })),
1860
- remove: z22.record(z22.string(), z22.string()).default(() => ({ ...GIFT_LABELS.remove })),
1861
- selectOptions: z22.record(z22.string(), z22.string()).default(() => ({ ...GIFT_LABELS.selectOptions })),
1862
- yourGift: z22.record(z22.string(), z22.string()).default(() => ({ ...GIFT_LABELS.yourGift }))
2430
+ giftLabels: z33.object({
2431
+ addGift: z33.record(z33.string(), z33.string()).default(() => ({ ...GIFT_LABELS.addGift })),
2432
+ remove: z33.record(z33.string(), z33.string()).default(() => ({ ...GIFT_LABELS.remove })),
2433
+ selectOptions: z33.record(z33.string(), z33.string()).default(() => ({ ...GIFT_LABELS.selectOptions })),
2434
+ yourGift: z33.record(z33.string(), z33.string()).default(() => ({ ...GIFT_LABELS.yourGift }))
1863
2435
  }).default(() => ({
1864
2436
  addGift: { ...GIFT_LABELS.addGift },
1865
2437
  remove: { ...GIFT_LABELS.remove },
@@ -1867,38 +2439,38 @@ var CABProgressBarSection = z22.lazy(
1867
2439
  yourGift: { ...GIFT_LABELS.yourGift }
1868
2440
  })),
1869
2441
  /** The bar's a11y label; the merchant names it in the editor, else this default (React parity). */
1870
- name: z22.string().default("Order progress"),
2442
+ name: z33.string().default("Order progress"),
1871
2443
  /**
1872
2444
  * Bar fill color. An explicit (widget-authored) value is used verbatim; when absent the server
1873
2445
  * fills it with the live shop theme's `buttonBackground` (React `useRebuyTheme` parity). Still
1874
2446
  * absent after that ⇒ the theme fetch failed and the client renders its platform-default bar —
1875
2447
  * optional, not defaulted, so "merchant chose no color" stays distinguishable from any real value.
1876
2448
  */
1877
- progressColor: z22.string().optional(),
1878
- sectionType: z22.literal(SectionType.progressBar).default(SectionType.progressBar),
2449
+ progressColor: z33.string().optional(),
2450
+ sectionType: z33.literal(SectionType.progressBar).default(SectionType.progressBar),
1879
2451
  /**
1880
2452
  * The bar's milestones in the merchant's CONFIGURED order — the renderer does NOT sort them (022
1881
2453
  * O-B2: sorting by resolved threshold reversed configured milestones on out-of-order or mixed-unit
1882
2454
  * count-vs-cents bars). Drives the fill (even slot per tier) and, for smart-cart product tiers, the
1883
2455
  * gift add/remove. The visual tier display lives in `sections` (composable), NOT here.
1884
2456
  */
1885
- tiers: z22.union([z22.array(CABProgressTier), z22.strictObject({}).transform(() => [])]).default(() => [])
2457
+ tiers: z33.union([z33.array(CABProgressTier), z33.strictObject({}).transform(() => [])]).default(() => [])
1886
2458
  })
1887
2459
  );
1888
2460
 
1889
2461
  // src/schema/widgets/checkout-and-beyond/quantity.ts
1890
2462
  import { v7 as uuidv79 } from "uuid";
1891
- import { z as z23 } from "zod";
1892
- var CABQuantitySection = z23.object({
2463
+ import { z as z34 } from "zod";
2464
+ var CABQuantitySection = z34.object({
1893
2465
  ...CABVariableFields,
1894
- errorMessages: z23.record(
1895
- z23.string(),
1896
- z23.object({
2466
+ errorMessages: z34.record(
2467
+ z34.string(),
2468
+ z34.object({
1897
2469
  /** `{{step}}` is interpolated at render — the off-increment message React never gave a merchant key. */
1898
- increment: z23.string().optional(),
1899
- max: z23.string().optional(),
1900
- min: z23.string().optional(),
1901
- neg: z23.string().optional()
2470
+ increment: z34.string().optional(),
2471
+ max: z34.string().optional(),
2472
+ min: z34.string().optional(),
2473
+ neg: z34.string().optional()
1902
2474
  })
1903
2475
  ).default({
1904
2476
  en: {
@@ -1908,18 +2480,18 @@ var CABQuantitySection = z23.object({
1908
2480
  neg: "Quantity cannot be negative"
1909
2481
  }
1910
2482
  }),
1911
- inputType: z23.enum(quantityInputs).default(QuantityInput.select),
1912
- max: z23.number().min(1).max(100).default(10),
1913
- min: z23.number().min(1).default(1),
2483
+ inputType: z34.enum(quantityInputs).default(QuantityInput.select),
2484
+ max: z34.number().min(1).max(100).default(10),
2485
+ min: z34.number().min(1).default(1),
1914
2486
  /** The a11y label for the quantity control; the merchant names it in the editor, else this default. */
1915
- name: z23.string().default("Quantity"),
1916
- sectionId: z23.uuid().default(() => uuidv79()),
1917
- sectionType: z23.literal(SectionType.quantity).default(SectionType.quantity)
2487
+ name: z34.string().default("Quantity"),
2488
+ sectionId: z34.uuid().default(() => uuidv79()),
2489
+ sectionType: z34.literal(SectionType.quantity).default(SectionType.quantity)
1918
2490
  });
1919
2491
 
1920
2492
  // src/schema/widgets/checkout-and-beyond/reviews.ts
1921
2493
  import { v7 as uuidv710 } from "uuid";
1922
- import { z as z24 } from "zod";
2494
+ import { z as z35 } from "zod";
1923
2495
 
1924
2496
  // src/schema/widgets/checkout-and-beyond/reviewLabels.ts
1925
2497
  var REVIEW_LABELS = {
@@ -1982,49 +2554,49 @@ var REVIEW_LABELS = {
1982
2554
  };
1983
2555
 
1984
2556
  // src/schema/widgets/checkout-and-beyond/reviews.ts
1985
- var CABReviewsSection = z24.object({
2557
+ var CABReviewsSection = z35.object({
1986
2558
  ...CABVariableFields,
1987
- color: z24.string().default("#fadb14"),
2559
+ color: z35.string().default("#fadb14"),
1988
2560
  /**
1989
2561
  * Localized review-count templates (`{{reviewCount}}`), one/other plural forms; the client picks the
1990
2562
  * form via `Intl.PluralRules`. Defaults here (schema-first) so every tree localizes without converter
1991
2563
  * or merchant work.
1992
2564
  */
1993
- countLabels: z24.object({
1994
- one: z24.record(z24.string(), z24.string()).default(() => ({ ...REVIEW_LABELS.one })),
1995
- other: z24.record(z24.string(), z24.string()).default(() => ({ ...REVIEW_LABELS.other }))
2565
+ countLabels: z35.object({
2566
+ one: z35.record(z35.string(), z35.string()).default(() => ({ ...REVIEW_LABELS.one })),
2567
+ other: z35.record(z35.string(), z35.string()).default(() => ({ ...REVIEW_LABELS.other }))
1996
2568
  }).default(() => ({ one: { ...REVIEW_LABELS.one }, other: { ...REVIEW_LABELS.other } })),
1997
- name: z24.string().optional(),
1998
- sectionId: z24.uuid().default(() => uuidv710()),
1999
- sectionType: z24.literal(SectionType.reviews).default(SectionType.reviews),
2000
- size: z24.enum(reviewsSizes).default(ReviewsSize.default),
2569
+ name: z35.string().optional(),
2570
+ sectionId: z35.uuid().default(() => uuidv710()),
2571
+ sectionType: z35.literal(SectionType.reviews).default(SectionType.reviews),
2572
+ size: z35.enum(reviewsSizes).default(ReviewsSize.default),
2001
2573
  /**
2002
2574
  * Star outline color, interpolated into a style by the renderer, so it is hex-constrained like
2003
2575
  * `CABButtonSection.color`. The legacy source (`PostPurchaseSettings.styles.starStrokeColor`) is a free
2004
2576
  * string: its converter must validate and fall back to this default rather than let one odd value drop
2005
2577
  * the whole reviews section.
2006
2578
  */
2007
- strokeColor: z24.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
2579
+ strokeColor: z35.string().regex(HEX_COLOR_REGEX).default("#fadb14"),
2008
2580
  /** Star outline width in px; `0` draws no outline. The legacy `styles.strokeWidth` is a STRING ('1') — its converter coerces. */
2009
- strokeWidth: z24.number().nonnegative().default(0)
2581
+ strokeWidth: z35.number().nonnegative().default(0)
2010
2582
  });
2011
2583
 
2012
2584
  // src/schema/widgets/checkout-and-beyond/separator.ts
2013
2585
  import { v7 as uuidv711 } from "uuid";
2014
- import { z as z25 } from "zod";
2015
- var CABSeparatorSection = z25.object({
2586
+ import { z as z36 } from "zod";
2587
+ var CABSeparatorSection = z36.object({
2016
2588
  ...CABVariableFields,
2017
- sectionId: z25.uuid().default(() => uuidv711()),
2018
- sectionType: z25.literal(SectionType.separator).default(SectionType.separator)
2589
+ sectionId: z36.uuid().default(() => uuidv711()),
2590
+ sectionType: z36.literal(SectionType.separator).default(SectionType.separator)
2019
2591
  });
2020
2592
 
2021
2593
  // src/schema/widgets/checkout-and-beyond/variants.ts
2022
2594
  import { isEmpty, pickBy, slice } from "es-toolkit/compat";
2023
2595
  import { v7 as uuidv712 } from "uuid";
2024
- import { z as z26 } from "zod";
2025
- var CABVariantsSection = z26.object({
2596
+ import { z as z37 } from "zod";
2597
+ var CABVariantsSection = z37.object({
2026
2598
  ...CABVariableFields,
2027
- hideOutOfStockVariants: z26.boolean().default(false),
2599
+ hideOutOfStockVariants: z37.boolean().default(false),
2028
2600
  /**
2029
2601
  * The menu's own label per buyer locale (`selectLanguage`), for the whole-variant (`single`) and the
2030
2602
  * product-group menus; `multiple` labels each option's menu by its name. Optional and additive
@@ -2035,30 +2607,30 @@ var CABVariantsSection = z26.object({
2035
2607
  * `selectLanguage` returning `''` — which a renderer's `??` fallback would show as an empty menu label.
2036
2608
  * A label left with no entries reads as absent.
2037
2609
  */
2038
- label: z26.record(z26.string(), z26.string()).transform((label2) => {
2610
+ label: z37.record(z37.string(), z37.string()).transform((label2) => {
2039
2611
  const filled = pickBy(label2, (value) => value.trim() !== "");
2040
2612
  return isEmpty(filled) ? void 0 : filled;
2041
2613
  }).optional().catch(void 0),
2042
- name: z26.string().optional(),
2043
- sectionId: z26.uuid().default(() => uuidv712()),
2044
- sectionType: z26.literal(SectionType.variants).default(SectionType.variants),
2045
- selector: z26.enum(slice(variantSelectors, 0, 3)).default(VariantSelector.menu),
2614
+ name: z37.string().optional(),
2615
+ sectionId: z37.uuid().default(() => uuidv712()),
2616
+ sectionType: z37.literal(SectionType.variants).default(SectionType.variants),
2617
+ selector: z37.enum(slice(variantSelectors, 0, 3)).default(VariantSelector.menu),
2046
2618
  /** `productGroup` is additive (REB-24817): a renderer without group support must render nothing for it. */
2047
- variantMode: z26.enum(variantsModes).default(VariantsMode.multiple)
2619
+ variantMode: z37.enum(variantsModes).default(VariantsMode.multiple)
2048
2620
  });
2049
2621
 
2050
2622
  // src/schema/widgets/checkout-and-beyond/shared.ts
2051
- var CABBorder = z27.object({
2052
- radius: z27.enum(borderRadii).default(BorderRadius.base),
2053
- style: z27.enum(borderStyles).default(BorderStyle.none),
2054
- width: z27.enum(borderWidths).default(BorderWidth.base)
2623
+ var CABBorder = z38.object({
2624
+ radius: z38.enum(borderRadii).default(BorderRadius.base),
2625
+ style: z38.enum(borderStyles).default(BorderStyle.none),
2626
+ width: z38.enum(borderWidths).default(BorderWidth.base)
2055
2627
  }).default({
2056
2628
  radius: BorderRadius.base,
2057
2629
  style: BorderStyle.none,
2058
2630
  width: BorderWidth.base
2059
2631
  });
2060
- var CABSection = z27.lazy(
2061
- () => z27.discriminatedUnion("sectionType", [
2632
+ var CABSection = z38.lazy(
2633
+ () => z38.discriminatedUnion("sectionType", [
2062
2634
  CABBannerSection,
2063
2635
  CABButtonSection,
2064
2636
  CABCarouselSection,
@@ -2081,17 +2653,17 @@ var CABSection = z27.lazy(
2081
2653
  );
2082
2654
 
2083
2655
  // src/schema/widgets/checkout-and-beyond/image.ts
2084
- var CABImageSectionFields = z28.object({
2656
+ var CABImageSectionFields = z39.object({
2085
2657
  ...CABVariableFields,
2086
- altText: z28.string().refine(checkForHTML, NO_HTML).default(""),
2658
+ altText: z39.string().refine(checkForHTML, NO_HTML).default(""),
2087
2659
  /**
2088
2660
  * Image width÷height (e.g. `46` for the progress-bar fill); `null` renders at natural size. Any POSITIVE
2089
2661
  * ratio — never limited to 1, but never `0`/negative either: a renderer dividing by it would get `Infinity`,
2090
2662
  * and CSS `aspect-ratio` drops a negative value and silently reverts to natural size.
2091
2663
  */
2092
- aspectRatio: z28.number().positive().nullable().default(null),
2093
- border: z28.lazy(() => CABBorder),
2094
- buttonField: z28.enum(buttonFields).optional(),
2664
+ aspectRatio: z39.number().positive().nullable().default(null),
2665
+ border: z39.lazy(() => CABBorder),
2666
+ buttonField: z39.enum(buttonFields).optional(),
2095
2667
  /**
2096
2668
  * `icon` renders `source` as a single `s-icon` by name (the uber-image path covers icons too), reading
2097
2669
  * `iconSize`/`iconTone`. The other values are a harmless informational holdover from the previous
@@ -2110,30 +2682,30 @@ var CABImageSectionFields = z28.object({
2110
2682
  * behavior degraded gracefully and told us about it. The client-assembled half of the invariant was never
2111
2683
  * the risky half.
2112
2684
  */
2113
- category: z28.enum(["gallery", "icon", "icons", "payment-methods", "secure-checkout"]).nullable().default(null),
2685
+ category: z39.enum(["gallery", "icon", "icons", "payment-methods", "secure-checkout"]).nullable().default(null),
2114
2686
  /** `icon`-mode only: the `s-icon` `size`; `null` ⇒ the component default (`base`). Ignored by image treatments. */
2115
- iconSize: z28.enum(iconSizes).nullable().default(null),
2687
+ iconSize: z39.enum(iconSizes).nullable().default(null),
2116
2688
  /** `icon`-mode only: the `s-icon` `tone`; `null` ⇒ the component default (`auto`). Ignored by image treatments. */
2117
- iconTone: z28.enum(iconTones).nullable().default(null),
2118
- name: z28.string().optional(),
2119
- naturalHeight: z28.number().default(0),
2120
- naturalWidth: z28.number().default(0),
2689
+ iconTone: z39.enum(iconTones).nullable().default(null),
2690
+ name: z39.string().optional(),
2691
+ naturalHeight: z39.number().default(0),
2692
+ naturalWidth: z39.number().default(0),
2121
2693
  /**
2122
2694
  * `contain` (the component default): dimensionless images render in s-image's 1/1 default frame, and
2123
2695
  * `cover` would crop non-square art (payment badges lost their edges — 15993); `contain` letterboxes.
2124
2696
  */
2125
- objectFit: z28.enum(objectFits).default(ObjectFit.contain),
2126
- sectionId: z28.uuid().default(() => uuidv713()),
2127
- sectionType: z28.literal(SectionType.image).default(SectionType.image),
2697
+ objectFit: z39.enum(objectFits).default(ObjectFit.contain),
2698
+ sectionId: z39.uuid().default(() => uuidv713()),
2699
+ sectionType: z39.literal(SectionType.image).default(SectionType.image),
2128
2700
  /**
2129
2701
  * An image URL or dynamic token, an empty default, or — when `category: 'icon'` — an icon NAME. Icon mode
2130
2702
  * takes a name or `''` only, NOT a token: no renderer resolves a token to an icon today, so accepting one
2131
2703
  * would promise a capability that doesn't exist. Widen the refine's carve-out to `DYNAMIC_TOKEN_REGEX` when a
2132
2704
  * server-chosen tier icon actually needs it.
2133
2705
  */
2134
- source: z28.union([z28.url(), z28.literal(""), z28.string().regex(DYNAMIC_TOKEN_REGEX), z28.enum(iconNames)]).default(""),
2706
+ source: z39.union([z39.url(), z39.literal(""), z39.string().regex(DYNAMIC_TOKEN_REGEX), z39.enum(iconNames)]).default(""),
2135
2707
  thumbnails: CABImageThumbnails.optional(),
2136
- width: z28.number().default(100)
2708
+ width: z39.number().default(100)
2137
2709
  });
2138
2710
  var iconNameSet = new Set(iconNames);
2139
2711
  var isIconName = (source2) => iconNameSet.has(source2);
@@ -2284,7 +2856,7 @@ var convertContentBlockTextToV2 = (language3, text2 = literalText, textAlign = "
2284
2856
  };
2285
2857
 
2286
2858
  // src/schema/widgets/checkout-and-beyond/root.ts
2287
- import { z as z29 } from "zod";
2859
+ import { z as z40 } from "zod";
2288
2860
 
2289
2861
  // src/schema/widgets/checkout-and-beyond/uiLabels.ts
2290
2862
  var UI_LABELS = {
@@ -2304,8 +2876,8 @@ var UI_LABELS = {
2304
2876
  };
2305
2877
 
2306
2878
  // src/schema/widgets/checkout-and-beyond/root.ts
2307
- var localized = (fallback) => z29.record(z29.string(), z29.string()).default(() => ({ ...fallback })).transform((labels) => Object.keys(labels).length > 0 ? labels : { ...fallback });
2308
- var CABUILabels = z29.object({
2879
+ var localized = (fallback) => z40.record(z40.string(), z40.string()).default(() => ({ ...fallback })).transform((labels) => Object.keys(labels).length > 0 ? labels : { ...fallback });
2880
+ var CABUILabels = z40.object({
2309
2881
  currentPage: localized(UI_LABELS.currentPage),
2310
2882
  guest: localized(UI_LABELS.guest),
2311
2883
  loadingOptions: localized(UI_LABELS.loadingOptions),
@@ -2330,37 +2902,37 @@ var CABUILabels = z29.object({
2330
2902
  subscriptionOptions: { ...UI_LABELS.subscriptionOptions },
2331
2903
  updating: { ...UI_LABELS.updating }
2332
2904
  }));
2333
- var TargetArea = z29.object({
2334
- fill: z29.boolean().optional(),
2335
- flip: z29.enum(["both", "horizontal", "vertical"]).optional(),
2336
- icon: z29.string(),
2337
- label: z29.string(),
2338
- width: z29.string()
2905
+ var TargetArea = z40.object({
2906
+ fill: z40.boolean().optional(),
2907
+ flip: z40.enum(["both", "horizontal", "vertical"]).optional(),
2908
+ icon: z40.string(),
2909
+ label: z40.string(),
2910
+ width: z40.string()
2339
2911
  }).default(targetAreas[EditorMode.checkoutExtension][1]);
2340
- var CABTracking = z29.object({
2341
- enableAttribution: z29.boolean().nullish().transform((value) => value ?? true),
2342
- enableSource: z29.boolean().nullish().transform((value) => value ?? true),
2343
- enableWidget: z29.boolean().nullish().transform((value) => value ?? true)
2912
+ var CABTracking = z40.object({
2913
+ enableAttribution: z40.boolean().nullish().transform((value) => value ?? true),
2914
+ enableSource: z40.boolean().nullish().transform((value) => value ?? true),
2915
+ enableWidget: z40.boolean().nullish().transform((value) => value ?? true)
2344
2916
  }).nullish().transform((value) => value ?? { enableAttribution: true, enableSource: true, enableWidget: true });
2345
- var CABExperiment = z29.object({
2346
- elementId: z29.number(),
2347
- experimentId: z29.number(),
2348
- experimentType: z29.string(),
2349
- selectedId: z29.number()
2917
+ var CABExperiment = z40.object({
2918
+ elementId: z40.number(),
2919
+ experimentId: z40.number(),
2920
+ experimentType: z40.string(),
2921
+ selectedId: z40.number()
2350
2922
  });
2351
2923
  var CABRootSection = CABLayoutSection.extend({
2352
- editorMode: z29.enum(editorModes).default(EditorMode.checkoutExtension),
2924
+ editorMode: z40.enum(editorModes).default(EditorMode.checkoutExtension),
2353
2925
  experiment: CABExperiment.optional(),
2354
2926
  // Same rule as CABTracking above: the v1 converters pass raw legacy `previewMode` through, so a
2355
2927
  // `null` must fall back rather than throw the widget; malformed values still throw.
2356
- previewMode: z29.boolean().nullish().transform((value) => value ?? false),
2357
- storeId: z29.number().nullable().default(null),
2928
+ previewMode: z40.boolean().nullish().transform((value) => value ?? false),
2929
+ storeId: z40.number().nullable().default(null),
2358
2930
  targetArea: TargetArea.nullable(),
2359
2931
  tracking: CABTracking,
2360
- type: z29.enum(widgetTypes).default(WidgetType.ui_extension_content_block),
2932
+ type: z40.enum(widgetTypes).default(WidgetType.ui_extension_content_block),
2361
2933
  uiLabels: CABUILabels,
2362
- version: z29.literal(2).default(2),
2363
- widgetId: z29.number().nullable().default(null)
2934
+ version: z40.literal(2).default(2),
2935
+ widgetId: z40.number().nullable().default(null)
2364
2936
  });
2365
2937
 
2366
2938
  // src/transforms/v1Location.ts
@@ -3853,172 +4425,10 @@ var convertOfferToV2 = ({ id, name, settings }) => {
3853
4425
  var parseShielded = (schema, raw, transform = (input) => input) => schema.parse(transform(convertNumericObjects(deepCamelCase(raw))));
3854
4426
 
3855
4427
  // src/transforms/postPurchase/convertMessagePreview.ts
3856
- import { z as z40 } from "zod";
4428
+ import { z as z47 } from "zod";
3857
4429
 
3858
4430
  // src/transforms/postPurchase/convertCalloutBlock.ts
3859
- import { z as z32 } from "zod";
3860
-
3861
- // src/schema/widgets/post-purchase-offer/postPurchaseBlocks.ts
3862
- import { get as get2, isEqual } from "es-toolkit/compat";
3863
- import { v4 as uuidv4 } from "uuid";
3864
- import { z as z31 } from "zod";
3865
-
3866
- // src/schema/widgets/post-purchase-offer/constants.ts
3867
- import { z as z30 } from "zod";
3868
- var blockTypes = [
3869
- "callout_banner",
3870
- "image",
3871
- "image_content",
3872
- "pay_button",
3873
- "product_information",
3874
- "separator",
3875
- "text",
3876
- "timer"
3877
- ];
3878
- var blockType = z30.enum(blockTypes);
3879
- var BlockType = blockType.enum;
3880
-
3881
- // src/schema/widgets/post-purchase-offer/postPurchaseBlocks.ts
3882
- var TextAlignment2 = z31.union([z31.enum(["leading", "center", "trailing"]), z31.unknown().transform(() => "leading")]);
3883
- var TextSize = z31.union([
3884
- z31.enum(["xsmall", "small", "base", "default", "medium", "large", "xlarge"]),
3885
- z31.unknown().transform(() => "medium")
3886
- ]);
3887
- var CalloutBannerBlock = z31.object({
3888
- id: z31.string().optional().default(() => uuidv4()),
3889
- name: z31.string().optional(),
3890
- settings: z31.object({
3891
- superTitle: z31.union([z31.string(), TiptapDocument]).default(() => "Limited-time Offer"),
3892
- title: z31.union([z31.string(), TiptapDocument]).default(() => "Add this customer favorite to your order")
3893
- }),
3894
- styles: z31.object({
3895
- superTitleEmphasized: z31.boolean().default(false),
3896
- superTitleSize: TextSize.default("large"),
3897
- textAlignment: TextAlignment2.default("center"),
3898
- titleEmphasized: z31.boolean().default(true),
3899
- titleSize: TextSize.default("large")
3900
- }),
3901
- type: z31.literal(BlockType.callout_banner).default(BlockType.callout_banner)
3902
- });
3903
- var ImageBlock = z31.object({
3904
- id: z31.string().optional().default(() => uuidv4()),
3905
- name: z31.string().optional(),
3906
- settings: z31.union([
3907
- z31.object({
3908
- /** This isn't used in the post-purchase offer widget */
3909
- alt: z31.string().optional().default(""),
3910
- image: z31.union([z31.literal(""), z31.url()]).default(""),
3911
- imageSize: z31.number().default(20),
3912
- imageType: z31.enum(["URL", "UPLOAD"]).default("URL")
3913
- }),
3914
- z31.array(z31.never()).transform(() => ({}))
3915
- ]),
3916
- type: z31.literal(BlockType.image).default(BlockType.image)
3917
- });
3918
- var ImageContentBlock = z31.object({
3919
- id: z31.string().optional().default(() => uuidv4()),
3920
- name: z31.string().optional(),
3921
- settings: z31.object({
3922
- alt: z31.string().optional().default(""),
3923
- description: z31.union([z31.string(), TiptapDocument, z31.null()]).default(
3924
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
3925
- ),
3926
- descriptionSize: TextSize.default("medium"),
3927
- horizontalAlignment: TextAlignment2.default("leading"),
3928
- image: z31.union([z31.literal(""), z31.url()]).default(""),
3929
- imagePlacement: z31.enum(["bottom", "left", "right", "top"]).optional().default("left"),
3930
- imageSize: z31.number().default(20),
3931
- superTitle: z31.union([z31.string(), TiptapDocument]).default("Lorem Ipsum!"),
3932
- superTitleSize: TextSize.default("medium"),
3933
- /** This isn't configurable in the admin-nextjs editor */
3934
- swapContent: z31.boolean().optional().default(false),
3935
- title: z31.union([z31.string(), TiptapDocument]).nullable().default("Lorem ipsum dolor sit amet"),
3936
- titleSize: TextSize.default("medium")
3937
- }),
3938
- type: z31.literal(BlockType.image_content).default(BlockType.image_content)
3939
- });
3940
- var PayButtonBlock = z31.object({
3941
- id: z31.string().optional().default(() => uuidv4()),
3942
- name: z31.string().optional(),
3943
- settings: z31.union([
3944
- z31.object({
3945
- label: z31.string().default("Pay Now")
3946
- }),
3947
- z31.array(z31.never()).transform(() => ({
3948
- label: "Pay Now"
3949
- }))
3950
- ]),
3951
- styles: z31.union([
3952
- z31.object({
3953
- buttonWidth: z31.number().min(10).max(100).default(50)
3954
- }),
3955
- z31.array(z31.never()).transform(() => ({
3956
- buttonWidth: 50
3957
- }))
3958
- ]),
3959
- type: z31.literal(BlockType.pay_button).default(BlockType.pay_button)
3960
- });
3961
- var ProductInformationBlock = z31.object({
3962
- /** This isn't configurable in the admin-nextjs editor */
3963
- hideProductImage: z31.boolean().optional().default(false),
3964
- id: z31.string().optional().default(() => uuidv4()),
3965
- name: z31.string().optional(),
3966
- type: z31.literal(BlockType.product_information).default(BlockType.product_information)
3967
- });
3968
- var SeparatorBlock = z31.object({
3969
- id: z31.string().optional().default(() => uuidv4()),
3970
- name: z31.string().optional(),
3971
- type: z31.literal(BlockType.separator).default(BlockType.separator)
3972
- });
3973
- var TextBlock = z31.object({
3974
- id: z31.string().optional().default(() => uuidv4()),
3975
- name: z31.string().optional(),
3976
- settings: z31.object({
3977
- description: z31.union([z31.string(), TiptapDocument]).optional().default(
3978
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
3979
- ),
3980
- /** This isn't used in the post-purchase offer widget */
3981
- descriptionSize: TextSize.default("medium"),
3982
- horizontalAlignment: TextAlignment2.default("leading"),
3983
- superTitle: z31.union([z31.string(), TiptapDocument]).default("Super Title"),
3984
- /** This isn't used in the post-purchase offer widget */
3985
- superTitleSize: TextSize.default("medium"),
3986
- title: z31.union([z31.string(), TiptapDocument]).nullable().default("Lorem ipsum dolor sit amet."),
3987
- /** This isn't used in the post-purchase offer widget */
3988
- titleSize: TextSize.default("medium")
3989
- }),
3990
- type: z31.literal(BlockType.text).default(BlockType.text)
3991
- });
3992
- var TimerBlock = z31.object({
3993
- id: z31.string().optional().default(() => uuidv4()),
3994
- name: z31.string().optional(),
3995
- settings: z31.object({
3996
- durationMinutes: z31.number().min(0).default(10),
3997
- durationSeconds: z31.union([z31.number().min(0), z31.unknown().transform(() => 0)]).default(0),
3998
- endOffer: z31.boolean().default(true),
3999
- title: z31.union([z31.string(), TiptapDocument]).default("Hurry \u2014 your special offer ends in:")
4000
- }),
4001
- styles: z31.object({
4002
- textAlignment: TextAlignment2.default("center"),
4003
- timerBackgroundColor: z31.enum(["info", "warning", "success", "critical"]).default("warning"),
4004
- timerEmphasized: z31.union([z31.boolean(), z31.unknown().transform(() => true)]).default(true),
4005
- timerSize: TextSize.default("medium"),
4006
- timerWidth: z31.number().min(10).max(100).optional().default(100),
4007
- titleEmphasized: z31.union([z31.boolean(), z31.unknown().transform(() => true)]).default(false),
4008
- titleSize: TextSize.default("medium")
4009
- }),
4010
- type: z31.literal(BlockType.timer).default(BlockType.timer)
4011
- });
4012
- var PostPurchaseBlock = z31.discriminatedUnion("type", [
4013
- CalloutBannerBlock,
4014
- ImageBlock,
4015
- ImageContentBlock,
4016
- PayButtonBlock,
4017
- ProductInformationBlock,
4018
- SeparatorBlock,
4019
- TextBlock,
4020
- TimerBlock
4021
- ]);
4431
+ import { z as z41 } from "zod";
4022
4432
 
4023
4433
  // src/transforms/postPurchase/styledTextRun.ts
4024
4434
  var sizes = {
@@ -4042,7 +4452,7 @@ var styledTextRun = (text2, emphasized, size) => ({
4042
4452
  // src/transforms/postPurchase/convertCalloutBlock.ts
4043
4453
  var fields = ["superTitle", "title"];
4044
4454
  var Shell = CalloutBannerBlock.pick({ styles: true }).extend({
4045
- settings: z32.object({ superTitle: z32.unknown().optional(), title: z32.unknown().optional() })
4455
+ settings: z41.object({ superTitle: z41.unknown().optional(), title: z41.unknown().optional() })
4046
4456
  });
4047
4457
  var convertCalloutBlock = (raw, report) => {
4048
4458
  const omit = (detail) => {
@@ -4077,7 +4487,7 @@ var convertCalloutBlock = (raw, report) => {
4077
4487
  };
4078
4488
 
4079
4489
  // src/transforms/postPurchase/convertImageFrame.ts
4080
- import { z as z33 } from "zod";
4490
+ import { z as z42 } from "zod";
4081
4491
 
4082
4492
  // src/transforms/postPurchase/sizeImageSource.ts
4083
4493
  var sizeImageSource = (source2, size) => {
@@ -4089,7 +4499,7 @@ var sizeImageSource = (source2, size) => {
4089
4499
  };
4090
4500
 
4091
4501
  // src/transforms/postPurchase/convertImageFrame.ts
4092
- var Source = z33.url().default("https://cdn.shopify.com/s/files/1/0605/0555/5177/files/placeholder.jpg?v=1657762972");
4502
+ var Source = z42.url().default("https://cdn.shopify.com/s/files/1/0605/0555/5177/files/placeholder.jpg?v=1657762972");
4093
4503
  var DEFAULT_IMAGE_SIZE = 20;
4094
4504
  var convertImageFrame = (raw) => {
4095
4505
  const parsed = ImageBlock.shape.settings.options[0].safeParse(raw);
@@ -4135,7 +4545,7 @@ var convertImageBlock = (raw, report) => {
4135
4545
  };
4136
4546
 
4137
4547
  // src/transforms/postPurchase/convertImageContentBlock.ts
4138
- import { z as z35 } from "zod";
4548
+ import { z as z44 } from "zod";
4139
4549
 
4140
4550
  // src/transforms/postPurchase/convertImageContentText.ts
4141
4551
  var fields2 = ["superTitle", "title", "description"];
@@ -4160,286 +4570,98 @@ var convertImageContentText = (settings, report) => {
4160
4570
  } : text2;
4161
4571
  const section = CABTextSection.safeParse({ content: { en: content } });
4162
4572
  return section.success ? [section.data] : omit();
4163
- });
4164
- return CABLayoutSection.parse({ alignment: { horizontal }, sections });
4165
- };
4166
-
4167
- // src/transforms/postPurchase/imageContentSettings.ts
4168
- import { z as z34 } from "zod";
4169
- var ImageContentSettings = ImageContentBlock.shape.settings.pick({
4170
- descriptionSize: true,
4171
- horizontalAlignment: true,
4172
- superTitleSize: true,
4173
- swapContent: true,
4174
- titleSize: true
4175
- }).extend({
4176
- alt: z34.unknown().optional(),
4177
- description: z34.unknown().optional(),
4178
- image: z34.unknown().optional(),
4179
- imagePlacement: ImageContentBlock.shape.settings.shape.imagePlacement.unwrap().nullable(),
4180
- imageSize: ImageContentBlock.shape.settings.shape.imageSize.pipe(z34.number().positive()),
4181
- superTitle: z34.unknown().optional(),
4182
- title: z34.unknown().optional()
4183
- });
4184
-
4185
- // src/transforms/postPurchase/convertImageContentBlock.ts
4186
- var Shell2 = z35.object({ settings: ImageContentSettings, type: z35.literal("image_content") });
4187
- var PROPORTIONAL_BREAK_AT = 500;
4188
- var convertImageContentBlock = (raw, report) => {
4189
- const shell = Shell2.safeParse(raw);
4190
- if (!shell.success) {
4191
- report?.({ detail: "Unreadable post-purchase image-content block", kind: "section" });
4192
- return [];
4193
- }
4194
- const settings = shell.data.settings;
4195
- const alt = typeof settings.alt === "string" ? settings.alt : void 0;
4196
- if (settings.alt !== void 0 && alt === void 0) {
4197
- report?.({ detail: "Unreadable post-purchase image-content alt text", kind: "section" });
4198
- }
4199
- const image = convertImageFrame({ alt, image: settings.image, imageSize: settings.imageSize });
4200
- if (!image) report?.({ detail: "Unreadable post-purchase image-content image", kind: "section" });
4201
- const text2 = convertImageContentText(settings, report);
4202
- const textFrame = CABLayoutSection.parse({ direction: "grid", grid: { columns: ["100%"] }, sections: [text2] });
4203
- const placement = settings.imagePlacement ?? (settings.swapContent ? "right" : "left");
4204
- const imageFirst = placement === "left" || placement === "top";
4205
- const sections = imageFirst ? [image, textFrame] : [textFrame, image];
4206
- const ordered = sections.filter((section) => section !== null);
4207
- const inner = placement === "left" || placement === "right" ? CABLayoutSection.parse({
4208
- sections: ordered,
4209
- spacing: "loose",
4210
- wrapping: {
4211
- breakAt: settings.imageSize <= 1 ? PROPORTIONAL_BREAK_AT : settings.imageSize,
4212
- maxPerLine: 2
4213
- }
4214
- }) : CABLayoutSection.parse({
4215
- direction: "grid",
4216
- grid: { columns: ["100%"] },
4217
- sections: [CABLayoutSection.parse({ sections: ordered })],
4218
- width: 95
4219
- });
4220
- return [CABLayoutSection.parse({ frame: "offer-content", sections: [inner], spacing: "none" })];
4221
- };
4222
-
4223
- // src/transforms/postPurchase/productCardPay.ts
4224
- import { mapValues } from "es-toolkit";
4225
- var PAY_NOW = { en: "Pay Now" };
4226
- var DECLINE_THIS_OFFER = { en: "Decline this offer" };
4227
- var NOT_AVAILABLE_DEFAULTS = ["Not available"];
4228
- var soldOutSlot = ({ language: language3 }) => labelSlot("soldOut", OFFER_LABELS.soldOut, language3.global.notAvailable, [], NOT_AVAILABLE_DEFAULTS);
4229
- var productCardPay = (settings) => {
4230
- const payNow = labelSlot("payNow", PAY_NOW, settings.language.global.payNow);
4231
- const priced = settings.language.displayCtaPrice ? { ...payNow, content: mapValues(payNow.content, (label2) => `${label2} \xB7 {{total}}`) } : payNow;
4232
- return [addToOrderButton("primary", [priced, soldOutSlot(settings)])];
4233
- };
4234
- var productCardDecline = ({ language: language3 }) => [
4235
- {
4236
- action: "declineOffer",
4237
- buttonStyle: "secondary",
4238
- sections: [labelSlot("declineOffer", DECLINE_THIS_OFFER, language3.global.declineThisOffer)],
4239
- sectionType: "button"
4240
- }
4241
- ];
4242
-
4243
- // src/transforms/postPurchase/productCardSettings.ts
4244
- import { mapValues as mapValues2 } from "es-toolkit";
4245
- import { z as z38 } from "zod";
4246
-
4247
- // src/schema/widgets/post-purchase-offer/postPurchaseSettings.ts
4248
- import { z as z37 } from "zod";
4249
-
4250
- // src/schema/widgets/post-purchase-offer/common.ts
4251
- import { replace, toNumber, toString } from "es-toolkit/compat";
4252
- import { z as z36 } from "zod";
4253
- var strictNumber = z36.union([
4254
- z36.number(),
4255
- z36.string().transform((value) => toNumber(replace(toString(value), /[^0-9.]/g, "")) || 0),
4256
- z36.unknown().transform(() => 0)
4257
- ]);
4258
-
4259
- // src/schema/widgets/post-purchase-offer/postPurchaseSettings.ts
4260
- var PostPurchaseSettings = z37.object({
4261
- advance: z37.object({ removePartialOrder: z37.boolean().default(true) }).optional().default(() => ({ removePartialOrder: true })),
4262
- blocks: z37.union([z37.array(PostPurchaseBlock), z37.unknown().transform(() => [])]).default(() => [
4263
- PostPurchaseBlock.parse({ settings: {}, styles: {}, type: BlockType.timer }),
4264
- PostPurchaseBlock.parse({ settings: {}, styles: {}, type: BlockType.callout_banner }),
4265
- PostPurchaseBlock.parse({ type: BlockType.product_information })
4266
- ]),
4267
- columnLayout: z37.boolean().optional(),
4268
- discount: z37.object({
4269
- amount: strictNumber.default(30),
4270
- shouldShowSavingText: z37.boolean().optional().default(true),
4271
- type: z37.enum(["none", "fixed_amount", "percentage"]).default("none")
4272
- }),
4273
- discountedFrom: z37.enum(["price", "compare_at_price"]).optional().default("price"),
4274
- endpoint: z37.string().optional(),
4275
- integrations: z37.object({
4276
- judgeme: z37.boolean().default(false),
4277
- junip: z37.boolean().optional().default(false),
4278
- klaviyo: z37.boolean().default(false),
4279
- loox: z37.boolean().default(false),
4280
- okendo: z37.boolean().default(false),
4281
- opinew: z37.boolean().optional().default(false),
4282
- reviewsio: z37.boolean().optional().default(false),
4283
- stamped: z37.boolean().default(false),
4284
- yotpo: z37.boolean().default(false)
4285
- }),
4286
- key: z37.string().optional(),
4287
- language: z37.object({
4288
- description: z37.union([z37.string(), z37.unknown().transform(() => "")]).default(""),
4289
- descriptionEnabled: z37.boolean().optional().default(false),
4290
- descriptionRte: z37.object({
4291
- content: z37.array(z37.unknown()),
4292
- type: z37.string()
4293
- }).optional(),
4294
- descriptionSpacing: z37.enum(["none", "xtight", "tight", "default", "loose", "xloose"]).optional(),
4295
- displayCtaPrice: z37.boolean().default(true),
4296
- global: z37.object({
4297
- declineAll: z37.string().optional().default("Decline All"),
4298
- declineThisOffer: z37.string().default("Decline this offer"),
4299
- free: z37.string().optional().default("Free"),
4300
- notAvailable: z37.string().optional().default("Not available"),
4301
- payNow: z37.string().default("Pay Now"),
4302
- payNowUpgradeToSubscription: z37.string().optional().default("Upgrade to Subscription"),
4303
- quantity: z37.string().default("Quantity"),
4304
- /** This isn't configurable in the admin-nextjs editor */
4305
- recurringShipping: z37.string().optional().default("Recurring shipping"),
4306
- recurringSubtotal: z37.string().default("Recurring subtotal"),
4307
- reviewsPlural: z37.string().optional().default("Reviews"),
4308
- reviewsSingular: z37.string().optional().default("Review"),
4309
- save: z37.union([z37.string(), z37.unknown().transform(() => "Save")]).optional().default("Save"),
4310
- shipping: z37.string().default("Shipping"),
4311
- subscriptionOption: z37.string().optional().default("Subscribe and Save"),
4312
- subtotal: z37.string().default("Subtotal"),
4313
- taxes: z37.string().default("Taxes"),
4314
- total: z37.string().default("Total")
4315
- }).optional().default(() => ({
4316
- declineAll: "Decline All",
4317
- declineThisOffer: "Decline this offer",
4318
- free: "Free",
4319
- notAvailable: "Not available",
4320
- payNow: "Pay Now",
4321
- payNowUpgradeToSubscription: "Upgrade to Subscription",
4322
- quantity: "Quantity",
4323
- /** This isn't configurable in the admin-nextjs editor */
4324
- recurringShipping: "Recurring shipping",
4325
- recurringSubtotal: "Recurring subtotal",
4326
- reviewsPlural: "Reviews",
4327
- reviewsSingular: "Review",
4328
- save: "Save",
4329
- shipping: "Shipping",
4330
- subscriptionOption: "Subscribe and Save",
4331
- subtotal: "Subtotal",
4332
- taxes: "Taxes",
4333
- total: "Total"
4334
- })),
4335
- globalTranslation: z37.string().optional().default("english")
4336
- }),
4337
- metafields: z37.boolean().default(false),
4338
- /** This isn't configurable in the admin-nextjs editor */
4339
- offer: z37.object({
4340
- count: z37.number().default(1)
4341
- }),
4342
- offersLayout: z37.object({
4343
- itemsPerPage: z37.number().optional(),
4344
- type: z37.enum(["list", "grid", "carousel"]).default("list")
4345
- }).optional().default({
4346
- type: "list"
4347
- }),
4348
- /** This isn't configurable in the admin-nextjs editor */
4349
- previewMode: z37.boolean().default(false),
4350
- productLimit: z37.number().optional().default(1),
4351
- productOptions: z37.object({
4352
- /** This isn't configurable in the admin-nextjs editor */
4353
- hideOutOfStockVariants: z37.boolean().optional().default(false),
4354
- matchVariant: z37.boolean().optional().default(true),
4355
- showDeclineAll: z37.enum(["above", "below", "both", "none"]).optional(),
4356
- showShipping: z37.boolean().optional().default(true),
4357
- showSubtotal: z37.boolean().optional().default(true),
4358
- showTaxes: z37.boolean().optional().default(true),
4359
- showTotal: z37.boolean().optional().default(true),
4360
- showVariantSelectors: z37.boolean().optional().default(true),
4361
- variantSelectorDisplayMode: z37.enum(["separate", "single"]).optional().default("separate")
4362
- }),
4363
- productType: z37.union([z37.enum(["one-time", "subscription", "both"]), z37.literal("").transform(() => "one-time")]).default("one-time"),
4364
- productTypeSelector: z37.enum(["dropdown", "radio"]).optional().default("radio"),
4365
- quantityInputs: z37.object({
4366
- defaultQuantity: z37.union([z37.number(), z37.null().transform(() => 1)]).default(1),
4367
- enabled: z37.boolean().default(true),
4368
- maxValue: z37.union([z37.number(), z37.null().transform(() => 15)]).default(15),
4369
- minValue: z37.union([z37.number().min(0), z37.unknown().transform(() => 0)]).default(1)
4370
- }),
4371
- shipping: z37.object({
4372
- amount: strictNumber.default(10),
4373
- /** This isn't configurable in the admin-nextjs editor */
4374
- enabled: z37.boolean().default(true),
4375
- title: z37.string().optional().default("Shipping Fees"),
4376
- type: z37.enum(["free", "fixed_amount"]).default("free")
4377
- }),
4378
- styles: z37.object({
4379
- starColor: z37.string().optional().default("#fadb14"),
4380
- starSize: z37.union([z37.enum(["small", "medium", "large"]), z37.unknown().transform(() => "large")]).optional().default("large"),
4381
- starStrokeColor: z37.string().optional().default("#fadb14"),
4382
- strokeWidth: z37.string().optional().default("1")
4383
- }).optional(),
4384
- subImages: z37.number().nullish().default(8),
4385
- subscription: z37.object({
4386
- discount: z37.object({
4387
- amount: strictNumber.default(30),
4388
- shouldShowSavingText: z37.boolean().optional().default(true),
4389
- type: z37.enum(["none", "fixed_amount", "percentage"]).default("none")
4390
- }),
4391
- shipping: z37.object({
4392
- amount: strictNumber.default(1),
4393
- /** This isn't configurable in the admin-nextjs editor */
4394
- enabled: z37.boolean().default(true),
4395
- /** This isn't configurable in the admin-nextjs editor */
4396
- recurringAmount: strictNumber.default(2),
4397
- shippingTitle: z37.string().default("Shipping Fees"),
4398
- type: z37.enum(["free", "fixed_amount"]).default("free")
4399
- })
4400
- }).optional().default({
4401
- discount: {
4402
- amount: 30,
4403
- shouldShowSavingText: true,
4404
- type: "none"
4405
- },
4406
- shipping: {
4407
- amount: 1,
4408
- /** This isn't configurable in the admin-nextjs editor */
4409
- enabled: true,
4410
- /** This isn't configurable in the admin-nextjs editor */
4411
- recurringAmount: 2,
4412
- shippingTitle: "Shipping Fees",
4413
- type: "free"
4414
- }
4415
- }),
4416
- subscriptionIsDefault: z37.boolean().optional(),
4417
- /** This isn't configurable in the admin-nextjs editor */
4418
- template: z37.string().optional().default(""),
4419
- type: z37.literal("shopify_post_purchase_extension").default("shopify_post_purchase_extension"),
4420
- upgradeToSubscription: z37.object({
4421
- acceptButtonText: z37.string().optional().default("Upgrade"),
4422
- allow: z37.boolean().optional().default(false),
4423
- emailRefundNotice: z37.boolean().optional().default(false),
4424
- errorBannerText: z37.string().optional().default("This subscription cannot be added - limit one per order")
4425
- }).optional().default(() => ({
4426
- acceptButtonText: "Upgrade",
4427
- allow: false,
4428
- emailRefundNotice: false,
4429
- errorBannerText: "This subscription cannot be added - limit one per order"
4430
- }))
4573
+ });
4574
+ return CABLayoutSection.parse({ alignment: { horizontal }, sections });
4575
+ };
4576
+
4577
+ // src/transforms/postPurchase/imageContentSettings.ts
4578
+ import { z as z43 } from "zod";
4579
+ var ImageContentSettings = ImageContentBlock.shape.settings.pick({
4580
+ descriptionSize: true,
4581
+ horizontalAlignment: true,
4582
+ superTitleSize: true,
4583
+ swapContent: true,
4584
+ titleSize: true
4585
+ }).extend({
4586
+ alt: z43.unknown().optional(),
4587
+ description: z43.unknown().optional(),
4588
+ image: z43.unknown().optional(),
4589
+ imagePlacement: ImageContentBlock.shape.settings.shape.imagePlacement.unwrap().nullable(),
4590
+ imageSize: ImageContentBlock.shape.settings.shape.imageSize.pipe(z43.number().positive()),
4591
+ superTitle: z43.unknown().optional(),
4592
+ title: z43.unknown().optional()
4431
4593
  });
4432
4594
 
4595
+ // src/transforms/postPurchase/convertImageContentBlock.ts
4596
+ var Shell2 = z44.object({ settings: ImageContentSettings, type: z44.literal("image_content") });
4597
+ var PROPORTIONAL_BREAK_AT = 500;
4598
+ var convertImageContentBlock = (raw, report) => {
4599
+ const shell = Shell2.safeParse(raw);
4600
+ if (!shell.success) {
4601
+ report?.({ detail: "Unreadable post-purchase image-content block", kind: "section" });
4602
+ return [];
4603
+ }
4604
+ const settings = shell.data.settings;
4605
+ const alt = typeof settings.alt === "string" ? settings.alt : void 0;
4606
+ if (settings.alt !== void 0 && alt === void 0) {
4607
+ report?.({ detail: "Unreadable post-purchase image-content alt text", kind: "section" });
4608
+ }
4609
+ const image = convertImageFrame({ alt, image: settings.image, imageSize: settings.imageSize });
4610
+ if (!image) report?.({ detail: "Unreadable post-purchase image-content image", kind: "section" });
4611
+ const text2 = convertImageContentText(settings, report);
4612
+ const textFrame = CABLayoutSection.parse({ direction: "grid", grid: { columns: ["100%"] }, sections: [text2] });
4613
+ const placement = settings.imagePlacement ?? (settings.swapContent ? "right" : "left");
4614
+ const imageFirst = placement === "left" || placement === "top";
4615
+ const sections = imageFirst ? [image, textFrame] : [textFrame, image];
4616
+ const ordered = sections.filter((section) => section !== null);
4617
+ const inner = placement === "left" || placement === "right" ? CABLayoutSection.parse({
4618
+ sections: ordered,
4619
+ spacing: "loose",
4620
+ wrapping: {
4621
+ breakAt: settings.imageSize <= 1 ? PROPORTIONAL_BREAK_AT : settings.imageSize,
4622
+ maxPerLine: 2
4623
+ }
4624
+ }) : CABLayoutSection.parse({
4625
+ direction: "grid",
4626
+ grid: { columns: ["100%"] },
4627
+ sections: [CABLayoutSection.parse({ sections: ordered })],
4628
+ width: 95
4629
+ });
4630
+ return [CABLayoutSection.parse({ frame: "offer-content", sections: [inner], spacing: "none" })];
4631
+ };
4632
+
4633
+ // src/transforms/postPurchase/productCardPay.ts
4634
+ import { mapValues } from "es-toolkit";
4635
+ var PAY_NOW = { en: "Pay Now" };
4636
+ var DECLINE_THIS_OFFER = { en: "Decline this offer" };
4637
+ var NOT_AVAILABLE_DEFAULTS = ["Not available"];
4638
+ var soldOutSlot = ({ language: language3 }) => labelSlot("soldOut", OFFER_LABELS.soldOut, language3.global.notAvailable, [], NOT_AVAILABLE_DEFAULTS);
4639
+ var productCardPay = (settings) => {
4640
+ const payNow = labelSlot("payNow", PAY_NOW, settings.language.global.payNow);
4641
+ const priced = settings.language.displayCtaPrice ? { ...payNow, content: mapValues(payNow.content, (label2) => `${label2} \xB7 {{total}}`) } : payNow;
4642
+ return [addToOrderButton("primary", [priced, soldOutSlot(settings)])];
4643
+ };
4644
+ var productCardDecline = ({ language: language3 }) => [
4645
+ {
4646
+ action: "declineOffer",
4647
+ buttonStyle: "secondary",
4648
+ sections: [labelSlot("declineOffer", DECLINE_THIS_OFFER, language3.global.declineThisOffer)],
4649
+ sectionType: "button"
4650
+ }
4651
+ ];
4652
+
4433
4653
  // src/transforms/postPurchase/productCardSettings.ts
4654
+ import { mapValues as mapValues2 } from "es-toolkit";
4655
+ import { z as z45 } from "zod";
4434
4656
  var { shape } = PostPurchaseSettings;
4435
- var Labels = z38.object(
4657
+ var Labels = z45.object(
4436
4658
  mapValues2(shape.language.shape.global.unwrap().unwrap().shape, (label2) => label2.catch(() => label2.parse(void 0)))
4437
4659
  );
4438
- var Language = z38.object({
4660
+ var Language = z45.object({
4439
4661
  displayCtaPrice: shape.language.shape.displayCtaPrice.catch(true),
4440
4662
  global: Labels.catch(() => Labels.parse({}))
4441
4663
  });
4442
- var Fields = z38.object({
4664
+ var Fields = z45.object({
4443
4665
  discount: shape.discount.catch(() => shape.discount.parse({})),
4444
4666
  language: Language.catch(() => Language.parse({})),
4445
4667
  productOptions: shape.productOptions.catch(() => shape.productOptions.parse({})),
@@ -4723,7 +4945,7 @@ var convertSeparatorBlock = (raw, report) => {
4723
4945
  };
4724
4946
 
4725
4947
  // src/transforms/postPurchase/convertTextBlock.ts
4726
- import { z as z39 } from "zod";
4948
+ import { z as z46 } from "zod";
4727
4949
  var fields3 = ["superTitle", "title", "description"];
4728
4950
  var styles = {
4729
4951
  description: [{ attrs: { color: "base", fontSize: "small" }, type: "textStyle" }],
@@ -4731,13 +4953,13 @@ var styles = {
4731
4953
  title: [{ type: "bold" }, { attrs: { color: "base", fontSize: "large" }, type: "textStyle" }]
4732
4954
  };
4733
4955
  var settingsSchema = TextBlock.shape.settings;
4734
- var Shell3 = z39.object({
4956
+ var Shell3 = z46.object({
4735
4957
  settings: settingsSchema.pick({ horizontalAlignment: true }).extend({
4736
- description: z39.unknown().optional(),
4737
- superTitle: z39.unknown().optional(),
4738
- title: z39.unknown().optional()
4958
+ description: z46.unknown().optional(),
4959
+ superTitle: z46.unknown().optional(),
4960
+ title: z46.unknown().optional()
4739
4961
  }),
4740
- type: z39.literal("text")
4962
+ type: z46.literal("text")
4741
4963
  });
4742
4964
  var convertTextBlock = (raw, report) => {
4743
4965
  const shell = Shell3.safeParse(raw);
@@ -4809,11 +5031,11 @@ var convertTimerBlock = (raw, report) => {
4809
5031
  };
4810
5032
 
4811
5033
  // src/transforms/postPurchase/convertMessagePreview.ts
4812
- var MessageWidget = z40.object({
4813
- blocks: z40.array(z40.unknown()).min(1),
4814
- type: z40.literal(WidgetType.shopify_post_purchase_extension)
5034
+ var MessageWidget = z47.object({
5035
+ blocks: z47.array(z47.unknown()).min(1),
5036
+ type: z47.literal(WidgetType.shopify_post_purchase_extension)
4815
5037
  });
4816
- var BlockType2 = z40.object({ type: z40.string() });
5038
+ var BlockType2 = z47.object({ type: z47.string() });
4817
5039
  var converters = {
4818
5040
  callout_banner: convertCalloutBlock,
4819
5041
  image: convertImageBlock,
@@ -5325,6 +5547,12 @@ var reportUnreadableVerdict = (metadata, report) => {
5325
5547
  if (RuleVerdict.safeParse(metadata).success || isDefinitiveRuleAnswer(metadata)) return;
5326
5548
  report?.({ detail: "rule verdict lacks readable custom.shouldShow", kind: "ruleVerdict" });
5327
5549
  };
5550
+ var withReviews = (product, integrations, report) => {
5551
+ const { reviews: engineReviews, ...rest } = product;
5552
+ const reviews = projectOfferProductReviews(integrations, product, report);
5553
+ if (reviews !== void 0) return { ...rest, reviews };
5554
+ return engineReviews === void 0 ? product : rest;
5555
+ };
5328
5556
  var dedupeById = (products) => uniqBy2(products, (product) => product.id);
5329
5557
  var MAX_REQUEST_LINE_BYTES = 32e3;
5330
5558
  var requestLineBytes = (path, search) => new TextEncoder().encode(`GET ${path}?${search} HTTP/1.1`).length;
@@ -5351,7 +5579,9 @@ var fetchDataSourceResults = async (input, ctx) => {
5351
5579
  if (input.limit === 0) reportUnreadableVerdict(metadata, ctx.reportDrift);
5352
5580
  if (input.limit !== 0 && (upstream != null && (typeof upstream !== "object" || Array.isArray(upstream)) || raw.data != null && !Array.isArray(raw.data)))
5353
5581
  ctx.reportDrift?.({ detail: "data-source products payload is not an array", kind: "dataSource" });
5354
- const products = dedupeById(rawProducts.filter((product) => keepValidProduct(product, ctx.reportDrift)));
5582
+ const products = dedupeById(rawProducts.filter((product) => keepValidProduct(product, ctx.reportDrift))).map(
5583
+ (product) => withReviews(product, input.integrations, ctx.reportDrift)
5584
+ );
5355
5585
  return {
5356
5586
  metadata,
5357
5587
  /** B2B sessions filter by the company-location catalog server-side (the client bypasses its market hide). */
@@ -5361,7 +5591,7 @@ var fetchDataSourceResults = async (input, ctx) => {
5361
5591
  };
5362
5592
 
5363
5593
  // src/server/adsEngine.ts
5364
- import { z as z41 } from "zod";
5594
+ import { z as z48 } from "zod";
5365
5595
  var SUPPLY_KEY = { production: "AQF5RKLR", staging: "Z56LQ7PS" };
5366
5596
  var OFFER_LIMIT = 4;
5367
5597
  var SUPPORTED_COUNTRIES = /* @__PURE__ */ new Set(["US", "CA", "GB"]);
@@ -5374,30 +5604,30 @@ var transactionItems = (input) => (input.transactionItems?.map(({ id, priceCents
5374
5604
  title,
5375
5605
  type
5376
5606
  })) ?? input.cart.items.map(({ productId: productId2, quantity: quantity3 }) => ({ id: productId2, quantity: quantity3 }))).slice(0, MAX_TRANSACTION_ITEMS);
5377
- var AdsEngineOffer = z41.object({
5607
+ var AdsEngineOffer = z48.object({
5378
5608
  /** Pill-style value prop ("Get $45 Cashback"). Carried through the parse; the card does not render it yet (REB-24429 — pill design pending). */
5379
- callout: z41.string().catch(""),
5380
- click_url: z41.string(),
5381
- cta: z41.string(),
5609
+ callout: z48.string().catch(""),
5610
+ click_url: z48.string(),
5611
+ cta: z48.string(),
5382
5612
  /** `omitempty` on the ad server — a bid without it must not fail the parse and drop the whole batch (composeOffer falls back to 'No Thanks'). */
5383
- decline_cta: z41.string().catch(""),
5384
- decline_url: z41.string(),
5385
- description: z41.string(),
5613
+ decline_cta: z48.string().catch(""),
5614
+ decline_url: z48.string(),
5615
+ description: z48.string(),
5386
5616
  /** Short legal disclosure (FDIC / auto-renewal / FTC-affiliation lines). Rendered subdued under the CTAs. */
5387
- disclaimer: z41.string().catch(""),
5617
+ disclaimer: z48.string().catch(""),
5388
5618
  /** Long legal text behind the disclosure toggle in the composed card (REB-24429). */
5389
- extended_disclaimer: z41.string().catch(""),
5390
- headline: z41.string(),
5391
- id: z41.string(),
5619
+ extended_disclaimer: z48.string().catch(""),
5620
+ headline: z48.string(),
5621
+ id: z48.string(),
5392
5622
  /** Advertiser-supplied image alt; the composed card falls back to the stripped headline when empty. */
5393
- image_alt: z41.string().catch(""),
5394
- image_url: z41.string().optional(),
5623
+ image_alt: z48.string().catch(""),
5624
+ image_url: z48.string().optional(),
5395
5625
  /** The serving network's own privacy page (Falcon, REB-24744); the card prefers it over the widget setting. A bad value drops only this field, never the offer. */
5396
- privacy_url: z41.string().optional().catch(void 0),
5397
- view_url: z41.string()
5626
+ privacy_url: z48.string().optional().catch(void 0),
5627
+ view_url: z48.string()
5398
5628
  });
5399
- var AdsEngineResponse = z41.object({
5400
- offers: z41.array(z41.unknown()).default([]).transform(
5629
+ var AdsEngineResponse = z48.object({
5630
+ offers: z48.array(z48.unknown()).default([]).transform(
5401
5631
  (offers) => offers.flatMap((offer) => {
5402
5632
  const result = AdsEngineOffer.safeParse(offer);
5403
5633
  return result.success ? [result.data] : [];
@@ -5409,8 +5639,8 @@ var AdsEngineResponse = z41.object({
5409
5639
  * offers-testing fixture on a test store forces it, REB-24467). Both optional: an older engine or a
5410
5640
  * non-JSON body must not fail the parse and drop the offers.
5411
5641
  */
5412
- request_id: z41.string().optional().catch(void 0),
5413
- testing: z41.boolean().optional().catch(void 0)
5642
+ request_id: z48.string().optional().catch(void 0),
5643
+ testing: z48.boolean().optional().catch(void 0)
5414
5644
  });
5415
5645
  var adsHost = (host) => `offers.${host}`;
5416
5646
  var isTestingRequest = (input, isProduction) => !isProduction || !!input.testing || input.customerEmail?.toLowerCase() === TEST_CUSTOMER_EMAIL;
@@ -5726,7 +5956,6 @@ var fetchMonetizeOffers = async (input, ctx, prefetched) => {
5726
5956
 
5727
5957
  // src/server/salvageSections.ts
5728
5958
  import { v7 as uuidv714 } from "uuid";
5729
- var STUB_SECTION_TYPE = "unknown";
5730
5959
  var knownSectionTypes = new Set(sectionTypes);
5731
5960
  var hasSectionsArray = (raw) => typeof raw === "object" && raw !== null && Array.isArray(raw.sections);
5732
5961
  var stubSection = (raw, report) => {
@@ -5739,7 +5968,7 @@ var stubSection = (raw, report) => {
5739
5968
  });
5740
5969
  return {
5741
5970
  sectionId: typeof rawId === "string" && rawId ? rawId : uuidv714(),
5742
- sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : STUB_SECTION_TYPE
5971
+ sectionType: typeof original === "string" && !knownSectionTypes.has(original) ? original : SALVAGED_SECTION_TYPE
5743
5972
  };
5744
5973
  };
5745
5974
  var salvageSection = (raw, report) => {
@@ -5882,13 +6111,13 @@ var convertWidgetSettings = async (input, ctx) => {
5882
6111
  };
5883
6112
 
5884
6113
  // src/server/giftWidgets.ts
5885
- import { z as z42 } from "zod";
5886
- var SettingsNumber = z42.union([
5887
- z42.number(),
5888
- z42.string().trim().regex(/^-?\d+(\.\d+)?$/)
5889
- ]).pipe(z42.coerce.number());
5890
- var SgwpMilestoneSchema = z42.object({
5891
- selectable_product_quantity: SettingsNumber.pipe(z42.number().int().nonnegative()),
6114
+ import { z as z49 } from "zod";
6115
+ var SettingsNumber = z49.union([
6116
+ z49.number(),
6117
+ z49.string().trim().regex(/^-?\d+(\.\d+)?$/)
6118
+ ]).pipe(z49.coerce.number());
6119
+ var SgwpMilestoneSchema = z49.object({
6120
+ selectable_product_quantity: SettingsNumber.pipe(z49.number().int().nonnegative()),
5892
6121
  unlock_price: SettingsNumber
5893
6122
  });
5894
6123
  var fetchGiftWidgetSettings = async (widgetId, shop, ctx) => unwrapData(await ctx.fetchUpstream("/api/v1/widgets/settings", { id: String(widgetId), shop }, { host: ctx.host }));
@@ -5899,7 +6128,7 @@ var readDataSourcePath = (settings) => {
5899
6128
  var readMilestones = (settings, report) => {
5900
6129
  const raw = settings && typeof settings === "object" ? settings.milestones : void 0;
5901
6130
  if (raw == null) return [];
5902
- const parsed = z42.array(SgwpMilestoneSchema).safeParse(raw);
6131
+ const parsed = z49.array(SgwpMilestoneSchema).safeParse(raw);
5903
6132
  if (parsed.success) return parsed.data;
5904
6133
  report?.({
5905
6134
  detail: [
@@ -6014,16 +6243,16 @@ var validateGifts = async (input, ctx) => {
6014
6243
 
6015
6244
  // src/server/normalizePostPurchaseProduct.ts
6016
6245
  import { merge } from "es-toolkit/compat";
6017
- import { z as z46 } from "zod";
6246
+ import { z as z53 } from "zod";
6018
6247
 
6019
6248
  // src/schema/postPurchaseProductSettings.ts
6020
- import { z as z44 } from "zod";
6249
+ import { z as z51 } from "zod";
6021
6250
 
6022
6251
  // src/schema/postPurchaseProductSource.ts
6023
- import { z as z43 } from "zod";
6024
- var PostPurchaseAmount = z43.string().regex(/^\d+(?:\.\d+)?$/).refine((value) => Number.isFinite(Number(value)));
6025
- var PostPurchaseProductId = z43.number().int().positive();
6026
- var PostPurchaseVariantId = z43.number().int().positive();
6252
+ import { z as z50 } from "zod";
6253
+ var PostPurchaseAmount = z50.string().regex(/^\d+(?:\.\d+)?$/).refine((value) => Number.isFinite(Number(value)));
6254
+ var PostPurchaseProductId = z50.number().int().positive();
6255
+ var PostPurchaseVariantId = z50.number().int().positive();
6027
6256
  var ProductBase = OfferProduct.pick({ description: true, id: true, image: true, title: true }).extend({
6028
6257
  description: OfferProduct.shape.description.nullable(),
6029
6258
  id: PostPurchaseProductId
@@ -6031,51 +6260,51 @@ var ProductBase = OfferProduct.pick({ description: true, id: true, image: true,
6031
6260
  var SourceVariant = OfferProduct.shape.variants.element.pick({ id: true, title: true }).extend({
6032
6261
  compareAtPrice: PostPurchaseAmount.nullable(),
6033
6262
  id: PostPurchaseVariantId,
6034
- imageId: z43.number().int().nonnegative().nullish(),
6035
- inventoryManagement: z43.string().nullable(),
6036
- inventoryPolicy: z43.string(),
6037
- inventoryQuantity: z43.number(),
6038
- option1: z43.unknown().optional(),
6039
- option2: z43.unknown().optional(),
6040
- option3: z43.unknown().optional(),
6263
+ imageId: z50.number().int().nonnegative().nullish(),
6264
+ inventoryManagement: z50.string().nullable(),
6265
+ inventoryPolicy: z50.string(),
6266
+ inventoryQuantity: z50.number(),
6267
+ option1: z50.unknown().optional(),
6268
+ option2: z50.unknown().optional(),
6269
+ option3: z50.unknown().optional(),
6041
6270
  price: PostPurchaseAmount,
6042
6271
  productId: PostPurchaseProductId,
6043
- sellingPlanAllocations: z43.unknown().optional()
6272
+ sellingPlanAllocations: z50.unknown().optional()
6044
6273
  });
6045
6274
  var Product2 = ProductBase.extend({
6046
- images: z43.array(
6047
- z43.object({ alt: z43.string().optional().catch(void 0), id: z43.number().int().positive(), src: z43.string() })
6275
+ images: z50.array(
6276
+ z50.object({ alt: z50.string().optional().catch(void 0), id: z50.number().int().positive(), src: z50.string() })
6048
6277
  ).nullish(),
6049
- metadata: z43.object({
6050
- productQuantity: z43.union([
6051
- z43.number().int().positive(),
6052
- z43.string().regex(/^[1-9]\d*$/).transform(Number)
6278
+ metadata: z50.object({
6279
+ productQuantity: z50.union([
6280
+ z50.number().int().positive(),
6281
+ z50.string().regex(/^[1-9]\d*$/).transform(Number)
6053
6282
  ]).optional()
6054
6283
  }).optional(),
6055
- metafields: z43.unknown().optional(),
6056
- options: z43.unknown().optional(),
6057
- productGroupVariants: z43.array(z43.unknown()).default([]),
6284
+ metafields: z50.unknown().optional(),
6285
+ options: z50.unknown().optional(),
6286
+ productGroupVariants: z50.array(z50.unknown()).default([]),
6058
6287
  // REB-25048: the v2 data source flags a subscription-only product here and sends no selling plan groups
6059
- requiresSellingPlan: z43.boolean().nullish(),
6060
- selectedVariant: z43.unknown().optional(),
6061
- sellingPlanGroups: z43.array(
6288
+ requiresSellingPlan: z50.boolean().nullish(),
6289
+ selectedVariant: z50.unknown().optional(),
6290
+ sellingPlanGroups: z50.array(
6062
6291
  OfferProduct.shape.sellingPlanGroups.unwrap().element.extend({
6063
- discountAmount: z43.unknown().optional(),
6064
- discountType: z43.unknown().optional(),
6065
- id: z43.unknown().optional(),
6066
- requiresSellingPlan: z43.boolean().default(false),
6067
- sellingPlans: z43.unknown().optional()
6292
+ discountAmount: z50.unknown().optional(),
6293
+ discountType: z50.unknown().optional(),
6294
+ id: z50.unknown().optional(),
6295
+ requiresSellingPlan: z50.boolean().default(false),
6296
+ sellingPlans: z50.unknown().optional()
6068
6297
  })
6069
6298
  ).default([]),
6070
- variants: z43.array(SourceVariant)
6299
+ variants: z50.array(SourceVariant)
6071
6300
  });
6072
- var PostPurchaseProductSource = z43.object({
6073
- data: z43.array(Product2),
6074
- metadata: z43.object({ widget: z43.record(z43.string(), z43.unknown()).default({}) }).default({ widget: {} })
6301
+ var PostPurchaseProductSource = z50.object({
6302
+ data: z50.array(Product2),
6303
+ metadata: z50.object({ widget: z50.record(z50.string(), z50.unknown()).default({}) }).default({ widget: {} })
6075
6304
  });
6076
6305
 
6077
6306
  // src/schema/postPurchaseProductSettings.ts
6078
- var amount = z44.union([z44.number().nonnegative(), PostPurchaseAmount.transform(Number)]);
6307
+ var amount = z51.union([z51.number().nonnegative(), PostPurchaseAmount.transform(Number)]);
6079
6308
  var PostPurchaseProductSettings = PostPurchaseSettings.pick({
6080
6309
  discountedFrom: true,
6081
6310
  language: true,
@@ -6087,18 +6316,18 @@ var PostPurchaseProductSettings = PostPurchaseSettings.pick({
6087
6316
  subscriptionIsDefault: true,
6088
6317
  upgradeToSubscription: true
6089
6318
  }).extend({
6090
- advance: z44.unknown().default({}),
6091
- blocks: z44.array(z44.unknown()).optional(),
6319
+ advance: z51.unknown().default({}),
6320
+ blocks: z51.array(z51.unknown()).optional(),
6092
6321
  discount: PostPurchaseSettings.shape.discount.extend({ amount: amount.default(30) }),
6093
- integrations: z44.unknown().default({}),
6322
+ integrations: z51.unknown().default({}),
6094
6323
  shipping: PostPurchaseSettings.shape.shipping.extend({ amount: amount.default(10) }),
6095
- styles: z44.unknown().default({}),
6324
+ styles: z51.unknown().default({}),
6096
6325
  subImages: PostPurchaseSettings.shape.subImages.catch(null),
6097
- subscription: z44.unknown().default(PostPurchaseSettings.shape.subscription.parse(void 0))
6326
+ subscription: z51.unknown().default(PostPurchaseSettings.shape.subscription.parse(void 0))
6098
6327
  });
6099
6328
 
6100
6329
  // src/server/readPostPurchaseProductSource.ts
6101
- import { z as z45 } from "zod";
6330
+ import { z as z52 } from "zod";
6102
6331
 
6103
6332
  // src/server/postPurchaseResponseDrift.ts
6104
6333
  var heard = /* @__PURE__ */ new WeakMap();
@@ -6124,9 +6353,9 @@ var requiresOwnSellingPlan = (product) => product.requiresSellingPlan === true;
6124
6353
  var requiresUnsuppliedSellingPlan = (product) => requiresOwnSellingPlan(product) && !product.sellingPlanGroups?.length;
6125
6354
 
6126
6355
  // src/server/readPostPurchaseProductSource.ts
6127
- var Envelope = PostPurchaseProductSource.extend({ data: z45.array(z45.unknown()) });
6356
+ var Envelope = PostPurchaseProductSource.extend({ data: z52.array(z52.unknown()) });
6128
6357
  var Product3 = PostPurchaseProductSource.shape.data.element;
6129
- var Identified = z45.object({ id: PostPurchaseProductId });
6358
+ var Identified = z52.object({ id: PostPurchaseProductId });
6130
6359
  var readPostPurchaseProductSource = (response, report) => {
6131
6360
  const { data, metadata } = Envelope.parse(deepCamelCase(response));
6132
6361
  const drift = [];
@@ -6161,9 +6390,9 @@ var normalizePostPurchaseSource = (settings, response, report) => {
6161
6390
  const original = PostPurchaseProductSettings.parse(deepCamelCase(settings));
6162
6391
  const { dropped, metadata, products } = readPostPurchaseProductSource(response, report);
6163
6392
  const overrides = Object.fromEntries(Object.entries(metadata.widget).filter(([key]) => key in original));
6164
- const discount = z46.object({ type: z46.string() }).safeParse(overrides.discount);
6393
+ const discount = z53.object({ type: z53.string() }).safeParse(overrides.discount);
6165
6394
  if (discount.success && discount.data.type === "fixed") {
6166
- overrides.discount = { ...z46.record(z46.string(), z46.unknown()).parse(overrides.discount), type: "fixed_amount" };
6395
+ overrides.discount = { ...z53.record(z53.string(), z53.unknown()).parse(overrides.discount), type: "fixed_amount" };
6167
6396
  }
6168
6397
  const effective = PostPurchaseProductSettings.parse(merge({}, original, overrides));
6169
6398
  const data = products.map(({ product }) => product);
@@ -6200,52 +6429,52 @@ var postPurchasePlanDiscountPercentage = (price, adjustment) => {
6200
6429
  };
6201
6430
 
6202
6431
  // src/schema/postPurchasePlanCatalog.ts
6203
- import { z as z49 } from "zod";
6432
+ import { z as z56 } from "zod";
6204
6433
 
6205
6434
  // src/schema/postPurchasePlanAdjustment.ts
6206
- import { z as z48 } from "zod";
6435
+ import { z as z55 } from "zod";
6207
6436
 
6208
6437
  // src/schema/postPurchaseDiscount.ts
6209
- import { z as z47 } from "zod";
6210
- var Discount = z47.object({
6211
- amount: z47.number().nonnegative(),
6212
- title: z47.string(),
6213
- type: z47.enum(["none", "fixed_amount", "percentage"])
6438
+ import { z as z54 } from "zod";
6439
+ var Discount = z54.object({
6440
+ amount: z54.number().nonnegative(),
6441
+ title: z54.string(),
6442
+ type: z54.enum(["none", "fixed_amount", "percentage"])
6214
6443
  });
6215
6444
  var validPostPurchaseDiscount = (discount) => discount.type !== "percentage" || discount.amount <= 100;
6216
6445
  var PostPurchaseDiscount = Discount.refine(validPostPurchaseDiscount);
6217
6446
 
6218
6447
  // src/schema/postPurchasePlanAdjustment.ts
6219
- var PostPurchasePlanAdjustment = z48.object({
6448
+ var PostPurchasePlanAdjustment = z55.object({
6220
6449
  amount: PostPurchaseDiscount.shape.amount,
6221
- type: z48.enum(["percentage", "fixed_amount", "price"])
6450
+ type: z55.enum(["percentage", "fixed_amount", "price"])
6222
6451
  }).refine(({ amount: amount2, type }) => type === "price" || validPostPurchaseDiscount({ amount: amount2, type }));
6223
6452
 
6224
6453
  // src/schema/postPurchasePlanCatalog.ts
6225
- var PostPurchasePlanOption = z49.object({
6454
+ var PostPurchasePlanOption = z56.object({
6226
6455
  adjustment: PostPurchasePlanAdjustment.optional(),
6227
6456
  /** cab-checkout's `discountPercentage`: the plan's own whole-percent saving on the regular price (`{{subscriptionDiscount}}`). */
6228
- discountPercentage: z49.number().int().min(0).max(100).optional(),
6229
- id: z49.number().int().positive(),
6230
- intervalFrequency: z49.string().regex(/^[1-9]\d*$/).optional(),
6231
- intervalLabel: z49.string().min(1),
6232
- name: z49.string().min(1)
6457
+ discountPercentage: z56.number().int().min(0).max(100).optional(),
6458
+ id: z56.number().int().positive(),
6459
+ intervalFrequency: z56.string().regex(/^[1-9]\d*$/).optional(),
6460
+ intervalLabel: z56.string().min(1),
6461
+ name: z56.string().min(1)
6233
6462
  });
6234
- var PostPurchasePlanCatalog = z49.object({
6235
- plans: z49.array(PostPurchasePlanOption),
6236
- productId: z49.number().int().positive(),
6237
- requiresSellingPlan: z49.boolean(),
6238
- variantId: z49.number().int().positive()
6463
+ var PostPurchasePlanCatalog = z56.object({
6464
+ plans: z56.array(PostPurchasePlanOption),
6465
+ productId: z56.number().int().positive(),
6466
+ requiresSellingPlan: z56.boolean(),
6467
+ variantId: z56.number().int().positive()
6239
6468
  });
6240
6469
 
6241
6470
  // src/server/postPurchaseLegacyPlans.ts
6242
- import { z as z51 } from "zod";
6471
+ import { z as z58 } from "zod";
6243
6472
 
6244
6473
  // src/server/postPurchasePlanAdjustment.ts
6245
- import { z as z50 } from "zod";
6246
- var SourceAdjustment = z50.object({
6474
+ import { z as z57 } from "zod";
6475
+ var SourceAdjustment = z57.object({
6247
6476
  amount: PostPurchaseProductSettings.shape.discount.shape.amount.unwrap(),
6248
- type: z50.enum(["percentage", "fixed_amount", "amount", "price", "none", "fixed"])
6477
+ type: z57.enum(["percentage", "fixed_amount", "amount", "price", "none", "fixed"])
6249
6478
  });
6250
6479
  var readPostPurchasePlanAdjustment = (raw) => {
6251
6480
  const source2 = SourceAdjustment.parse(raw);
@@ -6264,13 +6493,13 @@ var readPostPurchasePlanAdjustment = (raw) => {
6264
6493
  var Plan = PostPurchasePlanOption.pick({ id: true, name: true }).extend({
6265
6494
  orderIntervalFrequency: PostPurchasePlanOption.shape.intervalFrequency.unwrap(),
6266
6495
  orderIntervalUnitType: PostPurchasePlanOption.shape.intervalLabel,
6267
- shopifyVariantId: z51.string().regex(/^[1-9]\d*$/).nullish()
6496
+ shopifyVariantId: z58.string().regex(/^[1-9]\d*$/).nullish()
6268
6497
  });
6269
- var Group = z51.object({
6270
- discountAmount: z51.unknown(),
6271
- discountType: z51.unknown(),
6272
- id: z51.number().int().positive(),
6273
- sellingPlans: z51.array(z51.object({ shopifyVariantId: Plan.shape.shopifyVariantId }).passthrough())
6498
+ var Group = z58.object({
6499
+ discountAmount: z58.unknown(),
6500
+ discountType: z58.unknown(),
6501
+ id: z58.number().int().positive(),
6502
+ sellingPlans: z58.array(z58.object({ shopifyVariantId: Plan.shape.shopifyVariantId }).passthrough())
6274
6503
  });
6275
6504
  var readPostPurchaseLegacyPlans = (raw, variantId) => {
6276
6505
  const group = Group.pick({ id: true, sellingPlans: true }).parse(raw);
@@ -6291,22 +6520,22 @@ var readPostPurchaseLegacyPlans = (raw, variantId) => {
6291
6520
  };
6292
6521
 
6293
6522
  // src/server/postPurchaseModernPlans.ts
6294
- import { z as z52 } from "zod";
6295
- var Groups = z52.array(
6296
- z52.object({
6297
- id: z52.string().min(1),
6298
- sellingPlans: z52.unknown()
6523
+ import { z as z59 } from "zod";
6524
+ var Groups = z59.array(
6525
+ z59.object({
6526
+ id: z59.string().min(1),
6527
+ sellingPlans: z59.unknown()
6299
6528
  })
6300
6529
  );
6301
- var PostPurchasePlanAllocations = z52.array(z52.object({ sellingPlanGroupId: z52.string().min(1) }));
6530
+ var PostPurchasePlanAllocations = z59.array(z59.object({ sellingPlanGroupId: z59.string().min(1) }));
6302
6531
  var Allocations = PostPurchasePlanAllocations.default([]);
6303
- var Plans = z52.array(
6532
+ var Plans = z59.array(
6304
6533
  PostPurchasePlanOption.pick({ id: true, name: true }).extend({
6305
- options: z52.array(z52.object({ value: z52.string().min(1) })).min(1),
6306
- priceAdjustments: z52.array(z52.unknown())
6534
+ options: z59.array(z59.object({ value: z59.string().min(1) })).min(1),
6535
+ priceAdjustments: z59.array(z59.unknown())
6307
6536
  })
6308
6537
  );
6309
- var FirstAdjustment = z52.object({
6538
+ var FirstAdjustment = z59.object({
6310
6539
  value: PostPurchaseProductSettings.shape.discount.shape.amount.unwrap(),
6311
6540
  valueType: PostPurchasePlanAdjustment.shape.type
6312
6541
  });
@@ -6364,72 +6593,45 @@ var projectPostPurchasePlanCatalog = (normalized, report) => {
6364
6593
  };
6365
6594
 
6366
6595
  // src/schema/postPurchaseSubscriptionProduct.ts
6367
- import { z as z58 } from "zod";
6596
+ import { z as z64 } from "zod";
6368
6597
 
6369
6598
  // src/schema/postPurchaseProduct.ts
6370
- import { z as z57 } from "zod";
6599
+ import { z as z63 } from "zod";
6371
6600
 
6372
6601
  // src/schema/postPurchaseProductDisplay.ts
6373
- import { z as z55 } from "zod";
6602
+ import { z as z61 } from "zod";
6374
6603
 
6375
6604
  // src/schema/postPurchaseProductGallery.ts
6376
- import { z as z53 } from "zod";
6377
- var PostPurchaseProductGallery = z53.object({
6605
+ import { z as z60 } from "zod";
6606
+ var PostPurchaseProductGallery = z60.object({
6378
6607
  /**
6379
6608
  * Show the thumbnail row: the product had more than one image before `images` was capped at the
6380
6609
  * sub-image limit, and that limit allows at least one thumbnail. It stays true when the cap leaves one
6381
6610
  * image (legacy checks the count before slicing). Older replies omit it; fall back to `images.length > 1`.
6382
6611
  */
6383
- hasMultiple: z53.boolean().optional(),
6384
- images: z53.array(
6385
- z53.object({
6386
- description: z53.string(),
6387
- id: z53.number().int().positive(),
6388
- source: z53.string(),
6389
- thumbnail: z53.string()
6612
+ hasMultiple: z60.boolean().optional(),
6613
+ images: z60.array(
6614
+ z60.object({
6615
+ description: z60.string(),
6616
+ id: z60.number().int().positive(),
6617
+ source: z60.string(),
6618
+ thumbnail: z60.string()
6390
6619
  })
6391
6620
  ),
6392
- selectedImageId: z53.number().int().positive().optional()
6393
- });
6394
-
6395
- // src/schema/postPurchaseProductReviews.ts
6396
- import { z as z54 } from "zod";
6397
- var decimal = z54.union([
6398
- z54.number(),
6399
- z54.string().regex(/^\d+(?:\.\d+)?$/).transform(Number)
6400
- ]);
6401
- var PostPurchaseCommonReviewRating = z54.object({ value: decimal.pipe(z54.number().min(0).max(5)) });
6402
- var PostPurchaseKlaviyoReviewRating = PostPurchaseCommonReviewRating.extend({
6403
- // Legacy validates key presence, then displays the raw value on five stars without rescaling.
6404
- scale_max: z54.unknown().nonoptional(),
6405
- scale_min: z54.unknown().nonoptional()
6406
- });
6407
- var PostPurchaseReviewCount = z54.string().regex(/^\d+(?:\.\d+)?$/).transform(Number).pipe(z54.number().int().nonnegative());
6408
- var PostPurchaseReviewStyles = PostPurchaseSettings.shape.styles.unwrap().extend({
6409
- strokeWidth: decimal.pipe(z54.number().nonnegative()).default(1)
6410
- }).prefault({});
6411
- var PostPurchaseProductReviews = z54.object({
6412
- color: z54.string(),
6413
- rating: z54.number().positive().max(5),
6414
- reviewCount: z54.number().int().nonnegative(),
6415
- reviewsPlural: z54.string(),
6416
- reviewsSingular: z54.string(),
6417
- size: z54.enum(["small", "medium", "large"]),
6418
- strokeColor: z54.string(),
6419
- strokeWidth: z54.number().nonnegative()
6621
+ selectedImageId: z60.number().int().positive().optional()
6420
6622
  });
6421
6623
 
6422
6624
  // src/schema/postPurchaseProductDisplay.ts
6423
6625
  var language = PostPurchaseSettings.shape.language;
6424
- var PostPurchasePrimaryImageSource = z55.string().default(
6626
+ var PostPurchasePrimaryImageSource = z61.string().default(
6425
6627
  "https://cdn.shopify.com/shopifycloud/shopify/assets/no-image-2048-5e88c1b20e087fb7bbe9a3771824e743c244f437e4f8ba93bbf7b11b53f7824c.gif"
6426
6628
  );
6427
- var PostPurchaseProductDisplay = z55.object({
6629
+ var PostPurchaseProductDisplay = z61.object({
6428
6630
  descriptionDocument: TiptapDocument.optional(),
6429
6631
  discountedFrom: PostPurchaseSettings.shape.discountedFrom,
6430
6632
  gallery: PostPurchaseProductGallery.optional(),
6431
6633
  language: language.pick({ description: true, descriptionEnabled: true }),
6432
- primaryImage: z55.object({ description: z55.string(), source: z55.string() }),
6634
+ primaryImage: z61.object({ description: z61.string(), source: z61.string() }),
6433
6635
  productOptions: PostPurchaseSettings.shape.productOptions.pick({
6434
6636
  showDeclineAll: true,
6435
6637
  showShipping: true,
@@ -6444,16 +6646,16 @@ var PostPurchaseProductDisplay = z55.object({
6444
6646
  });
6445
6647
 
6446
6648
  // src/schema/postPurchaseSubscription.ts
6447
- import { z as z56 } from "zod";
6448
- var PostPurchaseSubscriptionTerms = z56.object({
6649
+ import { z as z62 } from "zod";
6650
+ var PostPurchaseSubscriptionTerms = z62.object({
6449
6651
  adjustment: PostPurchasePlanAdjustment.optional(),
6450
- initialShippingPrice: z56.number().nonnegative(),
6451
- intervalLabel: z56.string().min(1),
6452
- name: z56.string().min(1),
6453
- recurringShippingPrice: z56.number().nonnegative(),
6454
- shippingOption: z56.object({ presentmentTitle: z56.string(), title: z56.string() }),
6652
+ initialShippingPrice: z62.number().nonnegative(),
6653
+ intervalLabel: z62.string().min(1),
6654
+ name: z62.string().min(1),
6655
+ recurringShippingPrice: z62.number().nonnegative(),
6656
+ shippingOption: z62.object({ presentmentTitle: z62.string(), title: z62.string() }),
6455
6657
  /** A switch to subscription (STS): the one subscription that charges no initial shipping (L122). */
6456
- sts: z56.literal(true).optional()
6658
+ sts: z62.literal(true).optional()
6457
6659
  });
6458
6660
 
6459
6661
  // src/schema/postPurchaseProduct.ts
@@ -6463,52 +6665,52 @@ var PostPurchaseVariant = OfferProduct.shape.variants.element.pick({ id: true, t
6463
6665
  id: PostPurchaseVariantId,
6464
6666
  price: PostPurchaseAmount
6465
6667
  });
6466
- var PreparedPostPurchaseProduct = z57.object({
6668
+ var PreparedPostPurchaseProduct = z63.object({
6467
6669
  display: PostPurchaseProductDisplay,
6468
- labels: z57.object({
6670
+ labels: z63.object({
6469
6671
  declineAll: global.declineAll,
6470
- free: z57.string(),
6672
+ free: z63.string(),
6471
6673
  notAvailable: global.notAvailable,
6472
- payNow: z57.string(),
6473
- quantity: z57.string(),
6674
+ payNow: z63.string(),
6675
+ quantity: z63.string(),
6474
6676
  recurringShipping: global.recurringShipping,
6475
6677
  recurringSubtotal: global.recurringSubtotal,
6476
- save: z57.string(),
6477
- shipping: z57.string(),
6478
- subtotal: z57.string(),
6479
- taxes: z57.string(),
6480
- total: z57.string()
6678
+ save: z63.string(),
6679
+ shipping: z63.string(),
6680
+ subtotal: z63.string(),
6681
+ taxes: z63.string(),
6682
+ total: z63.string()
6481
6683
  }),
6482
6684
  product: OfferProduct.pick({ description: true, id: true, image: true, title: true }).extend({
6483
6685
  description: OfferProduct.shape.description.nullable(),
6484
6686
  id: PostPurchaseProductId,
6485
6687
  variant: PostPurchaseVariant
6486
6688
  }),
6487
- selection: z57.object({
6689
+ selection: z63.object({
6488
6690
  productId: PostPurchaseProductId,
6489
- quantity: z57.number().int().positive(),
6490
- sellingPlanId: z57.number().int().positive().optional(),
6691
+ quantity: z63.number().int().positive(),
6692
+ sellingPlanId: z63.number().int().positive().optional(),
6491
6693
  variantId: PostPurchaseVariantId
6492
6694
  }),
6493
- status: z57.literal("prepared"),
6494
- terms: z57.object({
6695
+ status: z63.literal("prepared"),
6696
+ terms: z63.object({
6495
6697
  discount: PostPurchaseDiscount,
6496
- shipping: z57.object({
6497
- amount: z57.number().nonnegative(),
6498
- title: z57.string(),
6499
- type: z57.enum(["free", "fixed_amount"])
6698
+ shipping: z63.object({
6699
+ amount: z63.number().nonnegative(),
6700
+ title: z63.string(),
6701
+ type: z63.enum(["free", "fixed_amount"])
6500
6702
  }),
6501
6703
  subscription: PostPurchaseSubscriptionTerms.optional()
6502
6704
  })
6503
6705
  });
6504
- var PostPurchasePreparation = z57.discriminatedUnion("status", [
6706
+ var PostPurchasePreparation = z63.discriminatedUnion("status", [
6505
6707
  PreparedPostPurchaseProduct,
6506
- z57.object({ reason: z57.string(), status: z57.literal("unsupported") })
6708
+ z63.object({ reason: z63.string(), status: z63.literal("unsupported") })
6507
6709
  ]);
6508
6710
 
6509
6711
  // src/schema/postPurchaseSubscriptionProduct.ts
6510
6712
  var PreparedPostPurchaseSubscription = PreparedPostPurchaseProduct.extend({
6511
- selection: PreparedPostPurchaseProduct.shape.selection.extend({ sellingPlanId: z58.number().int().positive() }),
6713
+ selection: PreparedPostPurchaseProduct.shape.selection.extend({ sellingPlanId: z64.number().int().positive() }),
6512
6714
  terms: PreparedPostPurchaseProduct.shape.terms.extend({
6513
6715
  subscription: PostPurchaseSubscriptionTerms
6514
6716
  }).refine(
@@ -6568,7 +6770,7 @@ var subscriptionUnavailable = (normalized, plan, allowCollection, quantity3) =>
6568
6770
  };
6569
6771
 
6570
6772
  // src/server/postPurchaseProductDisplay.ts
6571
- import { z as z60 } from "zod";
6773
+ import { z as z66 } from "zod";
6572
6774
 
6573
6775
  // src/server/postPurchasePrimaryImage.ts
6574
6776
  var postPurchasePrimaryImage = (product) => {
@@ -6594,7 +6796,7 @@ var postPurchaseProductGallery = (product, limit) => {
6594
6796
  };
6595
6797
 
6596
6798
  // src/transforms/postPurchase/convertDescriptionRte.ts
6597
- import { z as z59 } from "zod";
6799
+ import { z as z65 } from "zod";
6598
6800
  var alignments = { center: "center", left: "start", right: "end" };
6599
6801
  var colors = ["critical", "subdued", "success", "warning"];
6600
6802
  var sizes2 = ["extraSmall", "small", "medium", "large", "extraLarge"];
@@ -6606,19 +6808,19 @@ var spacings2 = {
6606
6808
  xloose: "extraLoose",
6607
6809
  xtight: "extraTight"
6608
6810
  };
6609
- var Block = z59.object({
6610
- attrs: z59.object({ alignment: z59.enum(["left", "center", "right"]).optional().catch(void 0) }).catch({}),
6611
- content: z59.array(z59.unknown()).catch([]),
6612
- type: z59.literal("ShopifyTextBlock")
6613
- });
6614
- var Run = z59.object({ marks: z59.array(z59.unknown()).catch([]), text: z59.string().min(1), type: z59.literal("text") });
6615
- var Inline = z59.object({
6616
- attrs: z59.object({
6617
- color: z59.enum(colors).optional().catch(void 0),
6618
- emphasis: z59.array(z59.unknown()).catch([]),
6619
- size: z59.enum(sizes2).optional().catch(void 0)
6811
+ var Block = z65.object({
6812
+ attrs: z65.object({ alignment: z65.enum(["left", "center", "right"]).optional().catch(void 0) }).catch({}),
6813
+ content: z65.array(z65.unknown()).catch([]),
6814
+ type: z65.literal("ShopifyTextBlock")
6815
+ });
6816
+ var Run = z65.object({ marks: z65.array(z65.unknown()).catch([]), text: z65.string().min(1), type: z65.literal("text") });
6817
+ var Inline = z65.object({
6818
+ attrs: z65.object({
6819
+ color: z65.enum(colors).optional().catch(void 0),
6820
+ emphasis: z65.array(z65.unknown()).catch([]),
6821
+ size: z65.enum(sizes2).optional().catch(void 0)
6620
6822
  }),
6621
- type: z59.literal("ShopifyTextInline")
6823
+ type: z65.literal("ShopifyTextInline")
6622
6824
  });
6623
6825
  var marks = (raw) => {
6624
6826
  const found = raw.map((mark) => Inline.safeParse(mark)).find(({ success }) => success)?.data;
@@ -6651,7 +6853,7 @@ var convertDescriptionRte = (rte, spacing) => TiptapDocument.parse({
6651
6853
  });
6652
6854
 
6653
6855
  // src/server/postPurchaseProductDisplay.ts
6654
- var ResponseLanguage = z60.object({ description: PostPurchaseProductDisplay.shape.responseDescription }).default({ description: "" });
6856
+ var ResponseLanguage = z66.object({ description: PostPurchaseProductDisplay.shape.responseDescription }).default({ description: "" });
6655
6857
  var projectPostPurchaseProductDisplay = (effective, metadata, product) => PostPurchaseProductDisplay.parse({
6656
6858
  descriptionDocument: effective.language.descriptionRte && convertDescriptionRte(effective.language.descriptionRte, effective.language.descriptionSpacing),
6657
6859
  discountedFrom: effective.discountedFrom,
@@ -6663,146 +6865,6 @@ var projectPostPurchaseProductDisplay = (effective, metadata, product) => PostPu
6663
6865
  shouldShowSavingText: effective.discount.shouldShowSavingText
6664
6866
  });
6665
6867
 
6666
- // src/server/postPurchaseReviewData.ts
6667
- import { z as z65 } from "zod";
6668
-
6669
- // src/server/postPurchaseJudgeMeReviews.ts
6670
- import { z as z62 } from "zod";
6671
-
6672
- // src/server/postPurchaseReviewFields.ts
6673
- import { z as z61 } from "zod";
6674
- var Field = z61.object({ key: z61.string(), namespace: z61.string(), value: z61.unknown().optional() });
6675
- var reviewFields = (raw) => {
6676
- const fields4 = z61.array(z61.unknown()).parse(raw).flatMap((field) => {
6677
- const parsed = Field.safeParse(field);
6678
- return parsed.success ? [parsed.data] : [];
6679
- });
6680
- return (namespace, key) => fields4.find((field) => field.namespace === namespace && field.key === key);
6681
- };
6682
- var ReviewFieldError = class extends Error {
6683
- constructor(field) {
6684
- super(`Unreadable review field ${field}`);
6685
- this.field = field;
6686
- }
6687
- };
6688
- var readReviewField = (field, read) => {
6689
- try {
6690
- return read(field.value);
6691
- } catch {
6692
- throw new ReviewFieldError(`${field.namespace}.${field.key}`);
6693
- }
6694
- };
6695
-
6696
- // src/server/postPurchaseJudgeMeReviews.ts
6697
- var readJudgeMeReviews = (find) => {
6698
- const field = find("judgeme", "badge");
6699
- if (!field) return null;
6700
- return readReviewField(field, (value) => {
6701
- const html = z62.string().parse(value);
6702
- const rating = html.match(/data-average-rating=['"](\d+(?:\.\d+)?)['"]/);
6703
- const count = html.match(/data-number-of-reviews=['"](\d+)['"]/);
6704
- if (!rating || !count) throw new Error("Unreadable Judge.me attributes");
6705
- return {
6706
- rating: PostPurchaseCommonReviewRating.parse({ value: rating[1] }).value,
6707
- reviewCount: PostPurchaseReviewCount.parse(count[1])
6708
- };
6709
- });
6710
- };
6711
-
6712
- // src/server/postPurchaseOkendoReviews.ts
6713
- import { z as z63 } from "zod";
6714
- var Summary = z63.object({
6715
- reviewAverageValue: PostPurchaseCommonReviewRating.shape.value,
6716
- reviewCount: z63.union([PostPurchaseProductReviews.shape.reviewCount, PostPurchaseReviewCount])
6717
- });
6718
- var readOkendoReviews = (find) => {
6719
- const field = find("okendo", "summaryData");
6720
- if (!field) return null;
6721
- const summary = readReviewField(field, (value) => Summary.parse(JSON.parse(z63.string().parse(value))));
6722
- return { rating: summary.reviewAverageValue, reviewCount: summary.reviewCount };
6723
- };
6724
-
6725
- // src/server/postPurchaseScalarReviews.ts
6726
- import { z as z64 } from "zod";
6727
- var readScalarReviews = (find, namespace, ratingKey, countKey) => {
6728
- const rating = find(namespace, ratingKey);
6729
- const count = find(namespace, countKey);
6730
- if (!rating || !count) return null;
6731
- return {
6732
- rating: readReviewField(
6733
- rating,
6734
- (value) => PostPurchaseCommonReviewRating.parse({ value: z64.string().parse(value) }).value
6735
- ),
6736
- reviewCount: readReviewField(count, (value) => PostPurchaseReviewCount.parse(value))
6737
- };
6738
- };
6739
-
6740
- // src/server/postPurchaseReviewData.ts
6741
- var readStandardReviews = (find) => {
6742
- const rating = find("reviews", "rating");
6743
- const count = find("reviews", "rating_count");
6744
- if (!rating || !count) return null;
6745
- return {
6746
- rating: readReviewField(rating, (raw) => {
6747
- const value = z65.string().parse(raw);
6748
- return PostPurchaseCommonReviewRating.parse(/^\d+(?:\.\d+)?$/.test(value) ? { value } : JSON.parse(value)).value;
6749
- }),
6750
- reviewCount: readReviewField(count, (value) => PostPurchaseReviewCount.parse(value))
6751
- };
6752
- };
6753
- var readPostPurchaseReviewData = (provider, find) => {
6754
- if (provider === "yotpo") return readScalarReviews(find, "yotpo", "reviews_average", "reviews_count");
6755
- if (provider === "stamped") return readScalarReviews(find, "stamped", "reviews_average", "reviews_count");
6756
- if (provider === "judgeme") return readJudgeMeReviews(find);
6757
- if (provider === "okendo") return readOkendoReviews(find);
6758
- if (provider === "opinew") return readScalarReviews(find, "opinew_metafields", "reviews_average", "reviews_count");
6759
- if (provider === "reviewsio")
6760
- return readScalarReviews(find, "reviewscouk", "rating", "total") ?? readStandardReviews(find);
6761
- if (provider === "loox") return readScalarReviews(find, "loox", "avg_rating", "num_reviews");
6762
- if (provider === "junip") return readScalarReviews(find, "junip", "rating_average", "rating_count");
6763
- const rating = find("reviews", "rating");
6764
- const count = find("reviews", "rating_count");
6765
- if (!rating || !count) return null;
6766
- return {
6767
- rating: readReviewField(
6768
- rating,
6769
- (value) => PostPurchaseKlaviyoReviewRating.parse(JSON.parse(z65.string().parse(value))).value
6770
- ),
6771
- reviewCount: readReviewField(count, (value) => PostPurchaseReviewCount.parse(value))
6772
- };
6773
- };
6774
-
6775
- // src/server/postPurchaseReviewProvider.ts
6776
- import { z as z66 } from "zod";
6777
- var IntegrationInput = z66.record(z66.string(), z66.unknown()).prefault({});
6778
- var precedence = [
6779
- "klaviyo",
6780
- "judgeme",
6781
- "junip",
6782
- "loox",
6783
- "okendo",
6784
- "opinew",
6785
- "reviewsio",
6786
- "stamped",
6787
- "yotpo"
6788
- ];
6789
- var ReviewsIoFlag = z66.preprocess((value) => {
6790
- if (typeof value === "string") {
6791
- const flag = value.toLowerCase();
6792
- if (flag === "true" || flag === "yes") return true;
6793
- if (flag === "false" || flag === "no") return false;
6794
- }
6795
- return value;
6796
- }, PostPurchaseSettings.shape.integrations.shape.reviewsio);
6797
- var selectPostPurchaseReviewProvider = (raw) => {
6798
- const integrations = IntegrationInput.parse(raw);
6799
- for (const provider of precedence) {
6800
- const schema = provider === "reviewsio" ? ReviewsIoFlag : PostPurchaseSettings.shape.integrations.shape[provider];
6801
- if (schema.parse(integrations[provider])) return provider;
6802
- }
6803
- return null;
6804
- };
6805
-
6806
6868
  // src/server/postPurchaseReviews.ts
6807
6869
  var projectPostPurchaseReviews = (settings, { id: productId2, metafields }, report) => {
6808
6870
  try {