fresh-squeezy 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -167,6 +167,25 @@ var ISSUE_CODES = {
167
167
  WEBHOOK_NOT_FOUND: "WEBHOOK_NOT_FOUND",
168
168
  WEBHOOK_EVENTS_MISSING: "WEBHOOK_EVENTS_MISSING",
169
169
  WEBHOOK_OPTIONAL_EVENTS: "WEBHOOK_OPTIONAL_EVENTS",
170
+ DISCOUNT_NOT_FOUND: "DISCOUNT_NOT_FOUND",
171
+ DISCOUNT_DRAFT: "DISCOUNT_DRAFT",
172
+ DISCOUNT_EXPIRED: "DISCOUNT_EXPIRED",
173
+ DISCOUNT_NOT_STARTED: "DISCOUNT_NOT_STARTED",
174
+ DISCOUNT_REDEMPTIONS_EXHAUSTED: "DISCOUNT_REDEMPTIONS_EXHAUSTED",
175
+ DISCOUNT_INVALID_AMOUNT: "DISCOUNT_INVALID_AMOUNT",
176
+ DISCOUNT_STORE_MISMATCH: "DISCOUNT_STORE_MISMATCH",
177
+ LICENSE_KEY_NOT_FOUND: "LICENSE_KEY_NOT_FOUND",
178
+ LICENSE_KEY_DISABLED: "LICENSE_KEY_DISABLED",
179
+ LICENSE_KEY_EXPIRED: "LICENSE_KEY_EXPIRED",
180
+ LICENSE_KEY_AT_ACTIVATION_LIMIT: "LICENSE_KEY_AT_ACTIVATION_LIMIT",
181
+ LICENSE_KEY_STORE_MISMATCH: "LICENSE_KEY_STORE_MISMATCH",
182
+ PLAN_VARIANT_NOT_FOUND: "PLAN_VARIANT_NOT_FOUND",
183
+ PLAN_NOT_SUBSCRIPTION: "PLAN_NOT_SUBSCRIPTION",
184
+ PLAN_INVALID_INTERVAL: "PLAN_INVALID_INTERVAL",
185
+ PLAN_FREE_PRICE: "PLAN_FREE_PRICE",
186
+ PLAN_TRIAL_INCONSISTENT: "PLAN_TRIAL_INCONSISTENT",
187
+ PLAN_DRAFT: "PLAN_DRAFT",
188
+ PLAN_STORE_MISMATCH: "PLAN_STORE_MISMATCH",
170
189
  NETWORK_ERROR: "NETWORK_ERROR",
171
190
  UNKNOWN: "UNKNOWN"
172
191
  };
@@ -298,6 +317,9 @@ async function getProduct(http, productId) {
298
317
  }
299
318
 
300
319
  // src/resources/variants.ts
320
+ async function getVariant(http, variantId) {
321
+ return http.getResource(`/v1/variants/${variantId}`);
322
+ }
301
323
  async function listVariantsForProduct(http, productId) {
302
324
  return http.getCollection("/v1/variants", {
303
325
  "filter[product_id]": String(productId)
@@ -490,6 +512,358 @@ function normalizeUrl(raw) {
490
512
  return raw.replace(/\/+$/, "").toLowerCase();
491
513
  }
492
514
 
515
+ // src/resources/discounts.ts
516
+ async function getDiscount(http, discountId) {
517
+ return http.getResource(`/v1/discounts/${discountId}`);
518
+ }
519
+
520
+ // src/validate/discount.ts
521
+ async function validateDiscount(http, mode, options) {
522
+ const issues = [];
523
+ let discount;
524
+ try {
525
+ discount = await getDiscount(http, options.discountId);
526
+ } catch (err) {
527
+ if (err instanceof FreshSqueezyError && err.status === 404) {
528
+ issues.push(
529
+ issue(ISSUE_CODES.DISCOUNT_NOT_FOUND, "error", `Discount ${options.discountId} not found.`, {
530
+ suggestedFix: "Verify the discount ID in the Lemon Squeezy dashboard.",
531
+ context: { discountId: String(options.discountId) }
532
+ })
533
+ );
534
+ return buildResult("discount", mode, issues);
535
+ }
536
+ const message = err instanceof Error ? err.message : "Unknown error";
537
+ issues.push(issue(ISSUE_CODES.UNKNOWN, "error", message));
538
+ return buildResult("discount", mode, issues);
539
+ }
540
+ const attrs = discount.attributes;
541
+ const expectedStore = String(options.storeId);
542
+ const actualStore = String(attrs.store_id);
543
+ if (expectedStore !== actualStore) {
544
+ issues.push(
545
+ issue(
546
+ ISSUE_CODES.DISCOUNT_STORE_MISMATCH,
547
+ "error",
548
+ `Discount belongs to store ${actualStore}, expected ${expectedStore}.`,
549
+ {
550
+ suggestedFix: "Use the correct store ID or discount ID \u2014 discounts should not cross stores.",
551
+ context: { expectedStoreId: expectedStore, actualStoreId: actualStore }
552
+ }
553
+ )
554
+ );
555
+ }
556
+ if (attrs.status === "draft") {
557
+ issues.push(
558
+ issue(
559
+ ISSUE_CODES.DISCOUNT_DRAFT,
560
+ "warning",
561
+ `Discount "${attrs.name}" is in draft status \u2014 customers cannot redeem it.`,
562
+ {
563
+ suggestedFix: "Publish the discount in the Lemon Squeezy dashboard before sharing the code.",
564
+ context: { name: attrs.name, code: attrs.code }
565
+ }
566
+ )
567
+ );
568
+ }
569
+ const now = /* @__PURE__ */ new Date();
570
+ if (attrs.expires_at && new Date(attrs.expires_at) < now) {
571
+ issues.push(
572
+ issue(
573
+ ISSUE_CODES.DISCOUNT_EXPIRED,
574
+ "error",
575
+ `Discount "${attrs.name}" expired at ${attrs.expires_at}.`,
576
+ {
577
+ suggestedFix: "Extend the expiration date or create a new discount.",
578
+ context: { name: attrs.name, expiresAt: attrs.expires_at }
579
+ }
580
+ )
581
+ );
582
+ }
583
+ if (attrs.starts_at && new Date(attrs.starts_at) > now) {
584
+ issues.push(
585
+ issue(
586
+ ISSUE_CODES.DISCOUNT_NOT_STARTED,
587
+ "warning",
588
+ `Discount "${attrs.name}" starts at ${attrs.starts_at} \u2014 not yet active.`,
589
+ {
590
+ suggestedFix: "Wait for the start date or adjust it in the dashboard.",
591
+ context: { name: attrs.name, startsAt: attrs.starts_at }
592
+ }
593
+ )
594
+ );
595
+ }
596
+ if (attrs.is_limited_redemptions && attrs.max_redemptions <= 0) {
597
+ issues.push(
598
+ issue(
599
+ ISSUE_CODES.DISCOUNT_REDEMPTIONS_EXHAUSTED,
600
+ "warning",
601
+ `Discount "${attrs.name}" has limited redemptions with max_redemptions \u2264 0.`,
602
+ {
603
+ suggestedFix: "Increase max_redemptions or disable the redemption limit.",
604
+ context: { name: attrs.name, maxRedemptions: attrs.max_redemptions }
605
+ }
606
+ )
607
+ );
608
+ }
609
+ if (attrs.amount <= 0) {
610
+ issues.push(
611
+ issue(
612
+ ISSUE_CODES.DISCOUNT_INVALID_AMOUNT,
613
+ "error",
614
+ `Discount "${attrs.name}" has amount ${attrs.amount} \u2014 must be positive.`,
615
+ {
616
+ suggestedFix: "Set a positive discount amount in the dashboard.",
617
+ context: { name: attrs.name, amount: attrs.amount }
618
+ }
619
+ )
620
+ );
621
+ } else if (attrs.amount_type === "percent" && attrs.amount > 100) {
622
+ issues.push(
623
+ issue(
624
+ ISSUE_CODES.DISCOUNT_INVALID_AMOUNT,
625
+ "error",
626
+ `Discount "${attrs.name}" is ${attrs.amount}% \u2014 percent discounts cannot exceed 100%.`,
627
+ {
628
+ suggestedFix: "Set the discount to 100% or less.",
629
+ context: { name: attrs.name, amount: attrs.amount, amountType: attrs.amount_type }
630
+ }
631
+ )
632
+ );
633
+ }
634
+ return buildResult("discount", mode, issues, attrs);
635
+ }
636
+
637
+ // src/resources/licenseKeys.ts
638
+ async function getLicenseKey(http, licenseKeyId) {
639
+ return http.getResource(`/v1/license-keys/${licenseKeyId}`);
640
+ }
641
+
642
+ // src/validate/licenseKey.ts
643
+ async function validateLicenseKey(http, mode, options) {
644
+ const issues = [];
645
+ let licenseKey;
646
+ try {
647
+ licenseKey = await getLicenseKey(http, options.licenseKeyId);
648
+ } catch (err) {
649
+ if (err instanceof FreshSqueezyError && err.status === 404) {
650
+ issues.push(
651
+ issue(
652
+ ISSUE_CODES.LICENSE_KEY_NOT_FOUND,
653
+ "error",
654
+ `License key ${options.licenseKeyId} not found.`,
655
+ {
656
+ suggestedFix: "Verify the license key ID in the Lemon Squeezy dashboard.",
657
+ context: { licenseKeyId: String(options.licenseKeyId) }
658
+ }
659
+ )
660
+ );
661
+ return buildResult("licenseKey", mode, issues);
662
+ }
663
+ const message = err instanceof Error ? err.message : "Unknown error";
664
+ issues.push(issue(ISSUE_CODES.UNKNOWN, "error", message));
665
+ return buildResult("licenseKey", mode, issues);
666
+ }
667
+ const attrs = licenseKey.attributes;
668
+ const expectedStore = String(options.storeId);
669
+ const actualStore = String(attrs.store_id);
670
+ if (expectedStore !== actualStore) {
671
+ issues.push(
672
+ issue(
673
+ ISSUE_CODES.LICENSE_KEY_STORE_MISMATCH,
674
+ "error",
675
+ `License key belongs to store ${actualStore}, expected ${expectedStore}.`,
676
+ {
677
+ suggestedFix: "Use the correct store ID or license key ID \u2014 keys should not cross stores.",
678
+ context: { expectedStoreId: expectedStore, actualStoreId: actualStore }
679
+ }
680
+ )
681
+ );
682
+ }
683
+ if (attrs.disabled) {
684
+ issues.push(
685
+ issue(
686
+ ISSUE_CODES.LICENSE_KEY_DISABLED,
687
+ "error",
688
+ `License key ${attrs.key_short} is disabled.`,
689
+ {
690
+ suggestedFix: "Re-enable the license key in the Lemon Squeezy dashboard.",
691
+ context: { keyShort: attrs.key_short }
692
+ }
693
+ )
694
+ );
695
+ }
696
+ if (attrs.expires_at && new Date(attrs.expires_at) < /* @__PURE__ */ new Date()) {
697
+ issues.push(
698
+ issue(
699
+ ISSUE_CODES.LICENSE_KEY_EXPIRED,
700
+ "error",
701
+ `License key ${attrs.key_short} expired at ${attrs.expires_at}.`,
702
+ {
703
+ suggestedFix: "Extend the expiration date or issue a new license key.",
704
+ context: { keyShort: attrs.key_short, expiresAt: attrs.expires_at }
705
+ }
706
+ )
707
+ );
708
+ }
709
+ if (attrs.activation_limit !== null && attrs.instances_count >= attrs.activation_limit) {
710
+ issues.push(
711
+ issue(
712
+ ISSUE_CODES.LICENSE_KEY_AT_ACTIVATION_LIMIT,
713
+ "warning",
714
+ `License key ${attrs.key_short} has reached its activation limit (${attrs.instances_count}/${attrs.activation_limit}).`,
715
+ {
716
+ suggestedFix: "Increase the activation limit or deactivate unused instances.",
717
+ context: {
718
+ keyShort: attrs.key_short,
719
+ instancesCount: attrs.instances_count,
720
+ activationLimit: attrs.activation_limit
721
+ }
722
+ }
723
+ )
724
+ );
725
+ }
726
+ return buildResult("licenseKey", mode, issues, attrs);
727
+ }
728
+
729
+ // src/validate/subscriptionPlan.ts
730
+ var VALID_INTERVALS = /* @__PURE__ */ new Set(["day", "week", "month", "year"]);
731
+ async function validateSubscriptionPlan(http, mode, options) {
732
+ const issues = [];
733
+ let variant;
734
+ try {
735
+ variant = await getVariant(http, options.variantId);
736
+ } catch (err) {
737
+ if (err instanceof FreshSqueezyError && err.status === 404) {
738
+ issues.push(
739
+ issue(
740
+ ISSUE_CODES.PLAN_VARIANT_NOT_FOUND,
741
+ "error",
742
+ `Variant ${options.variantId} not found.`,
743
+ {
744
+ suggestedFix: "Verify the variant ID in the Lemon Squeezy dashboard.",
745
+ context: { variantId: String(options.variantId) }
746
+ }
747
+ )
748
+ );
749
+ return buildResult("subscriptionPlan", mode, issues);
750
+ }
751
+ const message = err instanceof Error ? err.message : "Unknown error";
752
+ issues.push(issue(ISSUE_CODES.UNKNOWN, "error", message));
753
+ return buildResult("subscriptionPlan", mode, issues);
754
+ }
755
+ const attrs = variant.attributes;
756
+ if (!attrs.is_subscription) {
757
+ issues.push(
758
+ issue(
759
+ ISSUE_CODES.PLAN_NOT_SUBSCRIPTION,
760
+ "error",
761
+ `Variant ${options.variantId} is not a subscription variant (is_subscription is false).`,
762
+ {
763
+ suggestedFix: "Use a variant that has subscription billing enabled, or use the regular variant validator.",
764
+ context: { variantId: String(options.variantId) }
765
+ }
766
+ )
767
+ );
768
+ }
769
+ if (!attrs.interval || !VALID_INTERVALS.has(attrs.interval)) {
770
+ issues.push(
771
+ issue(
772
+ ISSUE_CODES.PLAN_INVALID_INTERVAL,
773
+ "error",
774
+ `Subscription variant has invalid interval: "${attrs.interval ?? "missing"}". Expected one of: day, week, month, year.`,
775
+ {
776
+ suggestedFix: "Set a valid billing interval in the variant configuration.",
777
+ context: { interval: attrs.interval ?? null }
778
+ }
779
+ )
780
+ );
781
+ }
782
+ if (attrs.interval_count === null || attrs.interval_count <= 0) {
783
+ issues.push(
784
+ issue(
785
+ ISSUE_CODES.PLAN_INVALID_INTERVAL,
786
+ "error",
787
+ `Subscription variant has invalid interval_count: ${attrs.interval_count}. Must be a positive integer.`,
788
+ {
789
+ suggestedFix: "Set interval_count to a positive value (e.g. 1 for monthly, 2 for biweekly).",
790
+ context: { intervalCount: attrs.interval_count }
791
+ }
792
+ )
793
+ );
794
+ }
795
+ if (attrs.price === 0 && attrs.is_subscription) {
796
+ issues.push(
797
+ issue(
798
+ ISSUE_CODES.PLAN_FREE_PRICE,
799
+ "warning",
800
+ `Subscription variant has a price of 0 \u2014 this is almost always a misconfiguration for paid plans.`,
801
+ {
802
+ suggestedFix: "Set the variant price to the intended amount in cents, or confirm this is intentionally free.",
803
+ context: { price: attrs.price }
804
+ }
805
+ )
806
+ );
807
+ }
808
+ if (attrs.has_free_trial && (!attrs.trial_interval || (attrs.trial_interval_count ?? 0) <= 0)) {
809
+ issues.push(
810
+ issue(
811
+ ISSUE_CODES.PLAN_TRIAL_INCONSISTENT,
812
+ "warning",
813
+ `Subscription variant has free trial enabled but trial interval is misconfigured (interval: "${attrs.trial_interval ?? "missing"}", count: ${attrs.trial_interval_count ?? 0}).`,
814
+ {
815
+ suggestedFix: "Set a valid trial interval and count, or disable the free trial.",
816
+ context: {
817
+ trialInterval: attrs.trial_interval ?? null,
818
+ trialIntervalCount: attrs.trial_interval_count ?? null
819
+ }
820
+ }
821
+ )
822
+ );
823
+ }
824
+ if (attrs.status === "draft") {
825
+ issues.push(
826
+ issue(
827
+ ISSUE_CODES.PLAN_DRAFT,
828
+ "warning",
829
+ `Subscription variant is in draft status \u2014 customers cannot subscribe.`,
830
+ {
831
+ suggestedFix: "Publish the variant in the Lemon Squeezy dashboard.",
832
+ context: { status: attrs.status }
833
+ }
834
+ )
835
+ );
836
+ }
837
+ try {
838
+ const product = await getProduct(http, attrs.product_id);
839
+ const expectedStore = String(options.storeId);
840
+ const actualStore = String(product.attributes.store_id);
841
+ if (expectedStore !== actualStore) {
842
+ issues.push(
843
+ issue(
844
+ ISSUE_CODES.PLAN_STORE_MISMATCH,
845
+ "error",
846
+ `Subscription variant belongs to store ${actualStore} (via product ${attrs.product_id}), expected ${expectedStore}.`,
847
+ {
848
+ suggestedFix: "Use the correct store ID or variant ID \u2014 plans should not cross stores.",
849
+ context: { expectedStoreId: expectedStore, actualStoreId: actualStore, productId: String(attrs.product_id) }
850
+ }
851
+ )
852
+ );
853
+ }
854
+ } catch {
855
+ }
856
+ const summary = {
857
+ variantId: variant.id,
858
+ interval: attrs.interval,
859
+ intervalCount: attrs.interval_count,
860
+ price: attrs.price,
861
+ hasFreeTrial: attrs.has_free_trial,
862
+ status: attrs.status
863
+ };
864
+ return buildResult("subscriptionPlan", mode, issues, summary);
865
+ }
866
+
493
867
  // src/validate/doctor.ts
494
868
  async function doctor(http, mode, options = {}) {
495
869
  const results = [];
@@ -517,6 +891,30 @@ async function doctor(http, mode, options = {}) {
517
891
  })
518
892
  );
519
893
  }
894
+ if (options.storeId !== void 0 && options.discountId !== void 0) {
895
+ results.push(
896
+ await validateDiscount(http, mode, {
897
+ storeId: options.storeId,
898
+ discountId: options.discountId
899
+ })
900
+ );
901
+ }
902
+ if (options.storeId !== void 0 && options.licenseKeyId !== void 0) {
903
+ results.push(
904
+ await validateLicenseKey(http, mode, {
905
+ storeId: options.storeId,
906
+ licenseKeyId: options.licenseKeyId
907
+ })
908
+ );
909
+ }
910
+ if (options.storeId !== void 0 && options.variantId !== void 0) {
911
+ results.push(
912
+ await validateSubscriptionPlan(http, mode, {
913
+ storeId: options.storeId,
914
+ variantId: options.variantId
915
+ })
916
+ );
917
+ }
520
918
  const ok = results.every((result) => result.ok);
521
919
  return { ok, mode, results };
522
920
  }
@@ -532,10 +930,16 @@ function createFreshSqueezy(config = {}) {
532
930
  validateStore: (storeId) => validateStore(http, resolved.mode, storeId),
533
931
  validateProduct: (options) => validateProduct(http, resolved.mode, options),
534
932
  validateWebhook: (options) => validateWebhook(http, resolved.mode, options),
933
+ validateDiscount: (options) => validateDiscount(http, resolved.mode, options),
934
+ validateLicenseKey: (options) => validateLicenseKey(http, resolved.mode, options),
935
+ validateSubscriptionPlan: (options) => validateSubscriptionPlan(http, resolved.mode, options),
535
936
  doctor: (options) => doctor(http, resolved.mode, {
536
937
  storeId: options?.storeId ?? resolved.storeId,
537
938
  productId: options?.productId,
538
- webhookUrl: options?.webhookUrl
939
+ webhookUrl: options?.webhookUrl,
940
+ discountId: options?.discountId,
941
+ licenseKeyId: options?.licenseKeyId,
942
+ variantId: options?.variantId
539
943
  })
540
944
  };
541
945
  }
@@ -778,6 +1182,15 @@ async function runValidateCommand(target, options) {
778
1182
  if (target === "product") {
779
1183
  return emit(await runProduct(client, options), options);
780
1184
  }
1185
+ if (target === "discount") {
1186
+ return emit(await runDiscount(client, options), options);
1187
+ }
1188
+ if (target === "license-key") {
1189
+ return emit(await runLicenseKey(client, options), options);
1190
+ }
1191
+ if (target === "subscription-plan") {
1192
+ return emit(await runSubscriptionPlan(client, options), options);
1193
+ }
781
1194
  const storeIds = await resolveStoresForTarget(client, target, options);
782
1195
  const results = await runPerStore(client, target, storeIds, options);
783
1196
  if (options.json) {
@@ -832,6 +1245,21 @@ async function runProduct(client, options) {
832
1245
  expectedStoreId: expected
833
1246
  });
834
1247
  }
1248
+ async function runDiscount(client, options) {
1249
+ const discountId = required(options.discountId, "--discount-id is required for `validate discount`.");
1250
+ const storeId = required(options.storeIds?.[0], "--store-ids is required for `validate discount`.");
1251
+ return client.validateDiscount({ storeId, discountId });
1252
+ }
1253
+ async function runLicenseKey(client, options) {
1254
+ const licenseKeyId = required(options.licenseKeyId, "--license-key-id is required for `validate license-key`.");
1255
+ const storeId = required(options.storeIds?.[0], "--store-ids is required for `validate license-key`.");
1256
+ return client.validateLicenseKey({ storeId, licenseKeyId });
1257
+ }
1258
+ async function runSubscriptionPlan(client, options) {
1259
+ const variantId = required(options.variantId, "--variant-id is required for `validate subscription-plan`.");
1260
+ const storeId = required(options.storeIds?.[0], "--store-ids is required for `validate subscription-plan`.");
1261
+ return client.validateSubscriptionPlan({ storeId, variantId });
1262
+ }
835
1263
  function emit(result, options) {
836
1264
  if (options.json) {
837
1265
  process.stdout.write(`${JSON.stringify(result, null, 2)}
@@ -922,6 +1350,9 @@ validate.command("connection").description("Check that the API key authenticates
922
1350
  validate.command("store").description("Check one or more stores are reachable").option("--store-ids <ids>", "Comma-separated store IDs", parseCsv).option("--all-stores", "Run against every reachable store").option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON").action(async (opts) => runValidate("store", opts));
923
1351
  validate.command("product").description("Check a product is published with at least one variant").requiredOption("--product-id <id>", "Product ID to validate").option("--store-ids <ids>", "Expected owning store IDs (first is used for cross-check)", parseCsv).option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON").action(async (opts) => runValidate("product", opts));
924
1352
  validate.command("webhook").description("Check a webhook is registered with the recommended events").requiredOption("--webhook-url <url>", "Public webhook URL").option("--store-ids <ids>", "Comma-separated store IDs", parseCsv).option("--all-stores", "Run against every reachable store").option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON").action(async (opts) => runValidate("webhook", opts));
1353
+ validate.command("discount").description("Check a discount code is valid and redeemable").requiredOption("--discount-id <id>", "Discount ID to validate").option("--store-ids <ids>", "Store ID for ownership check (first ID used)", parseCsv).option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON").action(async (opts) => runValidate("discount", opts));
1354
+ validate.command("license-key").description("Check a license key is active and not at its activation limit").requiredOption("--license-key-id <id>", "License key ID to validate").option("--store-ids <ids>", "Store ID for ownership check (first ID used)", parseCsv).option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON").action(async (opts) => runValidate("license-key", opts));
1355
+ validate.command("subscription-plan").description("Check a subscription plan variant has valid billing interval and trial config").requiredOption("--variant-id <id>", "Variant ID of the subscription plan").option("--store-ids <ids>", "Store ID for ownership check (first ID used)", parseCsv).option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON").action(async (opts) => runValidate("subscription-plan", opts));
925
1356
  program.command("init").description("Interactive setup: ask for credentials, pick a store, run doctor").option("--env-file <path>", "Where to write credentials (default: .env.local)").action(async (opts) => {
926
1357
  const code = await runInitCommand({ envFile: opts.envFile });
927
1358
  process.exit(code);
@@ -946,6 +1377,9 @@ async function runValidate(target, opts) {
946
1377
  allStores: Boolean(opts.allStores),
947
1378
  productId: opts.productId,
948
1379
  webhookUrl: opts.webhookUrl,
1380
+ discountId: opts.discountId,
1381
+ licenseKeyId: opts.licenseKeyId,
1382
+ variantId: opts.variantId,
949
1383
  json: Boolean(opts.json),
950
1384
  isInteractive
951
1385
  });