fresh-squeezy 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -5,6 +5,21 @@ import { readFileSync as readFileSync2 } from "fs";
5
5
  import { Command } from "commander";
6
6
  import dotenv from "dotenv";
7
7
 
8
+ // src/core/errors.ts
9
+ var FreshSqueezyError = class _FreshSqueezyError extends Error {
10
+ code;
11
+ status;
12
+ detail;
13
+ constructor(opts) {
14
+ super(opts.message);
15
+ this.name = "FreshSqueezyError";
16
+ this.code = opts.code;
17
+ this.status = opts.status;
18
+ this.detail = opts.detail;
19
+ Object.setPrototypeOf(this, _FreshSqueezyError.prototype);
20
+ }
21
+ };
22
+
8
23
  // src/cli/commands/augment.ts
9
24
  import { existsSync } from "fs";
10
25
  import { mkdir, readFile, writeFile } from "fs/promises";
@@ -217,21 +232,6 @@ export type {
217
232
  `;
218
233
  };
219
234
 
220
- // src/core/errors.ts
221
- var FreshSqueezyError = class _FreshSqueezyError extends Error {
222
- code;
223
- status;
224
- detail;
225
- constructor(opts) {
226
- super(opts.message);
227
- this.name = "FreshSqueezyError";
228
- this.code = opts.code;
229
- this.status = opts.status;
230
- this.detail = opts.detail;
231
- Object.setPrototypeOf(this, _FreshSqueezyError.prototype);
232
- }
233
- };
234
-
235
235
  // src/core/config.ts
236
236
  var DEFAULT_BASE_URL = "https://api.lemonsqueezy.com";
237
237
  var ENV_KEYS = {
@@ -314,46 +314,6 @@ var HttpClient = class {
314
314
  const doc = await this.request({ path: path4 });
315
315
  return doc.data;
316
316
  }
317
- /**
318
- * POST a JSON:API document and return the created `data` resource.
319
- *
320
- * @param path - API path starting with `/v1/...`.
321
- * @param body - JSON:API create document.
322
- * @returns The created resource.
323
- */
324
- async postResource(path4, body) {
325
- const doc = await this.request({ method: "POST", path: path4, body });
326
- return doc.data;
327
- }
328
- /**
329
- * PATCH a JSON:API document and return the updated `data` resource.
330
- *
331
- * @param path - API path starting with `/v1/...`.
332
- * @param body - JSON:API update document.
333
- * @returns The updated resource.
334
- */
335
- async patchResource(path4, body) {
336
- const doc = await this.request({ method: "PATCH", path: path4, body });
337
- return doc.data;
338
- }
339
- /**
340
- * DELETE a resource. Lemon Squeezy often returns an empty body on success.
341
- *
342
- * @param path - API path starting with `/v1/...`.
343
- * @returns Nothing — single void return, not a multi-field bag.
344
- */
345
- async deleteResource(path4) {
346
- await this.request({ method: "DELETE", path: path4 });
347
- }
348
- /**
349
- * Fetch a single page of a JSON:API collection. Most callers want
350
- * `paginate()` instead — this method is kept for one-shot lookups where
351
- * the caller knows the result fits in a single page.
352
- */
353
- async getCollection(path4, query) {
354
- const doc = await this.request({ path: path4, query });
355
- return doc.data;
356
- }
357
317
  /**
358
318
  * Walk a JSON:API collection across every page and concatenate the
359
319
  * results. Lemon Squeezy returns 25 items per page by default; without
@@ -416,582 +376,84 @@ var extractJsonApiErrors = (responseBody) => {
416
376
  );
417
377
  };
418
378
 
419
- // src/resources/affiliates.ts
420
- var getAffiliate = async (http, affiliateId) => {
421
- return http.getResource(`/v1/affiliates/${affiliateId}`);
422
- };
423
- var listAffiliatesForStore = async (http, storeId) => {
424
- return http.paginate("/v1/affiliates", {
425
- "filter[store_id]": String(storeId)
426
- });
427
- };
428
-
429
- // src/resources/checkouts.ts
430
- var getCheckout = async (http, checkoutId) => {
431
- return http.getResource(`/v1/checkouts/${checkoutId}`);
432
- };
433
- var listCheckoutsForStore = async (http, storeId) => {
434
- return http.paginate("/v1/checkouts", {
435
- "filter[store_id]": String(storeId)
436
- });
437
- };
438
- var createCheckout = async (http, body) => {
439
- return http.postResource("/v1/checkouts", body);
440
- };
441
-
442
- // src/resources/customers.ts
443
- var getCustomer = async (http, customerId) => {
444
- return http.getResource(`/v1/customers/${customerId}`);
445
- };
446
- var listCustomersForStore = async (http, storeId) => {
447
- return http.paginate("/v1/customers", {
448
- "filter[store_id]": String(storeId)
449
- });
450
- };
451
- var createCustomer = async (http, body) => {
452
- return http.postResource("/v1/customers", body);
453
- };
454
- var updateCustomer = async (http, customerId, body) => {
455
- return http.patchResource(`/v1/customers/${customerId}`, body);
456
- };
457
-
458
- // src/resources/discountRedemptions.ts
459
- var getDiscountRedemption = async (http, discountRedemptionId) => {
460
- return http.getResource(
461
- `/v1/discount-redemptions/${discountRedemptionId}`
462
- );
463
- };
464
- var listDiscountRedemptionsForDiscount = async (http, discountId) => {
465
- return http.paginate("/v1/discount-redemptions", {
466
- "filter[discount_id]": String(discountId)
467
- });
468
- };
469
-
470
- // src/resources/discounts.ts
471
- var getDiscount = async (http, discountId) => {
472
- return http.getResource(`/v1/discounts/${discountId}`);
473
- };
474
- var listDiscountsForStore = async (http, storeId) => {
475
- return http.paginate("/v1/discounts", {
476
- "filter[store_id]": String(storeId)
477
- });
478
- };
479
- var createDiscount = async (http, body) => {
480
- return http.postResource("/v1/discounts", body);
481
- };
482
- var deleteDiscount = async (http, discountId) => {
483
- await http.deleteResource(`/v1/discounts/${discountId}`);
484
- };
485
-
486
- // src/resources/files.ts
487
- var getFile = async (http, fileId) => {
488
- return http.getResource(`/v1/files/${fileId}`);
489
- };
490
- var listFilesForVariant = async (http, variantId) => {
491
- return http.paginate("/v1/files", {
492
- "filter[variant_id]": String(variantId)
493
- });
494
- };
495
-
496
- // src/resources/licenseKeyInstances.ts
497
- var getLicenseKeyInstance = async (http, licenseKeyInstanceId) => {
498
- return http.getResource(
499
- `/v1/license-key-instances/${licenseKeyInstanceId}`
500
- );
501
- };
502
- var listLicenseKeyInstancesForLicenseKey = async (http, licenseKeyId) => {
503
- return http.paginate("/v1/license-key-instances", {
504
- "filter[license_key_id]": String(licenseKeyId)
505
- });
506
- };
507
-
508
- // src/resources/licenseKeys.ts
509
- var getLicenseKey = async (http, licenseKeyId) => {
510
- return http.getResource(`/v1/license-keys/${licenseKeyId}`);
511
- };
512
- var listLicenseKeysForStore = async (http, storeId) => {
513
- return http.paginate("/v1/license-keys", {
514
- "filter[store_id]": String(storeId)
515
- });
516
- };
517
- var updateLicenseKey = async (http, licenseKeyId, body) => {
518
- return http.patchResource(`/v1/license-keys/${licenseKeyId}`, body);
519
- };
520
-
521
- // src/resources/orderItems.ts
522
- var getOrderItem = async (http, orderItemId) => {
523
- return http.getResource(`/v1/order-items/${orderItemId}`);
524
- };
525
- var listOrderItemsForOrder = async (http, orderId) => {
526
- return http.paginate("/v1/order-items", {
527
- "filter[order_id]": String(orderId)
528
- });
529
- };
530
-
531
- // src/resources/orders.ts
532
- var getOrder = async (http, orderId) => {
533
- return http.getResource(`/v1/orders/${orderId}`);
534
- };
535
- var listOrdersForStore = async (http, storeId) => {
536
- return http.paginate("/v1/orders", {
537
- "filter[store_id]": String(storeId)
538
- });
539
- };
540
- var refundOrder = async (http, orderId, body) => {
541
- return http.postResource(`/v1/orders/${orderId}/refund`, body ?? {});
542
- };
543
- var generateOrderInvoice = async (http, orderId, body) => {
544
- return http.request({
545
- method: "POST",
546
- path: `/v1/orders/${orderId}/generate-invoice`,
547
- body: body ?? {}
548
- });
549
- };
550
-
551
- // src/resources/prices.ts
552
- var getPrice = async (http, priceId) => {
553
- return http.getResource(`/v1/prices/${priceId}`);
554
- };
555
- var listPricesForVariant = async (http, variantId) => {
556
- return http.paginate("/v1/prices", {
557
- "filter[variant_id]": String(variantId)
558
- });
559
- };
560
-
561
- // src/resources/products.ts
562
- var getProduct = async (http, productId) => {
563
- return http.getResource(`/v1/products/${productId}`);
564
- };
565
- var listProducts = async (http, storeId) => {
566
- return http.paginate("/v1/products", {
567
- "filter[store_id]": String(storeId)
568
- });
569
- };
570
-
571
379
  // src/resources/registry.ts
572
- var resourceRegistry = [
573
- // users
574
- { resource: "user", verb: "get", docsPath: "users/retrieve-user", body: "none", idRole: "none" },
575
- // stores (read-only)
576
- { resource: "store", verb: "get", docsPath: "stores/retrieve-store", body: "none", idRole: "id" },
577
- {
578
- resource: "store",
579
- verb: "list",
580
- docsPath: "stores/list-all-stores",
581
- body: "none",
582
- idRole: "none"
583
- },
584
- // products (read-only)
585
- {
586
- resource: "product",
587
- verb: "get",
588
- docsPath: "products/retrieve-product",
589
- body: "none",
590
- idRole: "id"
591
- },
592
- {
593
- resource: "product",
594
- verb: "list",
595
- docsPath: "products/list-all-products",
596
- body: "none",
597
- idRole: "store"
598
- },
599
- // variants (read-only)
600
- {
601
- resource: "variant",
602
- verb: "get",
603
- docsPath: "variants/retrieve-variant",
604
- body: "none",
605
- idRole: "id"
606
- },
607
- {
608
- resource: "variant",
609
- verb: "list",
610
- docsPath: "variants/list-all-variants",
611
- body: "none",
612
- idRole: "parent"
613
- },
614
- // prices (read-only)
615
- { resource: "price", verb: "get", docsPath: "prices/retrieve-price", body: "none", idRole: "id" },
616
- {
617
- resource: "price",
618
- verb: "list",
619
- docsPath: "prices/list-all-prices",
620
- body: "none",
621
- idRole: "parent"
622
- },
623
- // files (read-only)
624
- { resource: "file", verb: "get", docsPath: "files/retrieve-file", body: "none", idRole: "id" },
625
- {
626
- resource: "file",
627
- verb: "list",
628
- docsPath: "files/list-all-files",
629
- body: "none",
630
- idRole: "parent"
631
- },
632
- // customers
633
- {
634
- resource: "customer",
635
- verb: "get",
636
- docsPath: "customers/retrieve-customer",
637
- body: "none",
638
- idRole: "id"
639
- },
640
- {
641
- resource: "customer",
642
- verb: "list",
643
- docsPath: "customers/list-all-customers",
644
- body: "none",
645
- idRole: "store"
646
- },
647
- {
648
- resource: "customer",
649
- verb: "create",
650
- docsPath: "customers/create-customer",
651
- body: "required",
652
- idRole: "none"
653
- },
654
- {
655
- resource: "customer",
656
- verb: "update",
657
- docsPath: "customers/update-customer",
658
- body: "required",
659
- idRole: "id"
660
- },
661
- // orders
662
- { resource: "order", verb: "get", docsPath: "orders/retrieve-order", body: "none", idRole: "id" },
663
- {
664
- resource: "order",
665
- verb: "list",
666
- docsPath: "orders/list-all-orders",
667
- body: "none",
668
- idRole: "store"
669
- },
670
- {
671
- resource: "order",
672
- verb: "refund",
673
- docsPath: "orders/issue-refund",
674
- body: "optional",
675
- destructive: true,
676
- idRole: "id"
677
- },
678
- {
679
- resource: "order",
680
- verb: "generate-invoice",
681
- docsPath: "orders/generate-order-invoice",
682
- body: "optional",
683
- idRole: "id"
684
- },
685
- // order-items (read-only)
686
- {
687
- resource: "order-item",
688
- verb: "get",
689
- docsPath: "order-items/retrieve-order-item",
690
- body: "none",
691
- idRole: "id"
692
- },
693
- {
694
- resource: "order-item",
695
- verb: "list",
696
- docsPath: "order-items/list-all-order-items",
697
- body: "none",
698
- idRole: "parent"
699
- },
700
- // subscriptions
701
- {
702
- resource: "subscription",
703
- verb: "get",
704
- docsPath: "subscriptions/retrieve-subscription",
705
- body: "none",
706
- idRole: "id"
707
- },
708
- {
709
- resource: "subscription",
710
- verb: "list",
711
- docsPath: "subscriptions/list-all-subscriptions",
712
- body: "none",
713
- idRole: "store"
714
- },
715
- {
716
- resource: "subscription",
717
- verb: "update",
718
- docsPath: "subscriptions/update-subscription",
719
- body: "required",
720
- idRole: "id"
721
- },
722
- {
723
- resource: "subscription",
724
- verb: "cancel",
725
- docsPath: "subscriptions/cancel-subscription",
726
- body: "none",
727
- destructive: true,
728
- idRole: "id"
729
- },
730
- // subscription-items
731
- {
732
- resource: "subscription-item",
733
- verb: "get",
734
- docsPath: "subscription-items/retrieve-subscription-item",
735
- body: "none",
736
- idRole: "id"
737
- },
738
- {
739
- resource: "subscription-item",
740
- verb: "list",
741
- docsPath: "subscription-items/list-all-subscription-items",
742
- body: "none",
743
- idRole: "parent"
744
- },
745
- {
746
- resource: "subscription-item",
747
- verb: "update",
748
- docsPath: "subscription-items/update-subscription-item",
749
- body: "required",
750
- idRole: "id"
751
- },
752
- {
753
- resource: "subscription-item",
754
- verb: "current-usage",
755
- docsPath: "subscription-items/retrieve-subscription-item-current-usage",
756
- body: "none",
757
- idRole: "id"
758
- },
759
- // subscription-invoices
760
- {
761
- resource: "subscription-invoice",
762
- verb: "get",
763
- docsPath: "subscription-invoices/retrieve-subscription-invoice",
764
- body: "none",
765
- idRole: "id"
766
- },
767
- {
768
- resource: "subscription-invoice",
769
- verb: "list",
770
- docsPath: "subscription-invoices/list-all-subscription-invoices",
771
- body: "none",
772
- idRole: "parent"
773
- },
774
- {
775
- resource: "subscription-invoice",
776
- verb: "refund",
777
- docsPath: "subscription-invoices/issue-refund",
778
- body: "optional",
779
- destructive: true,
780
- idRole: "id"
781
- },
782
- {
783
- resource: "subscription-invoice",
784
- verb: "generate-invoice",
785
- docsPath: "subscription-invoices/generate-subscription-invoice",
380
+ var VERB_RULES = {
381
+ get: { method: "GET", path: "/{id}", docs: "retrieve-{r}", body: "none" },
382
+ list: { method: "GET", path: "", docs: "list-all-{r}s", body: "none" },
383
+ create: { method: "POST", path: "", docs: "create-{r}", body: "required" },
384
+ update: { method: "PATCH", path: "/{id}", docs: "update-{r}", body: "required" },
385
+ delete: { method: "DELETE", path: "/{id}", docs: "delete-{r}", body: "none", destructive: true },
386
+ cancel: { method: "DELETE", path: "/{id}", docs: "cancel-{r}", body: "none", destructive: true },
387
+ refund: {
388
+ method: "POST",
389
+ path: "/{id}/refund",
390
+ docs: "issue-refund",
786
391
  body: "optional",
787
- idRole: "id"
788
- },
789
- // usage-records
790
- {
791
- resource: "usage-record",
792
- verb: "get",
793
- docsPath: "usage-records/retrieve-usage-record",
794
- body: "none",
795
- idRole: "id"
796
- },
797
- {
798
- resource: "usage-record",
799
- verb: "list",
800
- docsPath: "usage-records/list-all-usage-records",
801
- body: "none",
802
- idRole: "parent"
803
- },
804
- {
805
- resource: "usage-record",
806
- verb: "create",
807
- docsPath: "usage-records/create-usage-record",
808
- body: "required",
809
- idRole: "none"
810
- },
811
- // discounts
812
- {
813
- resource: "discount",
814
- verb: "get",
815
- docsPath: "discounts/retrieve-discount",
816
- body: "none",
817
- idRole: "id"
818
- },
819
- {
820
- resource: "discount",
821
- verb: "list",
822
- docsPath: "discounts/list-all-discounts",
823
- body: "none",
824
- idRole: "store"
825
- },
826
- {
827
- resource: "discount",
828
- verb: "create",
829
- docsPath: "discounts/create-discount",
830
- body: "required",
831
- idRole: "none"
832
- },
833
- {
834
- resource: "discount",
835
- verb: "delete",
836
- docsPath: "discounts/delete-discount",
837
- body: "none",
838
- destructive: true,
839
- idRole: "id"
840
- },
841
- // discount-redemptions (read-only)
842
- {
843
- resource: "discount-redemption",
844
- verb: "get",
845
- docsPath: "discount-redemptions/retrieve-discount-redemption",
846
- body: "none",
847
- idRole: "id"
848
- },
849
- {
850
- resource: "discount-redemption",
851
- verb: "list",
852
- docsPath: "discount-redemptions/list-all-discount-redemptions",
853
- body: "none",
854
- idRole: "parent"
855
- },
856
- // license-keys
857
- {
858
- resource: "license-key",
859
- verb: "get",
860
- docsPath: "license-keys/retrieve-license-key",
861
- body: "none",
862
- idRole: "id"
392
+ destructive: true
863
393
  },
864
- {
865
- resource: "license-key",
866
- verb: "list",
867
- docsPath: "license-keys/list-all-license-keys",
868
- body: "none",
869
- idRole: "store"
870
- },
871
- {
872
- resource: "license-key",
873
- verb: "update",
874
- docsPath: "license-keys/update-license-key",
875
- body: "required",
876
- idRole: "id"
877
- },
878
- // license-key-instances (read-only)
879
- {
880
- resource: "license-key-instance",
881
- verb: "get",
882
- docsPath: "license-key-instances/retrieve-license-key-instance",
883
- body: "none",
884
- idRole: "id"
885
- },
886
- {
887
- resource: "license-key-instance",
888
- verb: "list",
889
- docsPath: "license-key-instances/list-all-license-key-instances",
890
- body: "none",
891
- idRole: "parent"
892
- },
893
- // checkouts
894
- {
895
- resource: "checkout",
896
- verb: "get",
897
- docsPath: "checkouts/retrieve-checkout",
898
- body: "none",
899
- idRole: "id"
900
- },
901
- {
902
- resource: "checkout",
903
- verb: "list",
904
- docsPath: "checkouts/list-all-checkouts",
905
- body: "none",
906
- idRole: "store"
907
- },
908
- {
909
- resource: "checkout",
910
- verb: "create",
911
- docsPath: "checkouts/create-checkout",
912
- body: "required",
913
- idRole: "none"
914
- },
915
- // webhooks
916
- {
917
- resource: "webhook",
918
- verb: "get",
919
- docsPath: "webhooks/retrieve-webhook",
920
- body: "none",
921
- idRole: "id"
922
- },
923
- {
924
- resource: "webhook",
925
- verb: "list",
926
- docsPath: "webhooks/list-all-webhooks",
927
- body: "none",
928
- idRole: "store"
929
- },
930
- {
931
- resource: "webhook",
932
- verb: "create",
933
- docsPath: "webhooks/create-webhook",
934
- body: "required",
935
- idRole: "none"
936
- },
937
- {
938
- resource: "webhook",
939
- verb: "update",
940
- docsPath: "webhooks/update-webhook",
941
- body: "required",
942
- idRole: "id"
943
- },
944
- {
945
- resource: "webhook",
946
- verb: "delete",
947
- docsPath: "webhooks/delete-webhook",
948
- body: "none",
949
- destructive: true,
950
- idRole: "id"
951
- },
952
- // affiliates (read-only)
953
- {
954
- resource: "affiliate",
955
- verb: "get",
956
- docsPath: "affiliates/retrieve-affiliate",
957
- body: "none",
958
- idRole: "id"
394
+ "generate-invoice": {
395
+ method: "POST",
396
+ path: "/{id}/generate-invoice",
397
+ docs: "generate-{r}-invoice",
398
+ body: "optional"
959
399
  },
960
- {
961
- resource: "affiliate",
962
- verb: "list",
963
- docsPath: "affiliates/list-all-affiliates",
964
- body: "none",
965
- idRole: "store"
966
- }
400
+ "current-usage": {
401
+ method: "GET",
402
+ path: "/{id}/current-usage",
403
+ docs: "retrieve-{r}-current-usage",
404
+ body: "none"
405
+ }
406
+ };
407
+ var RESOURCES = [
408
+ ["user", ["get"]],
409
+ ["store", ["get", "list"]],
410
+ ["product", ["get", "list"], "store"],
411
+ ["variant", ["get", "list"], "product"],
412
+ ["price", ["get", "list"], "variant"],
413
+ ["file", ["get", "list"], "variant"],
414
+ ["customer", ["get", "list", "create", "update"], "store"],
415
+ ["order", ["get", "list", "refund", "generate-invoice"], "store"],
416
+ ["order-item", ["get", "list"], "order"],
417
+ ["subscription", ["get", "list", "update", "cancel"], "store"],
418
+ ["subscription-item", ["get", "list", "update", "current-usage"], "subscription"],
419
+ ["subscription-invoice", ["get", "list", "refund", "generate-invoice"], "subscription"],
420
+ ["usage-record", ["get", "list", "create"], "subscription-item"],
421
+ ["discount", ["get", "list", "create", "delete"], "store"],
422
+ ["discount-redemption", ["get", "list"], "discount"],
423
+ ["license-key", ["get", "list", "update"], "store"],
424
+ ["license-key-instance", ["get", "list"], "license-key"],
425
+ ["checkout", ["get", "list", "create"], "store"],
426
+ ["webhook", ["get", "list", "create", "update", "delete"], "store"],
427
+ ["affiliate", ["get", "list"], "store"]
967
428
  ];
968
- var RESOURCE_ALIASES = {
969
- products: "product",
970
- stores: "store",
971
- customers: "customer",
972
- orders: "order",
973
- "order-items": "order-item",
974
- subscriptions: "subscription",
975
- "subscription-items": "subscription-item",
976
- "subscription-invoices": "subscription-invoice",
977
- "usage-records": "usage-record",
978
- discounts: "discount",
979
- "discount-redemptions": "discount-redemption",
980
- "license-keys": "license-key",
981
- "license-key-instances": "license-key-instance",
982
- checkouts: "checkout",
983
- webhooks: "webhook",
984
- affiliates: "affiliate",
985
- variants: "variant",
986
- prices: "price",
987
- files: "file",
988
- users: "user"
429
+ var DOCS_PATH_EXCEPTIONS = {
430
+ "subscription-invoice:generate-invoice": "subscription-invoices/generate-subscription-invoice"
431
+ };
432
+ var USER_ME_PATH = "/v1/users/me";
433
+ var listIdRole = (listScope) => {
434
+ if (!listScope) return "none";
435
+ return listScope === "store" ? "store" : "parent";
436
+ };
437
+ var toSpec = (resource, verb, listScope) => {
438
+ const rule = VERB_RULES[verb];
439
+ const docsPath = DOCS_PATH_EXCEPTIONS[`${resource}:${verb}`] ?? `${resource}s/${rule.docs.replaceAll("{r}", resource)}`;
440
+ let idRole = rule.path.includes("{id}") ? "id" : "none";
441
+ if (verb === "list") idRole = listIdRole(listScope);
442
+ if (resource === "user") idRole = "none";
443
+ return {
444
+ resource,
445
+ verb,
446
+ docsPath,
447
+ body: rule.body,
448
+ ...rule.destructive ? { destructive: true } : {},
449
+ idRole
450
+ };
989
451
  };
452
+ var resourceRegistry = RESOURCES.flatMap(
453
+ ([resource, verbs, listScope]) => verbs.map((verb) => toSpec(resource, verb, listScope))
454
+ );
990
455
  var normalizeResourceName = (resource) => {
991
456
  const normalized = resource.replace(/_/g, "-").toLowerCase();
992
- if (RESOURCE_ALIASES[normalized]) {
993
- return RESOURCE_ALIASES[normalized];
994
- }
995
457
  if (normalized.endsWith("s") && normalized !== "status") {
996
458
  return normalized.slice(0, -1);
997
459
  }
@@ -1004,271 +466,11 @@ var findResourceVerb = (resource, verb) => {
1004
466
  var listRegisteredResources = () => {
1005
467
  return [...new Set(resourceRegistry.map((entry) => entry.resource))].sort();
1006
468
  };
1007
-
1008
- // src/resources/stores.ts
1009
- var getStore = async (http, storeId) => {
1010
- return http.getResource(`/v1/stores/${storeId}`);
1011
- };
1012
- var listStores = async (http) => {
1013
- return http.paginate("/v1/stores");
1014
- };
1015
-
1016
- // src/resources/subscriptionInvoices.ts
1017
- var getSubscriptionInvoice = async (http, subscriptionInvoiceId) => {
1018
- return http.getResource(
1019
- `/v1/subscription-invoices/${subscriptionInvoiceId}`
1020
- );
1021
- };
1022
- var listSubscriptionInvoicesForSubscription = async (http, subscriptionId) => {
1023
- return http.paginate("/v1/subscription-invoices", {
1024
- "filter[subscription_id]": String(subscriptionId)
1025
- });
1026
- };
1027
- var refundSubscriptionInvoice = async (http, subscriptionInvoiceId, body) => {
1028
- return http.postResource(
1029
- `/v1/subscription-invoices/${subscriptionInvoiceId}/refund`,
1030
- body ?? {}
1031
- );
1032
- };
1033
- var generateSubscriptionInvoice = async (http, subscriptionInvoiceId, body) => {
1034
- return http.request({
1035
- method: "POST",
1036
- path: `/v1/subscription-invoices/${subscriptionInvoiceId}/generate-invoice`,
1037
- body: body ?? {}
1038
- });
1039
- };
1040
-
1041
- // src/resources/subscriptionItems.ts
1042
- var getSubscriptionItem = async (http, subscriptionItemId) => {
1043
- return http.getResource(
1044
- `/v1/subscription-items/${subscriptionItemId}`
1045
- );
1046
- };
1047
- var listSubscriptionItemsForSubscription = async (http, subscriptionId) => {
1048
- return http.paginate("/v1/subscription-items", {
1049
- "filter[subscription_id]": String(subscriptionId)
1050
- });
1051
- };
1052
- var updateSubscriptionItem = async (http, subscriptionItemId, body) => {
1053
- return http.patchResource(
1054
- `/v1/subscription-items/${subscriptionItemId}`,
1055
- body
1056
- );
1057
- };
1058
- var getSubscriptionItemCurrentUsage = async (http, subscriptionItemId) => {
1059
- return http.request({
1060
- path: `/v1/subscription-items/${subscriptionItemId}/current-usage`
1061
- });
1062
- };
1063
-
1064
- // src/resources/subscriptions.ts
1065
- var getSubscription = async (http, subscriptionId) => {
1066
- return http.getResource(`/v1/subscriptions/${subscriptionId}`);
1067
- };
1068
- var listSubscriptionsForStore = async (http, storeId) => {
1069
- return http.paginate("/v1/subscriptions", {
1070
- "filter[store_id]": String(storeId)
1071
- });
1072
- };
1073
- var updateSubscription = async (http, subscriptionId, body) => {
1074
- return http.patchResource(`/v1/subscriptions/${subscriptionId}`, body);
1075
- };
1076
- var cancelSubscription = async (http, subscriptionId) => {
1077
- const doc = await http.request({
1078
- method: "DELETE",
1079
- path: `/v1/subscriptions/${subscriptionId}`
1080
- });
1081
- return doc?.data;
1082
- };
1083
-
1084
- // src/resources/usageRecords.ts
1085
- var getUsageRecord = async (http, usageRecordId) => {
1086
- return http.getResource(`/v1/usage-records/${usageRecordId}`);
1087
- };
1088
- var listUsageRecordsForSubscriptionItem = async (http, subscriptionItemId) => {
1089
- return http.paginate("/v1/usage-records", {
1090
- "filter[subscription_item_id]": String(subscriptionItemId)
1091
- });
1092
- };
1093
- var createUsageRecord = async (http, body) => {
1094
- return http.postResource("/v1/usage-records", body);
1095
- };
1096
-
1097
- // src/resources/users.ts
1098
- var getAuthenticatedUser = async (http) => {
1099
- return http.request({ path: "/v1/users/me" });
1100
- };
1101
-
1102
- // src/resources/variants.ts
1103
- var getVariant = async (http, variantId) => {
1104
- return http.getResource(`/v1/variants/${variantId}`);
1105
- };
1106
- var listVariantsForProduct = async (http, productId) => {
1107
- return http.paginate("/v1/variants", {
1108
- "filter[product_id]": String(productId)
1109
- });
1110
- };
1111
-
1112
- // src/resources/webhooks.ts
1113
- var getWebhook = async (http, webhookId) => {
1114
- return http.getResource(`/v1/webhooks/${webhookId}`);
1115
- };
1116
- var listWebhooksForStore = async (http, storeId) => {
1117
- return http.paginate("/v1/webhooks", {
1118
- "filter[store_id]": String(storeId)
1119
- });
1120
- };
1121
- var createWebhook = async (http, body) => {
1122
- return http.postResource("/v1/webhooks", body);
1123
- };
1124
- var updateWebhook = async (http, webhookId, body) => {
1125
- return http.patchResource(`/v1/webhooks/${webhookId}`, body);
1126
- };
1127
- var deleteWebhook = async (http, webhookId) => {
1128
- await http.deleteResource(`/v1/webhooks/${webhookId}`);
1129
- };
1130
-
1131
- // src/resources/invokeOp.ts
1132
- var invokeOp = async (http, resource, verb, args = {}) => {
1133
- const spec = findResourceVerb(resource, verb);
1134
- if (!spec) {
1135
- throw new FreshSqueezyError({
1136
- code: "UNKNOWN_OP",
1137
- message: `Unknown or unsupported op: ${verb} ${resource}. Run \`fresh-squeezy ops --list\`.`
1138
- });
1139
- }
1140
- const key = `${spec.resource}:${spec.verb}`;
1141
- switch (key) {
1142
- case "user:get": {
1143
- const doc = await getAuthenticatedUser(http);
1144
- return doc.data;
1145
- }
1146
- case "store:get":
1147
- return getStore(http, requireId(args, "store"));
1148
- case "store:list":
1149
- return listStores(http);
1150
- case "product:get":
1151
- return getProduct(http, requireId(args, "product"));
1152
- case "product:list":
1153
- return listProducts(http, requireStore(args));
1154
- case "variant:get":
1155
- return getVariant(http, requireId(args, "variant"));
1156
- case "variant:list":
1157
- return listVariantsForProduct(http, requireParent(args, "product"));
1158
- case "price:get":
1159
- return getPrice(http, requireId(args, "price"));
1160
- case "price:list":
1161
- return listPricesForVariant(http, requireParent(args, "variant"));
1162
- case "file:get":
1163
- return getFile(http, requireId(args, "file"));
1164
- case "file:list":
1165
- return listFilesForVariant(http, requireParent(args, "variant"));
1166
- case "customer:get":
1167
- return getCustomer(http, requireId(args, "customer"));
1168
- case "customer:list":
1169
- return listCustomersForStore(http, requireStore(args));
1170
- case "customer:create":
1171
- return createCustomer(http, requireBody(args));
1172
- case "customer:update":
1173
- return updateCustomer(http, requireId(args, "customer"), requireBody(args));
1174
- case "order:get":
1175
- return getOrder(http, requireId(args, "order"));
1176
- case "order:list":
1177
- return listOrdersForStore(http, requireStore(args));
1178
- case "order:refund":
1179
- return refundOrder(http, requireId(args, "order"), args.body);
1180
- case "order:generate-invoice":
1181
- return generateOrderInvoice(http, requireId(args, "order"), args.body);
1182
- case "order-item:get":
1183
- return getOrderItem(http, requireId(args, "order-item"));
1184
- case "order-item:list":
1185
- return listOrderItemsForOrder(http, requireParent(args, "order"));
1186
- case "subscription:get":
1187
- return getSubscription(http, requireId(args, "subscription"));
1188
- case "subscription:list":
1189
- return listSubscriptionsForStore(http, requireStore(args));
1190
- case "subscription:update":
1191
- return updateSubscription(http, requireId(args, "subscription"), requireBody(args));
1192
- case "subscription:cancel":
1193
- return cancelSubscription(http, requireId(args, "subscription"));
1194
- case "subscription-item:get":
1195
- return getSubscriptionItem(http, requireId(args, "subscription-item"));
1196
- case "subscription-item:list":
1197
- return listSubscriptionItemsForSubscription(http, requireParent(args, "subscription"));
1198
- case "subscription-item:update":
1199
- return updateSubscriptionItem(http, requireId(args, "subscription-item"), requireBody(args));
1200
- case "subscription-item:current-usage":
1201
- return getSubscriptionItemCurrentUsage(http, requireId(args, "subscription-item"));
1202
- case "subscription-invoice:get":
1203
- return getSubscriptionInvoice(http, requireId(args, "subscription-invoice"));
1204
- case "subscription-invoice:list":
1205
- return listSubscriptionInvoicesForSubscription(http, requireParent(args, "subscription"));
1206
- case "subscription-invoice:refund":
1207
- return refundSubscriptionInvoice(http, requireId(args, "subscription-invoice"), args.body);
1208
- case "subscription-invoice:generate-invoice":
1209
- return generateSubscriptionInvoice(http, requireId(args, "subscription-invoice"), args.body);
1210
- case "usage-record:get":
1211
- return getUsageRecord(http, requireId(args, "usage-record"));
1212
- case "usage-record:list":
1213
- return listUsageRecordsForSubscriptionItem(http, requireParent(args, "subscription-item"));
1214
- case "usage-record:create":
1215
- return createUsageRecord(http, requireBody(args));
1216
- case "discount:get":
1217
- return getDiscount(http, requireId(args, "discount"));
1218
- case "discount:list":
1219
- return listDiscountsForStore(http, requireStore(args));
1220
- case "discount:create":
1221
- return createDiscount(http, requireBody(args));
1222
- case "discount:delete":
1223
- await deleteDiscount(http, requireId(args, "discount"));
1224
- return void 0;
1225
- case "discount-redemption:get":
1226
- return getDiscountRedemption(http, requireId(args, "discount-redemption"));
1227
- case "discount-redemption:list":
1228
- return listDiscountRedemptionsForDiscount(http, requireParent(args, "discount"));
1229
- case "license-key:get":
1230
- return getLicenseKey(http, requireId(args, "license-key"));
1231
- case "license-key:list":
1232
- return listLicenseKeysForStore(http, requireStore(args));
1233
- case "license-key:update":
1234
- return updateLicenseKey(http, requireId(args, "license-key"), requireBody(args));
1235
- case "license-key-instance:get":
1236
- return getLicenseKeyInstance(http, requireId(args, "license-key-instance"));
1237
- case "license-key-instance:list":
1238
- return listLicenseKeyInstancesForLicenseKey(http, requireParent(args, "license-key"));
1239
- case "checkout:get":
1240
- return getCheckout(http, requireId(args, "checkout"));
1241
- case "checkout:list":
1242
- return listCheckoutsForStore(http, requireStore(args));
1243
- case "checkout:create":
1244
- return createCheckout(http, requireBody(args));
1245
- case "webhook:get":
1246
- return getWebhook(http, requireId(args, "webhook"));
1247
- case "webhook:list":
1248
- return listWebhooksForStore(http, requireStore(args));
1249
- case "webhook:create":
1250
- return createWebhook(http, requireBody(args));
1251
- case "webhook:update":
1252
- return updateWebhook(http, requireId(args, "webhook"), requireBody(args));
1253
- case "webhook:delete":
1254
- await deleteWebhook(http, requireId(args, "webhook"));
1255
- return void 0;
1256
- case "affiliate:get":
1257
- return getAffiliate(http, requireId(args, "affiliate"));
1258
- case "affiliate:list":
1259
- return listAffiliatesForStore(http, requireStore(args));
1260
- default:
1261
- throw new FreshSqueezyError({
1262
- code: "UNKNOWN_OP",
1263
- message: `Op registered but not wired: ${key}`
1264
- });
1265
- }
1266
- };
1267
- var requireId = (args, label) => {
469
+ var requireId = (args, spec) => {
1268
470
  if (args.id === void 0 || args.id === "") {
1269
471
  throw new FreshSqueezyError({
1270
472
  code: "MISSING_ARG",
1271
- message: `--id is required for this ${label} operation.`
473
+ message: `--id is required for this ${spec.resource} operation.`
1272
474
  });
1273
475
  }
1274
476
  return args.id;
@@ -1282,11 +484,11 @@ var requireStore = (args) => {
1282
484
  }
1283
485
  return args.storeId;
1284
486
  };
1285
- var requireParent = (args, label) => {
487
+ var requireParent = (args, parent) => {
1286
488
  if (args.parentId === void 0 || args.parentId === "") {
1287
489
  throw new FreshSqueezyError({
1288
490
  code: "MISSING_ARG",
1289
- message: `--parent-id (${label} id) is required for this list operation.`
491
+ message: `--parent-id (${parent} id) is required for this list operation.`
1290
492
  });
1291
493
  }
1292
494
  return args.parentId;
@@ -1300,15 +502,44 @@ var requireBody = (args) => {
1300
502
  }
1301
503
  return args.body;
1302
504
  };
1303
-
1304
- // src/core/mode.ts
1305
- var resolveActualMode = (testMode) => {
1306
- if (testMode === true) return "test";
1307
- if (testMode === false) return "live";
505
+ var listFilter = (spec, args) => {
506
+ const listScope = RESOURCES.find(([resource]) => resource === spec.resource)?.[2];
507
+ if (!listScope) return void 0;
508
+ const scopeId = listScope === "store" ? requireStore(args) : requireParent(args, listScope);
509
+ return { [`filter[${listScope.replace(/-/g, "_")}_id]`]: String(scopeId) };
510
+ };
511
+ var requestBody = (rule, args) => {
512
+ if (rule.body === "required") return requireBody(args);
513
+ if (rule.body === "optional") return args.body ?? {};
1308
514
  return void 0;
1309
515
  };
516
+ var invokeOp = async (http, resource, verb, args = {}) => {
517
+ const spec = findResourceVerb(resource, verb);
518
+ if (!spec) {
519
+ throw new FreshSqueezyError({
520
+ code: "UNKNOWN_OP",
521
+ message: `Unknown or unsupported op: ${verb} ${resource}. Run \`fresh-squeezy ops --list\`.`
522
+ });
523
+ }
524
+ if (spec.resource === "user") {
525
+ const doc2 = await http.request({ path: USER_ME_PATH });
526
+ return doc2.data;
527
+ }
528
+ const collectionPath = `/v1/${spec.resource}s`;
529
+ if (spec.verb === "list") {
530
+ return http.paginate(collectionPath, listFilter(spec, args));
531
+ }
532
+ const rule = VERB_RULES[spec.verb];
533
+ const path4 = collectionPath + rule.path.replace("{id}", () => String(requireId(args, spec)));
534
+ const doc = await http.request({
535
+ method: rule.method,
536
+ path: path4,
537
+ body: requestBody(rule, args)
538
+ });
539
+ return doc?.data ?? doc;
540
+ };
1310
541
 
1311
- // src/validate/rules.ts
542
+ // src/validate/issues.ts
1312
543
  var ISSUE_CODES = {
1313
544
  AUTH_FAILED: "AUTH_FAILED",
1314
545
  MODE_MISMATCH: "MODE_MISMATCH",
@@ -1432,11 +663,19 @@ var checkStoreOwnership = (check) => {
1432
663
  };
1433
664
 
1434
665
  // src/validate/connection.ts
666
+ var resolveActualMode = (testMode) => {
667
+ if (testMode === true) return "test";
668
+ if (testMode === false) return "live";
669
+ return void 0;
670
+ };
671
+ var fetchUserDocument = (http) => {
672
+ return http.request({ path: "/v1/users/me" });
673
+ };
1435
674
  var validateConnection = async (http, mode) => {
1436
675
  const fetched = await probeCollection(
1437
676
  async () => {
1438
- const userDoc2 = await getAuthenticatedUser(http);
1439
- const stores2 = await listStores(http);
677
+ const userDoc2 = await fetchUserDocument(http);
678
+ const stores2 = await http.paginate("/v1/stores");
1440
679
  return { userDoc: userDoc2, stores: stores2 };
1441
680
  },
1442
681
  {
@@ -1492,12 +731,15 @@ var validateDiscount = async (http, mode, options) => {
1492
731
  label: `discount ${options.discountId}`,
1493
732
  id: String(options.discountId)
1494
733
  };
1495
- const fetched = await probeFetch(() => getDiscount(http, options.discountId), {
1496
- notFoundCode: ISSUE_CODES.DISCOUNT_NOT_FOUND,
1497
- notFoundMessage: `Discount ${options.discountId} not found.`,
1498
- notFoundFix: "Verify the discount ID in the Lemon Squeezy dashboard.",
1499
- notFoundContext: { discountId: String(options.discountId) }
1500
- });
734
+ const fetched = await probeFetch(
735
+ () => http.getResource(`/v1/discounts/${options.discountId}`),
736
+ {
737
+ notFoundCode: ISSUE_CODES.DISCOUNT_NOT_FOUND,
738
+ notFoundMessage: `Discount ${options.discountId} not found.`,
739
+ notFoundFix: "Verify the discount ID in the Lemon Squeezy dashboard.",
740
+ notFoundContext: { discountId: String(options.discountId) }
741
+ }
742
+ );
1501
743
  if (!fetched.ok) {
1502
744
  return buildResult("discount", mode, [fetched.issue], void 0, target);
1503
745
  }
@@ -1604,12 +846,15 @@ var validateLicenseKey = async (http, mode, options) => {
1604
846
  label: `license key ${options.licenseKeyId}`,
1605
847
  id: String(options.licenseKeyId)
1606
848
  };
1607
- const fetched = await probeFetch(() => getLicenseKey(http, options.licenseKeyId), {
1608
- notFoundCode: ISSUE_CODES.LICENSE_KEY_NOT_FOUND,
1609
- notFoundMessage: `License key ${options.licenseKeyId} not found.`,
1610
- notFoundFix: "Verify the license key ID in the Lemon Squeezy dashboard.",
1611
- notFoundContext: { licenseKeyId: String(options.licenseKeyId) }
1612
- });
849
+ const fetched = await probeFetch(
850
+ () => http.getResource(`/v1/license-keys/${options.licenseKeyId}`),
851
+ {
852
+ notFoundCode: ISSUE_CODES.LICENSE_KEY_NOT_FOUND,
853
+ notFoundMessage: `License key ${options.licenseKeyId} not found.`,
854
+ notFoundFix: "Verify the license key ID in the Lemon Squeezy dashboard.",
855
+ notFoundContext: { licenseKeyId: String(options.licenseKeyId) }
856
+ }
857
+ );
1613
858
  if (!fetched.ok) {
1614
859
  return buildResult(
1615
860
  "licenseKey",
@@ -1691,19 +936,24 @@ var validateProduct = async (http, mode, options) => {
1691
936
  label: `product ${options.productId}`,
1692
937
  id: String(options.productId)
1693
938
  };
1694
- const fetched = await probeFetch(() => getProduct(http, options.productId), {
1695
- notFoundCode: ISSUE_CODES.PRODUCT_NOT_FOUND,
1696
- notFoundMessage: `Product ${options.productId} not found.`,
1697
- notFoundFix: "Verify the product ID in the Lemon Squeezy dashboard.",
1698
- notFoundContext: { productId: String(options.productId) }
1699
- });
939
+ const fetched = await probeFetch(
940
+ () => http.getResource(`/v1/products/${options.productId}`),
941
+ {
942
+ notFoundCode: ISSUE_CODES.PRODUCT_NOT_FOUND,
943
+ notFoundMessage: `Product ${options.productId} not found.`,
944
+ notFoundFix: "Verify the product ID in the Lemon Squeezy dashboard.",
945
+ notFoundContext: { productId: String(options.productId) }
946
+ }
947
+ );
1700
948
  if (!fetched.ok) {
1701
949
  return buildResult("product", mode, [fetched.issue], void 0, target);
1702
950
  }
1703
951
  const attrs = fetched.resource.attributes;
1704
952
  issues.push(...checkProduct(attrs, { expectedStoreId: options.expectedStoreId }));
1705
953
  const variantsProbed = await probeCollection(
1706
- () => listVariantsForProduct(http, options.productId)
954
+ () => http.paginate("/v1/variants", {
955
+ "filter[product_id]": String(options.productId)
956
+ })
1707
957
  );
1708
958
  if (!variantsProbed.ok) {
1709
959
  issues.push({ ...variantsProbed.issue, severity: "warning" });
@@ -1772,12 +1022,15 @@ var checkProduct = (attrs, options = {}) => {
1772
1022
  // src/validate/store.ts
1773
1023
  var validateStore = async (http, mode, storeId) => {
1774
1024
  const target = { label: `store ${storeId}`, id: String(storeId) };
1775
- const fetched = await probeFetch(() => getStore(http, storeId), {
1776
- notFoundCode: ISSUE_CODES.STORE_NOT_FOUND,
1777
- notFoundMessage: `Store ${storeId} not found with the current API key.`,
1778
- notFoundFix: "Check the store ID and confirm the API key belongs to the account that owns it.",
1779
- notFoundContext: { storeId: String(storeId) }
1780
- });
1025
+ const fetched = await probeFetch(
1026
+ () => http.getResource(`/v1/stores/${storeId}`),
1027
+ {
1028
+ notFoundCode: ISSUE_CODES.STORE_NOT_FOUND,
1029
+ notFoundMessage: `Store ${storeId} not found with the current API key.`,
1030
+ notFoundFix: "Check the store ID and confirm the API key belongs to the account that owns it.",
1031
+ notFoundContext: { storeId: String(storeId) }
1032
+ }
1033
+ );
1781
1034
  if (!fetched.ok) {
1782
1035
  return buildResult("store", mode, [fetched.issue], void 0, target);
1783
1036
  }
@@ -1795,7 +1048,7 @@ var validateSubscriptionPlan = async (http, mode, options) => {
1795
1048
  id: String(options.variantId)
1796
1049
  };
1797
1050
  const fetched = await probeFetch(
1798
- () => getVariant(http, options.variantId),
1051
+ () => http.getResource(`/v1/variants/${options.variantId}`),
1799
1052
  {
1800
1053
  notFoundCode: ISSUE_CODES.PLAN_VARIANT_NOT_FOUND,
1801
1054
  notFoundMessage: `Variant ${options.variantId} not found.`,
@@ -1815,10 +1068,13 @@ var validateSubscriptionPlan = async (http, mode, options) => {
1815
1068
  const variant = fetched.resource;
1816
1069
  const attrs = variant.attributes;
1817
1070
  const issues = checkSubscriptionPlan(attrs, { variantId: options.variantId });
1818
- const productProbed = await probeFetch(() => getProduct(http, attrs.product_id), {
1819
- notFoundCode: ISSUE_CODES.PRODUCT_NOT_FOUND,
1820
- notFoundMessage: `Product ${attrs.product_id} not found.`
1821
- });
1071
+ const productProbed = await probeFetch(
1072
+ () => http.getResource(`/v1/products/${attrs.product_id}`),
1073
+ {
1074
+ notFoundCode: ISSUE_CODES.PRODUCT_NOT_FOUND,
1075
+ notFoundMessage: `Product ${attrs.product_id} not found.`
1076
+ }
1077
+ );
1822
1078
  if (productProbed.ok) {
1823
1079
  const mismatch = checkStoreOwnership({
1824
1080
  expectedStoreId: options.storeId,
@@ -1929,14 +1185,6 @@ var checkSubscriptionPlan = (attrs, options) => {
1929
1185
  return issues;
1930
1186
  };
1931
1187
 
1932
- // src/core/equality.ts
1933
- var sameWebhookUrl = (a, b) => {
1934
- return normalizeWebhookUrl(a) === normalizeWebhookUrl(b);
1935
- };
1936
- var normalizeWebhookUrl = (raw) => {
1937
- return raw.replace(/\/+$/, "").toLowerCase();
1938
- };
1939
-
1940
1188
  // src/support/manifest.ts
1941
1189
  var RECOMMENDED_WEBHOOK_EVENTS = [
1942
1190
  "order_created",
@@ -1957,9 +1205,19 @@ var OPTIONAL_WEBHOOK_EVENTS = [
1957
1205
  ];
1958
1206
 
1959
1207
  // src/validate/webhook.ts
1208
+ var sameWebhookUrl = (a, b) => {
1209
+ return normalizeWebhookUrl(a) === normalizeWebhookUrl(b);
1210
+ };
1211
+ var normalizeWebhookUrl = (raw) => {
1212
+ return raw.replace(/\/+$/, "").toLowerCase();
1213
+ };
1960
1214
  var validateWebhook = async (http, mode, options) => {
1961
1215
  const target = { label: options.url, url: options.url };
1962
- const fetched = await probeCollection(() => listWebhooksForStore(http, options.storeId));
1216
+ const fetched = await probeCollection(
1217
+ () => http.paginate("/v1/webhooks", {
1218
+ "filter[store_id]": String(options.storeId)
1219
+ })
1220
+ );
1963
1221
  if (!fetched.ok) {
1964
1222
  return buildResult("webhook", mode, [fetched.issue], void 0, target);
1965
1223
  }
@@ -2214,80 +1472,196 @@ var createFreshSqueezy = (config = {}) => {
2214
1472
  };
2215
1473
  };
2216
1474
 
2217
- // src/cli/errors.ts
2218
- var renderCliError = (message, hints = []) => {
2219
- const lines = [`fresh-squeezy: ${message}`];
2220
- if (hints.length > 0) {
2221
- lines.push("", "Try:");
2222
- for (const hint of hints) {
2223
- lines.push(` ${hint}`);
2224
- }
1475
+ // src/cli/optionalValidators.ts
1476
+ var errorMessage = (err) => err instanceof Error ? err.message : String(err);
1477
+ var listChoices = async (load, toChoice) => {
1478
+ try {
1479
+ const items = await load();
1480
+ return { choices: items.map(toChoice) };
1481
+ } catch (err) {
1482
+ return { choices: [], error: errorMessage(err) };
2225
1483
  }
2226
- return `${lines.join("\n")}
2227
- `;
2228
1484
  };
2229
- var getDoctorHints = (err) => {
2230
- if (!isFreshSqueezyError(err)) return [];
2231
- if (err.code === "MISSING_API_KEY") {
2232
- return ["fresh-squeezy init", `export ${ENV_KEYS.apiKey}=ls_live_or_test_key`];
2233
- }
2234
- if (err.code === "INVALID_MODE") {
2235
- return ["fresh-squeezy doctor --mode test", "fresh-squeezy doctor --mode live"];
2236
- }
2237
- if (err.code === "NO_STORES") {
2238
- return ["Check that the API key belongs to an account with a Lemon Squeezy store."];
1485
+ var discoverSubscriptionPlans = async ({
1486
+ client,
1487
+ listProducts
1488
+ }) => {
1489
+ const choices = [];
1490
+ try {
1491
+ for (const product of await listProducts()) {
1492
+ const variants = await client.variants.list(
1493
+ product.id
1494
+ );
1495
+ for (const variant of variants) {
1496
+ if (!variant.attributes.is_subscription) continue;
1497
+ const { name, interval, interval_count } = variant.attributes;
1498
+ const cadence = interval ? `${interval_count ?? 1}/${interval}` : "no interval";
1499
+ choices.push({
1500
+ value: variant.id,
1501
+ label: `${product.attributes.name} / ${name} (${cadence}) - id ${variant.id}`
1502
+ });
1503
+ }
1504
+ }
1505
+ return { choices };
1506
+ } catch (err) {
1507
+ return { choices, error: errorMessage(err) };
2239
1508
  }
2240
- return [];
2241
1509
  };
2242
- var getValidateHints = (err, target) => {
2243
- if (!isFreshSqueezyError(err)) return [];
2244
- if (err.code === "MISSING_API_KEY") {
2245
- return ["fresh-squeezy init", `export ${ENV_KEYS.apiKey}=ls_live_or_test_key`];
2246
- }
2247
- if (err.code === "INVALID_MODE") {
2248
- return [
2249
- `fresh-squeezy validate ${target} --mode test`,
2250
- `fresh-squeezy validate ${target} --mode live`
2251
- ];
2252
- }
2253
- if (err.code === "MISSING_ARG") {
2254
- return getMissingArgHints(target);
1510
+ var isHttpUrl = (value) => {
1511
+ try {
1512
+ return ["http:", "https:"].includes(new URL(value).protocol);
1513
+ } catch {
1514
+ return false;
2255
1515
  }
2256
- return [];
2257
1516
  };
2258
- var getMissingArgHints = (target) => {
2259
- if (target === "connection") {
2260
- return ["fresh-squeezy validate connection"];
2261
- }
2262
- if (target === "store") {
2263
- return [
2264
- "fresh-squeezy validate store --store-ids 12",
2265
- "fresh-squeezy validate store --all-stores"
2266
- ];
2267
- }
2268
- if (target === "product") {
2269
- return ["fresh-squeezy validate product --product-id 987"];
2270
- }
2271
- if (target === "webhook") {
2272
- return [
2273
- "fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
2274
- "fresh-squeezy validate webhook --all-stores --webhook-url https://app.example.com/api/webhooks/lemon-squeezy"
2275
- ];
2276
- }
2277
- if (target === "discount") {
2278
- return ["fresh-squeezy validate discount --store-ids 12 --discount-id 123"];
1517
+ var validateWebhookUrls = (value) => {
1518
+ const urls = value.split(",").map((entry) => entry.trim()).filter(Boolean);
1519
+ return urls.every(isHttpUrl) ? true : "Enter valid webhook URLs.";
1520
+ };
1521
+ var OPTIONAL_VALIDATORS = [
1522
+ {
1523
+ name: "product",
1524
+ field: "productIds",
1525
+ resultName: "product",
1526
+ label: "Products",
1527
+ menuLabel: "Product checkout",
1528
+ noun: "products",
1529
+ manualNoun: "Product IDs",
1530
+ discover: ({ listProducts }) => listChoices(listProducts, (product) => ({
1531
+ value: product.id,
1532
+ label: `${product.attributes.name} (${product.attributes.status}) - id ${product.id}`
1533
+ }))
1534
+ },
1535
+ {
1536
+ name: "webhook",
1537
+ field: "webhookUrls",
1538
+ resultName: "webhook",
1539
+ label: "Webhooks",
1540
+ menuLabel: "Webhook registration",
1541
+ noun: "webhook URLs",
1542
+ manualNoun: "Webhook URLs",
1543
+ validate: validateWebhookUrls,
1544
+ discover: ({ client, storeId }) => listChoices(
1545
+ () => client.webhooks.list(storeId),
1546
+ (webhook) => ({
1547
+ value: webhook.attributes.url,
1548
+ label: `${webhook.attributes.url} - id ${webhook.id}`
1549
+ })
1550
+ )
1551
+ },
1552
+ {
1553
+ name: "discount",
1554
+ field: "discountIds",
1555
+ resultName: "discount",
1556
+ label: "Discounts",
1557
+ menuLabel: "Discount code",
1558
+ noun: "discounts",
1559
+ manualNoun: "Discount IDs",
1560
+ discover: ({ client, storeId }) => listChoices(
1561
+ () => client.discounts.list(storeId),
1562
+ (discount) => ({
1563
+ value: discount.id,
1564
+ label: `${discount.attributes.name} (${discount.attributes.code}) - id ${discount.id}`
1565
+ })
1566
+ )
1567
+ },
1568
+ {
1569
+ name: "license-key",
1570
+ field: "licenseKeyIds",
1571
+ resultName: "licenseKey",
1572
+ label: "License keys",
1573
+ menuLabel: "License key",
1574
+ noun: "license keys",
1575
+ manualNoun: "License key IDs",
1576
+ discover: ({ client, storeId }) => listChoices(
1577
+ () => client.licenseKeys.list(storeId),
1578
+ (key) => ({
1579
+ value: key.id,
1580
+ label: `${key.attributes.key_short} (${key.attributes.status}) - id ${key.id}`
1581
+ })
1582
+ )
1583
+ },
1584
+ {
1585
+ name: "subscription-plan",
1586
+ field: "variantIds",
1587
+ resultName: "subscriptionPlan",
1588
+ label: "Subscription plans",
1589
+ menuLabel: "Subscription plan",
1590
+ noun: "subscription plans",
1591
+ manualNoun: "Subscription plan variant IDs",
1592
+ discover: discoverSubscriptionPlans
2279
1593
  }
2280
- if (target === "license-key") {
2281
- return ["fresh-squeezy validate license-key --store-ids 12 --license-key-id 123"];
1594
+ ];
1595
+ var discoverChoices = async (client, storeId, names) => {
1596
+ let products;
1597
+ const context = {
1598
+ client,
1599
+ storeId,
1600
+ listProducts: () => {
1601
+ products ??= client.products.list(storeId);
1602
+ return products;
1603
+ }
1604
+ };
1605
+ const choices = {};
1606
+ for (const row of OPTIONAL_VALIDATORS) {
1607
+ if (names.includes(row.name)) choices[row.name] = await row.discover(context);
2282
1608
  }
2283
- return ["fresh-squeezy validate subscription-plan --store-ids 12 --variant-id 123"];
2284
- };
2285
- var isFreshSqueezyError = (err) => {
2286
- return err instanceof FreshSqueezyError;
1609
+ return choices;
2287
1610
  };
2288
1611
 
2289
1612
  // src/cli/render.ts
2290
1613
  import chalk from "chalk";
1614
+ var MISSING_API_KEY_HINTS = [
1615
+ "fresh-squeezy init",
1616
+ `export ${ENV_KEYS.apiKey}=ls_live_or_test_key`
1617
+ ];
1618
+ var brand = chalk.green.bold("fresh-squeezy");
1619
+ var divider = chalk.dim("\u2022");
1620
+ var renderBrandHeader = (title, subtitle) => {
1621
+ const lines = [`${brand} ${divider} ${chalk.bold(title)}`];
1622
+ if (subtitle) lines.push(chalk.dim(subtitle));
1623
+ lines.push("");
1624
+ return lines.join("\n");
1625
+ };
1626
+ var renderStep = (index, total, title, detail) => {
1627
+ const suffix = detail ? ` ${chalk.dim(detail)}` : "";
1628
+ const prefix = index > 1 ? "\n" : "";
1629
+ return `${prefix}${chalk.yellow("\u25CF")} ${chalk.dim(`${index}/${total}`)} ${chalk.bold(title)}${suffix}
1630
+ `;
1631
+ };
1632
+ var renderDetected = (label, value, source) => {
1633
+ const suffix = source ? ` ${chalk.dim(`from ${source}`)}` : "";
1634
+ return ` ${chalk.green("\u2713")} ${chalk.bold(label)} ${value}${suffix}
1635
+ `;
1636
+ };
1637
+ var renderCommandExamples = () => {
1638
+ return [
1639
+ renderBrandHeader("Command examples", "Direct commands stay stable for scripts and CI."),
1640
+ `${chalk.bold("Guided setup")}`,
1641
+ " fresh-squeezy init",
1642
+ "",
1643
+ `${chalk.bold("Full doctor")}`,
1644
+ " fresh-squeezy doctor --all-stores",
1645
+ " fresh-squeezy doctor --store-ids 12 --product-id 987 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
1646
+ "",
1647
+ `${chalk.bold("Single checks")}`,
1648
+ " fresh-squeezy validate connection",
1649
+ " fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
1650
+ "",
1651
+ `${chalk.bold("Bulk ops (delete / cancel / refund)")}`,
1652
+ " fresh-squeezy delete discount --all --store-ids 12 --dry-run",
1653
+ " fresh-squeezy delete discount --all --store-ids 12 --match TEST --yes",
1654
+ " fresh-squeezy delete webhook --ids 1,2,3 --yes",
1655
+ "",
1656
+ `${chalk.bold("Automation")}`,
1657
+ " fresh-squeezy doctor --all-stores --json",
1658
+ ""
1659
+ ].join("\n");
1660
+ };
1661
+ var renderCancelMessage = () => {
1662
+ return `${chalk.dim("fresh-squeezy cancelled.")}
1663
+ `;
1664
+ };
2291
1665
  var renderResult = (result) => {
2292
1666
  const lines = [];
2293
1667
  const badge = result.ok ? chalk.green("PASS") : chalk.red("FAIL");
@@ -2324,140 +1698,37 @@ var renderIssueLine = (issue2) => {
2324
1698
  const label = issue2.severity === "error" ? chalk.red("\u2717 error") : issue2.severity === "warning" ? chalk.yellow("! warn ") : chalk.blue("i info ");
2325
1699
  return `${label} ${chalk.gray(`[${issue2.code}]`)} ${issue2.message}`;
2326
1700
  };
2327
-
2328
- // src/cli/prompts.ts
2329
- import path from "path";
2330
- import { checkbox, confirm, input, password, select } from "@inquirer/prompts";
2331
-
2332
- // src/cli/resourceDiscovery.ts
2333
- var EMPTY_INIT_RESOURCE_CHOICES = {
2334
- products: { choices: [] },
2335
- webhooks: { choices: [] },
2336
- discounts: { choices: [] },
2337
- licenseKeys: { choices: [] },
2338
- subscriptionPlans: { choices: [] }
2339
- };
2340
- var discoverInitResourceChoices = async (client, storeId, targets) => {
2341
- const choices = {
2342
- products: { choices: [] },
2343
- webhooks: { choices: [] },
2344
- discounts: { choices: [] },
2345
- licenseKeys: { choices: [] },
2346
- subscriptionPlans: { choices: [] }
2347
- };
2348
- let products;
2349
- if (targets.includes("product") || targets.includes("subscription-plan")) {
2350
- const result = await discover(
2351
- () => paginate(client, "/v1/products", { "filter[store_id]": storeId })
2352
- );
2353
- if (result.ok) {
2354
- products = result.value;
2355
- choices.products = { choices: result.value.map(toProductChoice) };
2356
- } else {
2357
- choices.products = { choices: [], error: result.error };
2358
- }
2359
- }
2360
- if (targets.includes("webhook")) {
2361
- const result = await discover(
2362
- () => paginate(client, "/v1/webhooks", { "filter[store_id]": storeId })
2363
- );
2364
- choices.webhooks = result.ok ? { choices: result.value.map(toWebhookChoice) } : { choices: [], error: result.error };
2365
- }
2366
- if (targets.includes("discount")) {
2367
- const result = await discover(
2368
- () => paginate(client, "/v1/discounts", { "filter[store_id]": storeId })
2369
- );
2370
- choices.discounts = result.ok ? { choices: result.value.map(toDiscountChoice) } : { choices: [], error: result.error };
2371
- }
2372
- if (targets.includes("license-key")) {
2373
- const result = await discover(
2374
- () => paginate(client, "/v1/license-keys", { "filter[store_id]": storeId })
2375
- );
2376
- choices.licenseKeys = result.ok ? { choices: result.value.map(toLicenseKeyChoice) } : { choices: [], error: result.error };
2377
- }
2378
- if (targets.includes("subscription-plan")) {
2379
- const listedProducts = products ?? [];
2380
- if (choices.products.error) {
2381
- choices.subscriptionPlans = { choices: [], error: choices.products.error };
2382
- } else {
2383
- choices.subscriptionPlans = await discoverSubscriptionPlans(client, listedProducts);
1701
+ var renderCliError = (message, hints = []) => {
1702
+ const lines = [`fresh-squeezy: ${message}`];
1703
+ if (hints.length > 0) {
1704
+ lines.push("", "Try:");
1705
+ for (const hint of hints) {
1706
+ lines.push(` ${hint}`);
2384
1707
  }
2385
1708
  }
2386
- return choices;
1709
+ return `${lines.join("\n")}
1710
+ `;
2387
1711
  };
2388
- var discoverSubscriptionPlans = async (client, products) => {
2389
- const discovered = [];
2390
- for (const product of products) {
2391
- const result = await discover(
2392
- () => paginate(client, "/v1/variants", {
2393
- "filter[product_id]": product.id
2394
- })
2395
- );
2396
- if (!result.ok) {
2397
- return { choices: discovered, error: result.error };
2398
- }
2399
- for (const variant of result.value) {
2400
- if (variant.attributes.is_subscription) {
2401
- discovered.push(toSubscriptionPlanChoice(variant, product.attributes.name));
2402
- }
2403
- }
2404
- }
2405
- return { choices: discovered };
1712
+ var isFreshSqueezyError = (err) => {
1713
+ return err instanceof FreshSqueezyError;
2406
1714
  };
2407
- var paginate = async (client, path4, query) => {
2408
- const all = [];
2409
- let pageNumber = Number(query?.["page[number]"] ?? 1);
2410
- while (true) {
2411
- const doc = await client.request({
2412
- path: path4,
2413
- query: { ...query ?? {}, "page[number]": pageNumber }
2414
- });
2415
- all.push(...doc.data);
2416
- const lastPage = doc.meta?.page?.lastPage;
2417
- if (lastPage === void 0 || pageNumber >= lastPage) return all;
2418
- pageNumber += 1;
1715
+ var getDoctorHints = (err) => {
1716
+ if (!isFreshSqueezyError(err)) return [];
1717
+ if (err.code === "MISSING_API_KEY") {
1718
+ return MISSING_API_KEY_HINTS;
2419
1719
  }
2420
- };
2421
- var discover = async (load) => {
2422
- try {
2423
- return { ok: true, value: await load() };
2424
- } catch (err) {
2425
- return { ok: false, error: err instanceof Error ? err.message : String(err) };
1720
+ if (err.code === "INVALID_MODE") {
1721
+ return ["fresh-squeezy doctor --mode test", "fresh-squeezy doctor --mode live"];
2426
1722
  }
2427
- };
2428
- var toProductChoice = (product) => {
2429
- return {
2430
- value: product.id,
2431
- label: `${product.attributes.name} (${product.attributes.status}) - id ${product.id}`
2432
- };
2433
- };
2434
- var toWebhookChoice = (webhook) => {
2435
- return {
2436
- value: webhook.attributes.url,
2437
- label: `${webhook.attributes.url} - id ${webhook.id}`
2438
- };
2439
- };
2440
- var toDiscountChoice = (discount) => {
2441
- return {
2442
- value: discount.id,
2443
- label: `${discount.attributes.name} (${discount.attributes.code}) - id ${discount.id}`
2444
- };
2445
- };
2446
- var toLicenseKeyChoice = (licenseKey) => {
2447
- return {
2448
- value: licenseKey.id,
2449
- label: `${licenseKey.attributes.key_short} (${licenseKey.attributes.status}) - id ${licenseKey.id}`
2450
- };
2451
- };
2452
- var toSubscriptionPlanChoice = (variant, productName) => {
2453
- const interval = variant.attributes.interval ? `${variant.attributes.interval_count ?? 1}/${variant.attributes.interval}` : "no interval";
2454
- return {
2455
- value: variant.id,
2456
- label: `${productName} / ${variant.attributes.name} (${interval}) - id ${variant.id}`
2457
- };
1723
+ if (err.code === "NO_STORES") {
1724
+ return ["Check that the API key belongs to an account with a Lemon Squeezy store."];
1725
+ }
1726
+ return [];
2458
1727
  };
2459
1728
 
2460
1729
  // src/cli/prompts.ts
1730
+ import path from "path";
1731
+ import { checkbox, confirm, input, password, select } from "@inquirer/prompts";
2461
1732
  var PROMPT_THEME = {
2462
1733
  prefix: {
2463
1734
  idle: "\u203A",
@@ -2523,128 +1794,60 @@ var pickStores = async (choices) => {
2523
1794
  validate: (selected) => selected.length > 0 ? true : "Pick at least one store."
2524
1795
  });
2525
1796
  };
2526
- var selectDoctorTargets = async () => {
1797
+ var selectOptionalValidators = async () => {
2527
1798
  return checkbox({
2528
1799
  message: "Add resource checks to this doctor run?",
2529
1800
  theme: PROMPT_THEME,
2530
- choices: [
2531
- { name: "Product checkout", value: "product" },
2532
- { name: "Webhook registration", value: "webhook" },
2533
- { name: "Discount code", value: "discount" },
2534
- { name: "License key", value: "license-key" },
2535
- { name: "Subscription plan", value: "subscription-plan" }
2536
- ]
1801
+ choices: OPTIONAL_VALIDATORS.map((row) => ({ name: row.menuLabel, value: row.name }))
2537
1802
  });
2538
1803
  };
2539
1804
  var splitManualValues = (value) => (value ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
2540
- var required = (label) => {
2541
- return (value) => value.trim().length > 0 ? true : `${label} is required.`;
2542
- };
2543
- var optional = (label) => {
2544
- return (value) => {
2545
- const trimmed = value.trim();
2546
- if (trimmed.length === 0) return true;
2547
- return required(label)(trimmed);
2548
- };
2549
- };
2550
- var validateWebhookUrl = (value) => {
2551
- const trimmed = value.trim();
2552
- if (trimmed.length === 0) return "Webhook URL is required.";
2553
- try {
2554
- const url = new URL(trimmed);
2555
- return url.protocol === "http:" || url.protocol === "https:" ? true : "Webhook URL must start with http:// or https://.";
2556
- } catch {
2557
- return "Enter a valid webhook URL.";
2558
- }
2559
- };
2560
- var optionalWebhookUrls = (value) => {
2561
- const values2 = splitManualValues(value);
2562
- if (values2.length === 0) return true;
2563
- return values2.every((entry) => validateWebhookUrl(entry) === true) ? true : "Enter valid webhook URLs.";
2564
- };
2565
1805
  var cleanList = (value) => {
2566
1806
  const values2 = Array.isArray(value) ? value : splitManualValues(value);
2567
1807
  const cleaned = Array.from(new Set(values2.map((entry) => entry.trim()).filter(Boolean)));
2568
1808
  return cleaned.length > 0 ? cleaned : void 0;
2569
1809
  };
2570
- var DOCTOR_TARGET_SPECS = [
2571
- {
2572
- target: "product",
2573
- field: "productIds",
2574
- group: "products",
2575
- pickMessage: "Pick products to validate:",
2576
- emptyMessage: "No products selected. What now?",
2577
- manualMessage: "Product IDs to validate (comma-separated, leave empty to skip):",
2578
- validate: optional("Product ID")
2579
- },
2580
- {
2581
- target: "webhook",
2582
- field: "webhookUrls",
2583
- group: "webhooks",
2584
- pickMessage: "Pick webhook URLs to validate:",
2585
- emptyMessage: "No webhook URLs selected. What now?",
2586
- manualMessage: "Webhook URLs to validate (comma-separated, leave empty to skip):",
2587
- validate: optionalWebhookUrls
2588
- },
2589
- {
2590
- target: "discount",
2591
- field: "discountIds",
2592
- group: "discounts",
2593
- pickMessage: "Pick discounts to validate:",
2594
- emptyMessage: "No discounts selected. What now?",
2595
- manualMessage: "Discount IDs to validate (comma-separated, leave empty to skip):",
2596
- validate: optional("Discount ID")
2597
- },
2598
- {
2599
- target: "license-key",
2600
- field: "licenseKeyIds",
2601
- group: "licenseKeys",
2602
- pickMessage: "Pick license keys to validate:",
2603
- emptyMessage: "No license keys selected. What now?",
2604
- manualMessage: "License key IDs to validate (comma-separated, leave empty to skip):",
2605
- validate: optional("License key ID")
2606
- },
2607
- {
2608
- target: "subscription-plan",
2609
- field: "variantIds",
2610
- group: "subscriptionPlans",
2611
- pickMessage: "Pick subscription plans to validate:",
2612
- emptyMessage: "No subscription plans selected. What now?",
2613
- manualMessage: "Subscription plan variant IDs to validate (comma-separated, leave empty to skip):",
2614
- validate: optional("Variant ID")
2615
- }
2616
- ];
2617
- var askForDoctorTargetValues = async (targets, choices = EMPTY_INIT_RESOURCE_CHOICES) => {
2618
- if (targets.length === 0) return {};
1810
+ var pickDiscoveredIds = async (row, group) => {
1811
+ const discovered = group?.choices ?? [];
1812
+ if (discovered.length === 0) return "manual";
1813
+ const values2 = await checkbox({
1814
+ message: `Pick ${row.noun} to validate:`,
1815
+ theme: PROMPT_THEME,
1816
+ choices: discovered.map((choice) => ({ name: choice.label, value: choice.value }))
1817
+ });
1818
+ if (values2.length > 0) return values2;
1819
+ return askEmptyTargetAction(`No ${row.noun} selected. What now?`);
1820
+ };
1821
+ var askEmptyTargetAction = async (message) => {
1822
+ return select({
1823
+ message,
1824
+ theme: PROMPT_THEME,
1825
+ choices: [
1826
+ { name: "Enter manually", value: "manual" },
1827
+ { name: "Skip this check", value: "skip" }
1828
+ ]
1829
+ });
1830
+ };
1831
+ var askForValidatorIds = async (names, choices = {}) => {
1832
+ if (names.length === 0) return {};
2619
1833
  const answers = {};
2620
- const manualQuestions = [];
2621
- for (const spec of DOCTOR_TARGET_SPECS) {
2622
- if (!targets.includes(spec.target)) continue;
2623
- await resolveTargetValue({
2624
- answers,
2625
- manualQuestions,
2626
- group: choices[spec.group],
2627
- field: spec.field,
2628
- pickMessage: spec.pickMessage,
2629
- emptyMessage: spec.emptyMessage,
2630
- manualMessage: spec.manualMessage,
2631
- validate: spec.validate
2632
- });
2633
- }
2634
- for (const question of manualQuestions) {
2635
- answers[question.field] = await input({
2636
- message: question.message,
1834
+ const manualRows = [];
1835
+ for (const row of OPTIONAL_VALIDATORS) {
1836
+ if (!names.includes(row.name)) continue;
1837
+ const picked = await pickDiscoveredIds(row, choices[row.name]);
1838
+ if (picked === "manual") manualRows.push(row);
1839
+ else if (picked !== "skip") answers[row.field] = picked;
1840
+ }
1841
+ for (const row of manualRows) {
1842
+ answers[row.field] = await input({
1843
+ message: `${row.manualNoun} to validate (comma-separated, leave empty to skip):`,
2637
1844
  theme: PROMPT_THEME,
2638
- validate: question.validate
1845
+ validate: row.validate
2639
1846
  });
2640
1847
  }
2641
- return {
2642
- productIds: cleanList(answers.productIds),
2643
- webhookUrls: cleanList(answers.webhookUrls),
2644
- discountIds: cleanList(answers.discountIds),
2645
- licenseKeyIds: cleanList(answers.licenseKeyIds),
2646
- variantIds: cleanList(answers.variantIds)
2647
- };
1848
+ const targetValues = {};
1849
+ for (const row of OPTIONAL_VALIDATORS) targetValues[row.field] = cleanList(answers[row.field]);
1850
+ return targetValues;
2648
1851
  };
2649
1852
  var confirmLiveModeRun = async () => {
2650
1853
  return confirm({
@@ -2667,46 +1870,6 @@ var confirmResourceOp = async (message) => {
2667
1870
  theme: PROMPT_THEME
2668
1871
  });
2669
1872
  };
2670
- var resolveTargetValue = async (input2) => {
2671
- if (input2.group.choices.length === 0) {
2672
- input2.manualQuestions.push({
2673
- field: input2.field,
2674
- message: input2.manualMessage,
2675
- validate: input2.validate
2676
- });
2677
- return;
2678
- }
2679
- const values2 = await checkbox({
2680
- message: input2.pickMessage,
2681
- theme: PROMPT_THEME,
2682
- choices: input2.group.choices.map((choice) => ({
2683
- name: choice.label,
2684
- value: choice.value
2685
- }))
2686
- });
2687
- if (values2.length > 0) {
2688
- input2.answers[input2.field] = values2;
2689
- return;
2690
- }
2691
- const action = await askEmptyTargetAction(input2.emptyMessage);
2692
- if (action === "manual") {
2693
- input2.manualQuestions.push({
2694
- field: input2.field,
2695
- message: input2.manualMessage,
2696
- validate: input2.validate
2697
- });
2698
- }
2699
- };
2700
- var askEmptyTargetAction = async (message) => {
2701
- return select({
2702
- message,
2703
- theme: PROMPT_THEME,
2704
- choices: [
2705
- { name: "Enter manually", value: "manual" },
2706
- { name: "Skip this check", value: "skip" }
2707
- ]
2708
- });
2709
- };
2710
1873
  var formatPromptPath = (filePath) => {
2711
1874
  const relative = path.relative(process.cwd(), filePath);
2712
1875
  if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) return relative;
@@ -2714,7 +1877,7 @@ var formatPromptPath = (filePath) => {
2714
1877
  };
2715
1878
  var isPromptCancel = (error) => {
2716
1879
  if (!(error instanceof Error)) return false;
2717
- return error.name === "ExitPromptError" || error.message.includes("User force closed");
1880
+ return error.name === "ExitPromptError";
2718
1881
  };
2719
1882
  var pickManageOperation = async (ops) => {
2720
1883
  return select({
@@ -2741,6 +1904,14 @@ var pickBulkTargets = async (verb, items) => {
2741
1904
  };
2742
1905
 
2743
1906
  // src/cli/resolveStores.ts
1907
+ var listStoreChoices = async (client) => {
1908
+ const stores = await client.stores.list();
1909
+ return stores.map((store) => ({
1910
+ id: store.id,
1911
+ name: store.attributes.name,
1912
+ slug: store.attributes.slug
1913
+ }));
1914
+ };
2744
1915
  var resolveStores = async (client, input2) => {
2745
1916
  if (input2.storeIds && input2.storeIds.length > 0) {
2746
1917
  return { storeIds: dedupe(input2.storeIds), skipped: false };
@@ -2768,17 +1939,7 @@ var resolveStores = async (client, input2) => {
2768
1939
  if (ids.length === 1) {
2769
1940
  return { storeIds: ids, skipped: false };
2770
1941
  }
2771
- const detailed = await Promise.all(
2772
- ids.map(async (id) => {
2773
- const result = await client.validateStore(id);
2774
- return {
2775
- id,
2776
- name: result.resource?.name ?? "(unnamed)",
2777
- slug: result.resource?.slug ?? ""
2778
- };
2779
- })
2780
- );
2781
- const picked = await pickStores(detailed);
1942
+ const picked = await pickStores(await listStoreChoices(client));
2782
1943
  if (picked.length === 0) {
2783
1944
  throw new FreshSqueezyError({
2784
1945
  code: "NO_SELECTION",
@@ -2805,13 +1966,6 @@ var dedupe = (values2) => {
2805
1966
  };
2806
1967
 
2807
1968
  // src/cli/commands/doctor.ts
2808
- var ALL_RESOURCE_TARGETS = [
2809
- "product",
2810
- "webhook",
2811
- "discount",
2812
- "license-key",
2813
- "subscription-plan"
2814
- ];
2815
1969
  var runDoctorCommand = async (options) => {
2816
1970
  try {
2817
1971
  const client = createFreshSqueezy({ mode: options.mode });
@@ -2825,8 +1979,8 @@ var runDoctorCommand = async (options) => {
2825
1979
  }
2826
1980
  const reports = await Promise.all(
2827
1981
  resolved.storeIds.map(async (storeId) => {
2828
- const targets = await resolveDoctorTargets(client, storeId, options);
2829
- return client.doctor({ storeId, ...targets });
1982
+ const validatorIds = await resolveValidatorIds(client, storeId, options);
1983
+ return client.doctor({ storeId, ...validatorIds });
2830
1984
  })
2831
1985
  );
2832
1986
  const ok = reports.every((report) => report.ok);
@@ -2852,55 +2006,47 @@ var runDoctorCommand = async (options) => {
2852
2006
  return 2;
2853
2007
  }
2854
2008
  };
2855
- var resolveDoctorTargets = async (client, storeId, options) => {
2856
- const explicit = {
2857
- productIds: one(options.productId),
2858
- webhookUrls: one(options.webhookUrl),
2859
- discountIds: one(options.discountId),
2860
- licenseKeyIds: one(options.licenseKeyId),
2861
- variantIds: one(options.variantId)
2862
- };
2009
+ var one = (value) => value ? [value] : void 0;
2010
+ var explicitValidatorIds = (options) => ({
2011
+ productIds: one(options.productId),
2012
+ webhookUrls: one(options.webhookUrl),
2013
+ discountIds: one(options.discountId),
2014
+ licenseKeyIds: one(options.licenseKeyId),
2015
+ variantIds: one(options.variantId)
2016
+ });
2017
+ var values = (group) => group?.choices.length ? group.choices.map((choice) => choice.value) : void 0;
2018
+ var mergeValues = (explicit, discovered) => {
2019
+ const merged = Array.from(/* @__PURE__ */ new Set([...explicit ?? [], ...discovered ?? []]));
2020
+ return merged.length > 0 ? merged : void 0;
2021
+ };
2022
+ var resolveValidatorIds = async (client, storeId, options) => {
2023
+ const explicit = explicitValidatorIds(options);
2863
2024
  if (!options.allResources) return explicit;
2864
- const discovered = await discoverInitResourceChoices(client, storeId, ALL_RESOURCE_TARGETS);
2865
- reportDiscoveryErrors(discovered);
2866
- if (!options.json) {
2867
- process.stderr.write(
2868
- `fresh-squeezy: discovered store ${storeId} resources: ${formatDiscoveryCounts(discovered)}.
2025
+ const allNames = OPTIONAL_VALIDATORS.map((row) => row.name);
2026
+ const discovered = await discoverChoices(client, storeId, allNames);
2027
+ for (const row of OPTIONAL_VALIDATORS) {
2028
+ const error = discovered[row.name]?.error;
2029
+ if (error) {
2030
+ process.stderr.write(
2031
+ `fresh-squeezy: discovery skipped ${row.label.toLowerCase()}: ${error}
2869
2032
  `
2870
- );
2033
+ );
2034
+ }
2871
2035
  }
2872
- return {
2873
- productIds: mergeValues(explicit.productIds, values(discovered.products)),
2874
- webhookUrls: mergeValues(explicit.webhookUrls, values(discovered.webhooks)),
2875
- discountIds: mergeValues(explicit.discountIds, values(discovered.discounts)),
2876
- licenseKeyIds: mergeValues(explicit.licenseKeyIds, values(discovered.licenseKeys)),
2877
- variantIds: mergeValues(explicit.variantIds, values(discovered.subscriptionPlans))
2878
- };
2879
- };
2880
- var DISCOVERY_LABELS = [
2881
- ["products", "products"],
2882
- ["webhooks", "webhooks"],
2883
- ["discounts", "discounts"],
2884
- ["licenseKeys", "license keys"],
2885
- ["subscriptionPlans", "subscription plans"]
2886
- ];
2887
- var formatDiscoveryCounts = (choices) => DISCOVERY_LABELS.map(([key, label]) => `${label} ${choices[key].choices.length}`).join(", ");
2888
- var reportDiscoveryErrors = (choices) => {
2889
- for (const [key, label] of DISCOVERY_LABELS) {
2890
- const error = choices[key].error;
2891
- if (error) process.stderr.write(`fresh-squeezy: discovery skipped ${label}: ${error}
2036
+ if (!options.json) {
2037
+ const counts = OPTIONAL_VALIDATORS.map(
2038
+ (row) => `${row.label.toLowerCase()} ${discovered[row.name]?.choices.length ?? 0}`
2039
+ ).join(", ");
2040
+ process.stderr.write(`fresh-squeezy: discovered store ${storeId} resources: ${counts}.
2892
2041
  `);
2893
2042
  }
2043
+ const merged = {};
2044
+ for (const row of OPTIONAL_VALIDATORS) {
2045
+ merged[row.field] = mergeValues(explicit[row.field], values(discovered[row.name]));
2046
+ }
2047
+ return merged;
2894
2048
  };
2895
- var values = (group) => group.choices.length > 0 ? group.choices.map((choice) => choice.value) : void 0;
2896
- var one = (value) => value ? [value] : void 0;
2897
- var mergeValues = (explicit, discovered) => {
2898
- const merged = Array.from(/* @__PURE__ */ new Set([...explicit ?? [], ...discovered ?? []]));
2899
- return merged.length > 0 ? merged : void 0;
2900
- };
2901
- var hasExplicitResourceSelection = (options) => Boolean(
2902
- options.productId || options.webhookUrl || options.discountId || options.licenseKeyId || options.variantId
2903
- );
2049
+ var hasExplicitResourceSelection = (options) => Object.values(explicitValidatorIds(options)).some((ids) => ids !== void 0);
2904
2050
  var runConnectionOnly = async (client, options) => {
2905
2051
  const connection = await client.validateConnection();
2906
2052
  const report = {
@@ -2937,59 +2083,7 @@ var writeFatal = (err, asJson) => {
2937
2083
  // src/cli/commands/init.ts
2938
2084
  import fs from "fs/promises";
2939
2085
  import path2 from "path";
2940
- import chalk3 from "chalk";
2941
-
2942
- // src/cli/brand.ts
2943
2086
  import chalk2 from "chalk";
2944
- var brand = chalk2.green.bold("fresh-squeezy");
2945
- var divider = chalk2.dim("\u2022");
2946
- var renderBrandHeader = (title, subtitle) => {
2947
- const lines = [`${brand} ${divider} ${chalk2.bold(title)}`];
2948
- if (subtitle) lines.push(chalk2.dim(subtitle));
2949
- lines.push("");
2950
- return lines.join("\n");
2951
- };
2952
- var renderStep = (index, total, title, detail) => {
2953
- const suffix = detail ? ` ${chalk2.dim(detail)}` : "";
2954
- const prefix = index > 1 ? "\n" : "";
2955
- return `${prefix}${chalk2.yellow("\u25CF")} ${chalk2.dim(`${index}/${total}`)} ${chalk2.bold(title)}${suffix}
2956
- `;
2957
- };
2958
- var renderDetected = (label, value, source) => {
2959
- const suffix = source ? ` ${chalk2.dim(`from ${source}`)}` : "";
2960
- return ` ${chalk2.green("\u2713")} ${chalk2.bold(label)} ${value}${suffix}
2961
- `;
2962
- };
2963
- var renderCommandExamples = () => {
2964
- return [
2965
- renderBrandHeader("Command examples", "Direct commands stay stable for scripts and CI."),
2966
- `${chalk2.bold("Guided setup")}`,
2967
- " fresh-squeezy init",
2968
- "",
2969
- `${chalk2.bold("Full doctor")}`,
2970
- " fresh-squeezy doctor --all-stores",
2971
- " fresh-squeezy doctor --store-ids 12 --product-id 987 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
2972
- "",
2973
- `${chalk2.bold("Single checks")}`,
2974
- " fresh-squeezy validate connection",
2975
- " fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
2976
- "",
2977
- `${chalk2.bold("Bulk ops (delete / cancel / refund)")}`,
2978
- " fresh-squeezy delete discount --all --store-ids 12 --dry-run",
2979
- " fresh-squeezy delete discount --all --store-ids 12 --match TEST --yes",
2980
- " fresh-squeezy delete webhook --ids 1,2,3 --yes",
2981
- "",
2982
- `${chalk2.bold("Automation")}`,
2983
- " fresh-squeezy doctor --all-stores --json",
2984
- ""
2985
- ].join("\n");
2986
- };
2987
- var renderCancelMessage = () => {
2988
- return `${chalk2.dim("fresh-squeezy cancelled.")}
2989
- `;
2990
- };
2991
-
2992
- // src/cli/commands/init.ts
2993
2087
  var runInitCommand = async (options = {}) => {
2994
2088
  try {
2995
2089
  return await runInitFlow(options);
@@ -3020,7 +2114,8 @@ var runInitFlow = async (options) => {
3020
2114
  process.stdout.write(renderStep(1, 5, "Credentials", "reuse env when available"));
3021
2115
  const apiKey = await resolveApiKey();
3022
2116
  process.stdout.write(renderStep(2, 5, "Account probe", "detect mode and reachable stores"));
3023
- const { client, mode, connection } = await createDetectedClient(apiKey);
2117
+ const { client, connection } = await connectWithDetectedMode(apiKey);
2118
+ const mode = client.mode;
3024
2119
  if (!connection.ok) {
3025
2120
  process.stdout.write(`${renderReport({ ok: false, mode, results: [connection] })}
3026
2121
  `);
@@ -3030,81 +2125,60 @@ var runInitFlow = async (options) => {
3030
2125
  process.stderr.write(renderCancelMessage());
3031
2126
  return 130;
3032
2127
  }
3033
- const storeIds = connection.resource?.storeIds ?? [];
3034
- if (storeIds.length === 0) {
2128
+ const stores = await listStoreChoices(client);
2129
+ if (stores.length === 0) {
3035
2130
  process.stdout.write(
3036
- chalk3.yellow(
2131
+ chalk2.yellow(
3037
2132
  "No stores reachable with this key. Create a store in Lemon Squeezy and retry.\n"
3038
2133
  )
3039
2134
  );
3040
2135
  return 1;
3041
2136
  }
3042
- process.stdout.write(renderDetected("Stores", String(storeIds.length), "Lemon Squeezy API"));
3043
- const stores = await Promise.all(storeIds.map((id) => client.validateStore(id)));
3044
- const pickable = stores.filter((entry) => entry.ok && entry.resource).map((entry, index) => ({
3045
- id: storeIds[index] ?? "",
3046
- name: entry.resource?.name ?? "(unnamed)",
3047
- slug: entry.resource?.slug ?? ""
3048
- })).filter((entry) => entry.id !== "");
3049
- if (pickable.length === 0) {
3050
- process.stdout.write(
3051
- chalk3.yellow(
3052
- "Stores were discovered, but none could be validated. Check account access and retry.\n"
3053
- )
3054
- );
3055
- return 1;
3056
- }
2137
+ process.stdout.write(renderDetected("Stores", String(stores.length), "Lemon Squeezy API"));
3057
2138
  process.stdout.write(renderStep(3, 5, "Store selection", "auto-select when unambiguous"));
3058
- const storeId = await resolveStoreSelection(pickable);
2139
+ const storeId = await resolveStoreSelection(stores);
3059
2140
  process.stdout.write(renderStep(4, 5, "Optional checks", "pick resources before manual IDs"));
3060
- const selectedTargets = await selectDoctorTargets();
3061
- const resourceChoices = await discoverChoices(client, storeId, selectedTargets);
3062
- process.stdout.write(renderDiscoverySummary(resourceChoices, selectedTargets));
3063
- const doctorTargets = await askForDoctorTargetValues(selectedTargets, resourceChoices);
2141
+ const selectedValidators = await selectOptionalValidators();
2142
+ const resourceChoices = await discoverChoices(client, storeId, selectedValidators);
2143
+ process.stdout.write(renderDiscoverySummary(resourceChoices, selectedValidators));
2144
+ const validatorIds = await askForValidatorIds(selectedValidators, resourceChoices);
3064
2145
  const envPath = path2.resolve(process.cwd(), options.envFile ?? ".env");
3065
- const checkNames = getDoctorCheckNames(doctorTargets);
2146
+ const checkNames = listValidatorNames(validatorIds);
3066
2147
  process.stdout.write(renderSetupSummary({ envPath, mode, storeId, checkNames }));
3067
2148
  const shouldWrite = await confirmWriteEnvFile(envPath);
3068
2149
  if (shouldWrite) {
3069
2150
  await writeEnvFile(envPath, { apiKey, mode, storeId });
3070
- process.stdout.write(chalk3.green(`Wrote ${envPath}
2151
+ process.stdout.write(chalk2.green(`Wrote ${envPath}
3071
2152
  `));
3072
2153
  }
3073
- process.stdout.write(chalk3.dim(`
2154
+ process.stdout.write(chalk2.dim(`
3074
2155
  Running doctor (${checkNames.join(", ")})...
3075
2156
 
3076
2157
  `));
3077
- const report = await client.doctor({ storeId, ...doctorTargets });
2158
+ const report = await client.doctor({ storeId, ...validatorIds });
3078
2159
  process.stdout.write(`${renderReport(report)}
3079
2160
  `);
3080
2161
  return report.ok ? 0 : 1;
3081
2162
  };
3082
- var resolveStoreSelection = async (pickable) => {
3083
- if (pickable.length === 1) {
3084
- const store = pickable[0];
3085
- if (!store) throw new Error("Expected one reachable store.");
3086
- process.stdout.write(
3087
- renderDetected("Store", `${store.name} (${store.slug})`, `id ${store.id}`)
3088
- );
3089
- return store.id;
2163
+ var resolveStoreSelection = async (stores) => {
2164
+ const [only, ...others] = stores;
2165
+ if (only && others.length === 0) {
2166
+ process.stdout.write(renderDetected("Store", `${only.name} (${only.slug})`, `id ${only.id}`));
2167
+ return only.id;
3090
2168
  }
3091
- return pickStore(pickable);
3092
- };
3093
- var discoverChoices = async (client, storeId, selectedTargets) => {
3094
- if (selectedTargets.length === 0) return EMPTY_INIT_RESOURCE_CHOICES;
3095
- return discoverInitResourceChoices(client, storeId, selectedTargets);
2169
+ return pickStore(stores);
3096
2170
  };
3097
2171
  var resolveApiKey = async () => {
3098
2172
  const envApiKey = process.env[ENV_KEYS.apiKey]?.trim();
3099
2173
  if (envApiKey) {
3100
- process.stdout.write(chalk3.dim(`Using ${ENV_KEYS.apiKey} from environment.
2174
+ process.stdout.write(chalk2.dim(`Using ${ENV_KEYS.apiKey} from environment.
3101
2175
  `));
3102
2176
  return envApiKey;
3103
2177
  }
3104
2178
  const answers = await askForApiKey();
3105
2179
  return answers.apiKey;
3106
2180
  };
3107
- var createDetectedClient = async (apiKey) => {
2181
+ var connectWithDetectedMode = async (apiKey) => {
3108
2182
  const envMode = parseEnvMode();
3109
2183
  const initialMode = envMode ?? "test";
3110
2184
  let client = createFreshSqueezy({ apiKey, mode: initialMode });
@@ -3112,58 +2186,53 @@ var createDetectedClient = async (apiKey) => {
3112
2186
  const actualMode = connection.resource?.actualMode;
3113
2187
  if (actualMode && actualMode !== initialMode) {
3114
2188
  const message = envMode ? `Detected ${actualMode}-mode API key; ignoring ${ENV_KEYS.mode}=${initialMode} for this run.` : `Detected ${actualMode}-mode API key.`;
3115
- process.stdout.write(chalk3.yellow(`${message}
2189
+ process.stdout.write(chalk2.yellow(`${message}
3116
2190
  `));
3117
2191
  client = createFreshSqueezy({ apiKey, mode: actualMode });
3118
2192
  connection = await client.validateConnection();
3119
- return { client, mode: actualMode, connection };
2193
+ return { client, connection };
3120
2194
  }
3121
2195
  if (actualMode) {
3122
- process.stdout.write(chalk3.dim(`Detected ${actualMode}-mode API key.
2196
+ process.stdout.write(chalk2.dim(`Detected ${actualMode}-mode API key.
3123
2197
  `));
3124
- return { client, mode: actualMode, connection };
2198
+ return { client, connection };
3125
2199
  }
3126
2200
  if (envMode) {
3127
2201
  process.stdout.write(
3128
- chalk3.dim(`Using ${ENV_KEYS.mode}=${envMode}; API mode was not exposed.
2202
+ chalk2.dim(`Using ${ENV_KEYS.mode}=${envMode}; API mode was not exposed.
3129
2203
  `)
3130
2204
  );
3131
- return { client, mode: envMode, connection };
2205
+ return { client, connection };
3132
2206
  }
3133
- process.stdout.write(chalk3.dim("Could not auto-detect key mode; using test mode.\n"));
3134
- return { client, mode: initialMode, connection };
2207
+ process.stdout.write(chalk2.dim("Could not auto-detect key mode; using test mode.\n"));
2208
+ return { client, connection };
3135
2209
  };
3136
2210
  var parseEnvMode = () => {
3137
2211
  const value = process.env[ENV_KEYS.mode]?.trim();
3138
2212
  if (value === "test" || value === "live") return value;
3139
2213
  if (value) {
3140
2214
  process.stdout.write(
3141
- chalk3.yellow(`Ignoring invalid ${ENV_KEYS.mode}=${value}; expected test or live.
2215
+ chalk2.yellow(`Ignoring invalid ${ENV_KEYS.mode}=${value}; expected test or live.
3142
2216
  `)
3143
2217
  );
3144
2218
  }
3145
2219
  return void 0;
3146
2220
  };
3147
- var getDoctorCheckNames = (targets) => {
2221
+ var listValidatorNames = (validatorIds) => {
3148
2222
  const names = ["connection", "store"];
3149
- pushCheckName(names, "product", targets.productIds);
3150
- pushCheckName(names, "webhook", targets.webhookUrls);
3151
- pushCheckName(names, "discount", targets.discountIds);
3152
- pushCheckName(names, "licenseKey", targets.licenseKeyIds);
3153
- pushCheckName(names, "subscriptionPlan", targets.variantIds);
2223
+ for (const row of OPTIONAL_VALIDATORS) {
2224
+ const count = validatorIds[row.field]?.length ?? 0;
2225
+ if (count > 0) names.push(count === 1 ? row.resultName : `${row.resultName} x${count}`);
2226
+ }
3154
2227
  return names;
3155
2228
  };
3156
- var pushCheckName = (names, name, values2) => {
3157
- if (!values2 || values2.length === 0) return;
3158
- names.push(values2.length === 1 ? name : `${name} x${values2.length}`);
3159
- };
3160
2229
  var renderSetupSummary = (input2) => {
3161
2230
  return [
3162
2231
  renderStep(5, 5, "Ready to verify", "review before writing env"),
3163
- ` ${chalk3.dim("mode")} ${input2.mode}`,
3164
- ` ${chalk3.dim("store")} ${input2.storeId}`,
3165
- ` ${chalk3.dim("checks")} ${input2.checkNames.join(", ")}`,
3166
- ` ${chalk3.dim("env")} ${input2.envPath}`,
2232
+ ` ${chalk2.dim("mode")} ${input2.mode}`,
2233
+ ` ${chalk2.dim("store")} ${input2.storeId}`,
2234
+ ` ${chalk2.dim("checks")} ${input2.checkNames.join(", ")}`,
2235
+ ` ${chalk2.dim("env")} ${input2.envPath}`,
3167
2236
  ""
3168
2237
  ].join("\n");
3169
2238
  };
@@ -3205,32 +2274,25 @@ var readEnvFile = async (envPath) => {
3205
2274
  throw err;
3206
2275
  }
3207
2276
  };
3208
- var renderDiscoverySummary = (choices, selectedTargets) => {
3209
- if (selectedTargets.length === 0) return chalk3.dim(" No optional resource checks selected.\n");
3210
- const groups = [
3211
- ["product", "Products", choices.products],
3212
- ["webhook", "Webhooks", choices.webhooks],
3213
- ["discount", "Discounts", choices.discounts],
3214
- ["license-key", "License keys", choices.licenseKeys],
3215
- ["subscription-plan", "Subscription plans", choices.subscriptionPlans]
3216
- ];
3217
- const lines = [];
3218
- for (const [target, label, group] of groups) {
3219
- if (!selectedTargets.includes(target)) continue;
3220
- if (group.error) {
3221
- lines.push(chalk3.yellow(` ! ${label} discovery failed; manual entry is available.`));
3222
- } else {
3223
- lines.push(
3224
- renderDetected(label, String(group.choices.length), "Lemon Squeezy API").trimEnd()
3225
- );
2277
+ var renderDiscoverySummary = (choices, selectedValidators) => {
2278
+ if (selectedValidators.length === 0)
2279
+ return chalk2.dim(" No optional resource checks selected.\n");
2280
+ const lines = OPTIONAL_VALIDATORS.filter((row) => selectedValidators.includes(row.name)).map(
2281
+ (row) => {
2282
+ const group = choices[row.name];
2283
+ if (group?.error) {
2284
+ return chalk2.yellow(` ! ${row.label} discovery failed; manual entry is available.`);
2285
+ }
2286
+ const count = String(group?.choices.length ?? 0);
2287
+ return renderDetected(row.label, count, "Lemon Squeezy API").trimEnd();
3226
2288
  }
3227
- }
2289
+ );
3228
2290
  return `${lines.join("\n")}
3229
2291
  `;
3230
2292
  };
3231
2293
 
3232
2294
  // src/cli/commands/launcher.ts
3233
- import chalk4 from "chalk";
2295
+ import chalk3 from "chalk";
3234
2296
 
3235
2297
  // src/cli/projectInstall.ts
3236
2298
  import { spawn } from "child_process";
@@ -3518,7 +2580,7 @@ var runLauncherCommand = async (options) => {
3518
2580
  return 130;
3519
2581
  }
3520
2582
  const message = err instanceof Error ? err.message : String(err);
3521
- process.stderr.write(`${chalk4.red("fresh-squeezy bootstrap failed:")} ${message}
2583
+ process.stderr.write(`${chalk3.red("fresh-squeezy bootstrap failed:")} ${message}
3522
2584
 
3523
2585
  `);
3524
2586
  process.stderr.write(renderCommandExamples());
@@ -3561,9 +2623,9 @@ var renderProjectInstallResult = (result) => {
3561
2623
  return renderDetected("Project", "fresh-squeezy source repo", "package.json");
3562
2624
  }
3563
2625
  if (result.status === "skipped") {
3564
- return chalk4.dim(" Skipped dev dependency install (--no-install).\n");
2626
+ return chalk3.dim(" Skipped dev dependency install (--no-install).\n");
3565
2627
  }
3566
- return chalk4.yellow(" No package.json found; running without installing a dev dependency.\n");
2628
+ return chalk3.yellow(" No package.json found; running without installing a dev dependency.\n");
3567
2629
  };
3568
2630
 
3569
2631
  // src/cli/commands/resourceOps.ts
@@ -3600,14 +2662,13 @@ var runResourceOpCommand = async (options) => {
3600
2662
  message: "This op requires a JSON body (--body, --body-file, or stdin)."
3601
2663
  });
3602
2664
  }
3603
- const client = createFreshSqueezy({ mode: options.mode });
3604
2665
  const config = resolveConfig({ mode: options.mode });
3605
2666
  const http = new HttpClient(config);
3606
2667
  const storeId = options.storeIds?.[0] ?? config.storeId;
3607
2668
  if (options.all || options.ids) {
3608
- return await runBulk(http, spec, client.mode, options, { storeId, body });
2669
+ return await runBulk(http, spec, config.mode, options, { storeId, body });
3609
2670
  }
3610
- const allowed = await assertWriteSafety(spec, client.mode, options);
2671
+ const allowed = await assertWriteSafety(spec, config.mode, options);
3611
2672
  if (!allowed) return 2;
3612
2673
  const opBody = await invokeOp(http, options.resource, options.verb, {
3613
2674
  id: options.id,
@@ -3617,7 +2678,7 @@ var runResourceOpCommand = async (options) => {
3617
2678
  });
3618
2679
  const envelope = {
3619
2680
  ok: true,
3620
- mode: client.mode,
2681
+ mode: config.mode,
3621
2682
  resource: spec.resource,
3622
2683
  verb: spec.verb,
3623
2684
  docs: `https://docs.lemonsqueezy.com/api/${spec.docsPath}`,
@@ -3628,7 +2689,7 @@ var runResourceOpCommand = async (options) => {
3628
2689
  `);
3629
2690
  } else {
3630
2691
  process.stdout.write(
3631
- `${spec.verb} ${spec.resource} \u2014 ok (mode=${client.mode})
2692
+ `${spec.verb} ${spec.resource} \u2014 ok (mode=${config.mode})
3632
2693
  ${JSON.stringify(opBody ?? null, null, 2)}
3633
2694
  `
3634
2695
  );
@@ -3768,44 +2829,15 @@ var writeFatal2 = (err, json) => {
3768
2829
  };
3769
2830
 
3770
2831
  // src/cli/commands/validate.ts
3771
- var runValidateCommand = async (target, options) => {
3772
- try {
3773
- const client = createFreshSqueezy({ mode: options.mode });
3774
- if (target === "connection") {
3775
- return emit(await client.validateConnection(), options);
3776
- }
3777
- if (target === "product") {
3778
- return emit(await runProduct(client, options), options);
3779
- }
3780
- if (target === "discount") {
3781
- return emit(await runDiscount(client, options), options);
3782
- }
3783
- if (target === "license-key") {
3784
- return emit(await runLicenseKey(client, options), options);
3785
- }
3786
- if (target === "subscription-plan") {
3787
- return emit(await runSubscriptionPlan(client, options), options);
3788
- }
3789
- const storeIds = await resolveStoresForTarget(client, target, options);
3790
- const results = await runPerStore(client, target, storeIds, options);
3791
- if (options.json) {
3792
- process.stdout.write(`${JSON.stringify(results, null, 2)}
3793
- `);
3794
- } else {
3795
- for (const result of results) {
3796
- process.stdout.write(`${renderResult(result)}
3797
-
3798
- `);
3799
- }
3800
- }
3801
- return results.every((result) => result.ok) ? 0 : 1;
3802
- } catch (err) {
3803
- const message = err instanceof Error ? err.message : String(err);
3804
- process.stderr.write(renderCliError(message, getValidateHints(err, target)));
3805
- return 2;
2832
+ var EXAMPLE_WEBHOOK_URL = "https://app.example.com/api/webhooks/lemon-squeezy";
2833
+ var required = (value, message) => {
2834
+ if (value === void 0 || value === null || value === "") {
2835
+ throw new FreshSqueezyError({ code: "MISSING_ARG", message });
3806
2836
  }
2837
+ return value;
3807
2838
  };
3808
- var resolveStoresForTarget = async (client, target, options) => {
2839
+ var requiredStoreId = (options, target) => required(options.storeIds?.[0], `--store-ids is required for \`validate ${target}\`.`);
2840
+ var resolveStoresForSubcommand = async (client, target, options) => {
3809
2841
  const resolved = await resolveStores(client, {
3810
2842
  storeIds: options.storeIds,
3811
2843
  allStores: options.allStores,
@@ -3819,72 +2851,127 @@ var resolveStoresForTarget = async (client, target, options) => {
3819
2851
  }
3820
2852
  return resolved.storeIds;
3821
2853
  };
3822
- var runPerStore = async (client, target, storeIds, options) => {
3823
- if (target === "store") {
3824
- return Promise.all(storeIds.map((id) => client.validateStore(id)));
2854
+ var VALIDATE_SUBCOMMANDS = {
2855
+ connection: {
2856
+ description: "Check that the API key authenticates",
2857
+ stores: "none",
2858
+ examples: ["fresh-squeezy validate connection"],
2859
+ run: (client) => client.validateConnection()
2860
+ },
2861
+ store: {
2862
+ description: "Check one or more stores are reachable",
2863
+ stores: "multi",
2864
+ storeIdsHelp: "Comma-separated store IDs",
2865
+ examples: [
2866
+ "fresh-squeezy validate store --store-ids 12",
2867
+ "fresh-squeezy validate store --all-stores"
2868
+ ],
2869
+ run: async (client, options) => {
2870
+ const storeIds = await resolveStoresForSubcommand(client, "store", options);
2871
+ return Promise.all(storeIds.map((storeId) => client.validateStore(storeId)));
2872
+ }
2873
+ },
2874
+ product: {
2875
+ description: "Check a product is published with at least one variant",
2876
+ stores: "ownership",
2877
+ required: ["--product-id <id>", "Product ID to validate"],
2878
+ storeIdsHelp: "Expected owning store IDs (first is used for cross-check)",
2879
+ examples: ["fresh-squeezy validate product --product-id 987"],
2880
+ run: (client, options) => client.validateProduct({
2881
+ productId: required(options.productId, "--product-id is required for `validate product`."),
2882
+ expectedStoreId: options.storeIds?.[0]
2883
+ })
2884
+ },
2885
+ webhook: {
2886
+ description: "Check a webhook is registered with the recommended events",
2887
+ stores: "multi",
2888
+ required: ["--webhook-url <url>", "Public webhook URL"],
2889
+ storeIdsHelp: "Comma-separated store IDs",
2890
+ examples: [
2891
+ `fresh-squeezy validate webhook --store-ids 12 --webhook-url ${EXAMPLE_WEBHOOK_URL}`,
2892
+ `fresh-squeezy validate webhook --all-stores --webhook-url ${EXAMPLE_WEBHOOK_URL}`
2893
+ ],
2894
+ run: async (client, options) => {
2895
+ const storeIds = await resolveStoresForSubcommand(client, "webhook", options);
2896
+ const url = required(options.webhookUrl, "--webhook-url is required for `validate webhook`.");
2897
+ return Promise.all(storeIds.map((storeId) => client.validateWebhook({ storeId, url })));
2898
+ }
2899
+ },
2900
+ discount: {
2901
+ description: "Check a discount code is valid and redeemable",
2902
+ stores: "ownership",
2903
+ required: ["--discount-id <id>", "Discount ID to validate"],
2904
+ storeIdsHelp: "Store ID for ownership check (first ID used)",
2905
+ examples: ["fresh-squeezy validate discount --store-ids 12 --discount-id 123"],
2906
+ run: (client, options) => client.validateDiscount({
2907
+ discountId: required(
2908
+ options.discountId,
2909
+ "--discount-id is required for `validate discount`."
2910
+ ),
2911
+ storeId: requiredStoreId(options, "discount")
2912
+ })
2913
+ },
2914
+ "license-key": {
2915
+ description: "Check a license key is active and not at its activation limit",
2916
+ stores: "ownership",
2917
+ required: ["--license-key-id <id>", "License key ID to validate"],
2918
+ storeIdsHelp: "Store ID for ownership check (first ID used)",
2919
+ examples: ["fresh-squeezy validate license-key --store-ids 12 --license-key-id 123"],
2920
+ run: (client, options) => client.validateLicenseKey({
2921
+ licenseKeyId: required(
2922
+ options.licenseKeyId,
2923
+ "--license-key-id is required for `validate license-key`."
2924
+ ),
2925
+ storeId: requiredStoreId(options, "license-key")
2926
+ })
2927
+ },
2928
+ "subscription-plan": {
2929
+ description: "Check a subscription plan variant has valid billing interval and trial config",
2930
+ stores: "ownership",
2931
+ required: ["--variant-id <id>", "Variant ID of the subscription plan"],
2932
+ storeIdsHelp: "Store ID for ownership check (first ID used)",
2933
+ examples: ["fresh-squeezy validate subscription-plan --store-ids 12 --variant-id 123"],
2934
+ run: (client, options) => client.validateSubscriptionPlan({
2935
+ variantId: required(
2936
+ options.variantId,
2937
+ "--variant-id is required for `validate subscription-plan`."
2938
+ ),
2939
+ storeId: requiredStoreId(options, "subscription-plan")
2940
+ })
3825
2941
  }
3826
- if (target === "webhook") {
3827
- const url = required2(options.webhookUrl, "--webhook-url is required for `validate webhook`.");
3828
- return Promise.all(storeIds.map((storeId) => client.validateWebhook({ storeId, url })));
2942
+ };
2943
+ var errorHints = (err, target) => {
2944
+ if (!(err instanceof FreshSqueezyError)) return [];
2945
+ if (err.code === "MISSING_API_KEY") return MISSING_API_KEY_HINTS;
2946
+ if (err.code === "INVALID_MODE") {
2947
+ return [
2948
+ `fresh-squeezy validate ${target} --mode test`,
2949
+ `fresh-squeezy validate ${target} --mode live`
2950
+ ];
3829
2951
  }
2952
+ if (err.code === "MISSING_ARG") return VALIDATE_SUBCOMMANDS[target].examples;
3830
2953
  return [];
3831
2954
  };
3832
- var runProduct = async (client, options) => {
3833
- const productId = required2(options.productId, "--product-id is required for `validate product`.");
3834
- const expected = options.storeIds?.[0];
3835
- return client.validateProduct({
3836
- productId,
3837
- expectedStoreId: expected
3838
- });
3839
- };
3840
- var runDiscount = async (client, options) => {
3841
- const discountId = required2(
3842
- options.discountId,
3843
- "--discount-id is required for `validate discount`."
3844
- );
3845
- const storeId = required2(
3846
- options.storeIds?.[0],
3847
- "--store-ids is required for `validate discount`."
3848
- );
3849
- return client.validateDiscount({ storeId, discountId });
3850
- };
3851
- var runLicenseKey = async (client, options) => {
3852
- const licenseKeyId = required2(
3853
- options.licenseKeyId,
3854
- "--license-key-id is required for `validate license-key`."
3855
- );
3856
- const storeId = required2(
3857
- options.storeIds?.[0],
3858
- "--store-ids is required for `validate license-key`."
3859
- );
3860
- return client.validateLicenseKey({ storeId, licenseKeyId });
3861
- };
3862
- var runSubscriptionPlan = async (client, options) => {
3863
- const variantId = required2(
3864
- options.variantId,
3865
- "--variant-id is required for `validate subscription-plan`."
3866
- );
3867
- const storeId = required2(
3868
- options.storeIds?.[0],
3869
- "--store-ids is required for `validate subscription-plan`."
3870
- );
3871
- return client.validateSubscriptionPlan({ storeId, variantId });
3872
- };
3873
- var emit = (result, options) => {
2955
+ var writeResults = (results, options) => {
2956
+ const list = Array.isArray(results) ? results : [results];
3874
2957
  if (options.json) {
3875
- process.stdout.write(`${JSON.stringify(result, null, 2)}
2958
+ process.stdout.write(`${JSON.stringify(results, null, 2)}
3876
2959
  `);
3877
2960
  } else {
3878
- process.stdout.write(`${renderResult(result)}
3879
- `);
2961
+ const separator = Array.isArray(results) ? "\n\n" : "\n";
2962
+ for (const result of list) process.stdout.write(`${renderResult(result)}${separator}`);
3880
2963
  }
3881
- return result.ok ? 0 : 1;
2964
+ return list.every((result) => result.ok) ? 0 : 1;
3882
2965
  };
3883
- var required2 = (value, message) => {
3884
- if (value === void 0 || value === null || value === "") {
3885
- throw new FreshSqueezyError({ code: "MISSING_ARG", message });
2966
+ var runValidateCommand = async (target, options) => {
2967
+ try {
2968
+ const client = createFreshSqueezy({ mode: options.mode });
2969
+ return writeResults(await VALIDATE_SUBCOMMANDS[target].run(client, options), options);
2970
+ } catch (err) {
2971
+ const message = err instanceof Error ? err.message : String(err);
2972
+ process.stderr.write(renderCliError(message, errorHints(err, target)));
2973
+ return 2;
3886
2974
  }
3887
- return value;
3888
2975
  };
3889
2976
 
3890
2977
  // src/cli/main.ts
@@ -3902,7 +2989,10 @@ var OP_VERBS = [
3902
2989
  ];
3903
2990
  var parseMode = (value) => {
3904
2991
  if (value === "test" || value === "live") return value;
3905
- throw new Error(`Mode must be "test" or "live", got "${value}"`);
2992
+ throw new FreshSqueezyError({
2993
+ code: "INVALID_MODE",
2994
+ message: `Mode must be "test" or "live", got "${value}"`
2995
+ });
3906
2996
  };
3907
2997
  var parseCsv = (value) => value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3908
2998
  var readPackageVersion = () => {
@@ -3921,69 +3011,9 @@ var readPackageVersion = () => {
3921
3011
  };
3922
3012
  var isInteractive = Boolean(process.stdin.isTTY);
3923
3013
  var packageVersion = readPackageVersion();
3924
- var VALIDATE_COMMANDS = [
3925
- {
3926
- name: "connection",
3927
- description: "Check that the API key authenticates",
3928
- stores: "none"
3929
- },
3930
- {
3931
- name: "store",
3932
- description: "Check one or more stores are reachable",
3933
- stores: "multi",
3934
- storeIdsHelp: "Comma-separated store IDs"
3935
- },
3936
- {
3937
- name: "product",
3938
- description: "Check a product is published with at least one variant",
3939
- stores: "ownership",
3940
- required: ["--product-id <id>", "Product ID to validate"],
3941
- storeIdsHelp: "Expected owning store IDs (first is used for cross-check)"
3942
- },
3943
- {
3944
- name: "webhook",
3945
- description: "Check a webhook is registered with the recommended events",
3946
- stores: "multi",
3947
- required: ["--webhook-url <url>", "Public webhook URL"],
3948
- storeIdsHelp: "Comma-separated store IDs"
3949
- },
3950
- {
3951
- name: "discount",
3952
- description: "Check a discount code is valid and redeemable",
3953
- stores: "ownership",
3954
- required: ["--discount-id <id>", "Discount ID to validate"],
3955
- storeIdsHelp: "Store ID for ownership check (first ID used)"
3956
- },
3957
- {
3958
- name: "license-key",
3959
- description: "Check a license key is active and not at its activation limit",
3960
- stores: "ownership",
3961
- required: ["--license-key-id <id>", "License key ID to validate"],
3962
- storeIdsHelp: "Store ID for ownership check (first ID used)"
3963
- },
3964
- {
3965
- name: "subscription-plan",
3966
- description: "Check a subscription plan variant has valid billing interval and trial config",
3967
- stores: "ownership",
3968
- required: ["--variant-id <id>", "Variant ID of the subscription plan"],
3969
- storeIdsHelp: "Store ID for ownership check (first ID used)"
3970
- }
3971
- ];
3972
3014
  var exitWith = async (code) => {
3973
- process.exit(await code);
3974
- };
3975
- var toValidateOptions = (opts) => ({
3976
- mode: opts.mode,
3977
- storeIds: opts.storeIds,
3978
- allStores: Boolean(opts.allStores),
3979
- productId: opts.productId,
3980
- webhookUrl: opts.webhookUrl,
3981
- discountId: opts.discountId,
3982
- licenseKeyId: opts.licenseKeyId,
3983
- variantId: opts.variantId,
3984
- json: Boolean(opts.json),
3985
- isInteractive
3986
- });
3015
+ process.exitCode = await code;
3016
+ };
3987
3017
  var attachModeJson = (cmd) => cmd.option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON");
3988
3018
  var attachValidateFlags = (cmd, spec) => {
3989
3019
  if (spec.required) cmd.requiredOption(spec.required[0], spec.required[1]);
@@ -4035,7 +3065,8 @@ program.action(async (opts) => {
4035
3065
  "fresh-squeezy validate connection"
4036
3066
  ])
4037
3067
  );
4038
- process.exit(2);
3068
+ process.exitCode = 2;
3069
+ return;
4039
3070
  }
4040
3071
  await exitWith(
4041
3072
  runLauncherCommand({
@@ -4049,12 +3080,7 @@ var doctor2 = program.command("doctor").description("Run the full integration do
4049
3080
  "--all-resources",
4050
3081
  "Discover and validate every supported resource in each selected store"
4051
3082
  ).option("--product-id <id>", "Product to validate").option("--webhook-url <url>", "Webhook URL to validate").option("--discount-id <id>", "Discount to validate").option("--license-key-id <id>", "License key to validate").option("--variant-id <id>", "Subscription plan variant to validate").option("--json", "Emit machine-readable JSON").action(async (opts) => {
4052
- await exitWith(
4053
- runDoctorCommand({
4054
- ...toValidateOptions(opts),
4055
- allResources: Boolean(opts.allResources)
4056
- })
4057
- );
3083
+ await exitWith(runDoctorCommand({ ...opts, isInteractive }));
4058
3084
  });
4059
3085
  doctor2.addHelpText(
4060
3086
  "after",
@@ -4077,13 +3103,13 @@ Examples:
4077
3103
  fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
4078
3104
 
4079
3105
  Targets:
4080
- connection, store, product, webhook, discount, license-key, subscription-plan
3106
+ ${Object.keys(VALIDATE_SUBCOMMANDS).join(", ")}
4081
3107
  `
4082
3108
  );
4083
- for (const spec of VALIDATE_COMMANDS) {
4084
- attachValidateFlags(validate.command(spec.name).description(spec.description), spec).action(
3109
+ for (const [name, spec] of Object.entries(VALIDATE_SUBCOMMANDS)) {
3110
+ attachValidateFlags(validate.command(name).description(spec.description), spec).action(
4085
3111
  async (opts) => {
4086
- await exitWith(runValidateCommand(spec.name, toValidateOptions(opts)));
3112
+ await exitWith(runValidateCommand(name, { ...opts, isInteractive }));
4087
3113
  }
4088
3114
  );
4089
3115
  }
@@ -4164,6 +3190,6 @@ program.parseAsync(process.argv).catch((err) => {
4164
3190
  const message = err instanceof Error ? err.message : String(err);
4165
3191
  const hints = message.includes("Mode must be") ? ["fresh-squeezy doctor --mode test", "fresh-squeezy doctor --mode live"] : ["fresh-squeezy --help"];
4166
3192
  process.stderr.write(renderCliError(message, hints));
4167
- process.exit(2);
3193
+ process.exitCode = 2;
4168
3194
  });
4169
3195
  //# sourceMappingURL=cli.js.map