@rebuy/rebuy 3.59.0 → 3.60.1

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