fresh-squeezy 0.1.20 → 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,859 +376,101 @@ 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"
863
- },
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"
392
+ destructive: true
943
393
  },
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
- if (normalized.endsWith("s") && normalized !== "status") {
996
- return normalized.slice(0, -1);
997
- }
998
- return normalized;
999
- };
1000
- var findResourceVerb = (resource, verb) => {
1001
- const key = normalizeResourceName(resource);
1002
- return resourceRegistry.find((entry) => entry.resource === key && entry.verb === verb);
1003
- };
1004
- var listRegisteredResources = () => {
1005
- return [...new Set(resourceRegistry.map((entry) => entry.resource))].sort();
1006
- };
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);
457
+ if (normalized.endsWith("s") && normalized !== "status") {
458
+ return normalized.slice(0, -1);
459
+ }
460
+ return normalized;
1126
461
  };
1127
- var deleteWebhook = async (http, webhookId) => {
1128
- await http.deleteResource(`/v1/webhooks/${webhookId}`);
462
+ var findResourceVerb = (resource, verb) => {
463
+ const key = normalizeResourceName(resource);
464
+ return resourceRegistry.find((entry) => entry.resource === key && entry.verb === verb);
1129
465
  };
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
- }
466
+ var listRegisteredResources = () => {
467
+ return [...new Set(resourceRegistry.map((entry) => entry.resource))].sort();
1266
468
  };
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);
2384
- }
2385
- }
2386
- return choices;
2387
- };
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 };
2406
- };
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;
2419
- }
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) };
2426
- }
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
- };
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}`);
1707
+ }
1708
+ }
1709
+ return `${lines.join("\n")}
1710
+ `;
2445
1711
  };
2446
- var toLicenseKeyChoice = (licenseKey) => {
2447
- return {
2448
- value: licenseKey.id,
2449
- label: `${licenseKey.attributes.key_short} (${licenseKey.attributes.status}) - id ${licenseKey.id}`
2450
- };
1712
+ var isFreshSqueezyError = (err) => {
1713
+ return err instanceof FreshSqueezyError;
2451
1714
  };
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
- };
1715
+ var getDoctorHints = (err) => {
1716
+ if (!isFreshSqueezyError(err)) return [];
1717
+ if (err.code === "MISSING_API_KEY") {
1718
+ return MISSING_API_KEY_HINTS;
1719
+ }
1720
+ if (err.code === "INVALID_MODE") {
1721
+ return ["fresh-squeezy doctor --mode test", "fresh-squeezy doctor --mode live"];
1722
+ }
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",
@@ -2477,6 +1748,10 @@ var pickLauncherAction = async () => {
2477
1748
  name: "Run doctor now \u2014 pick stores interactively when needed",
2478
1749
  value: "doctor"
2479
1750
  },
1751
+ {
1752
+ name: "Manage resources \u2014 bulk delete / cancel / refund",
1753
+ value: "manage"
1754
+ },
2480
1755
  {
2481
1756
  name: "Show command examples \u2014 copy/paste friendly",
2482
1757
  value: "examples"
@@ -2519,128 +1794,60 @@ var pickStores = async (choices) => {
2519
1794
  validate: (selected) => selected.length > 0 ? true : "Pick at least one store."
2520
1795
  });
2521
1796
  };
2522
- var selectDoctorTargets = async () => {
1797
+ var selectOptionalValidators = async () => {
2523
1798
  return checkbox({
2524
1799
  message: "Add resource checks to this doctor run?",
2525
1800
  theme: PROMPT_THEME,
2526
- choices: [
2527
- { name: "Product checkout", value: "product" },
2528
- { name: "Webhook registration", value: "webhook" },
2529
- { name: "Discount code", value: "discount" },
2530
- { name: "License key", value: "license-key" },
2531
- { name: "Subscription plan", value: "subscription-plan" }
2532
- ]
1801
+ choices: OPTIONAL_VALIDATORS.map((row) => ({ name: row.menuLabel, value: row.name }))
2533
1802
  });
2534
1803
  };
2535
1804
  var splitManualValues = (value) => (value ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
2536
- var required = (label) => {
2537
- return (value) => value.trim().length > 0 ? true : `${label} is required.`;
2538
- };
2539
- var optional = (label) => {
2540
- return (value) => {
2541
- const trimmed = value.trim();
2542
- if (trimmed.length === 0) return true;
2543
- return required(label)(trimmed);
2544
- };
2545
- };
2546
- var validateWebhookUrl = (value) => {
2547
- const trimmed = value.trim();
2548
- if (trimmed.length === 0) return "Webhook URL is required.";
2549
- try {
2550
- const url = new URL(trimmed);
2551
- return url.protocol === "http:" || url.protocol === "https:" ? true : "Webhook URL must start with http:// or https://.";
2552
- } catch {
2553
- return "Enter a valid webhook URL.";
2554
- }
2555
- };
2556
- var optionalWebhookUrls = (value) => {
2557
- const values2 = splitManualValues(value);
2558
- if (values2.length === 0) return true;
2559
- return values2.every((entry) => validateWebhookUrl(entry) === true) ? true : "Enter valid webhook URLs.";
2560
- };
2561
1805
  var cleanList = (value) => {
2562
1806
  const values2 = Array.isArray(value) ? value : splitManualValues(value);
2563
1807
  const cleaned = Array.from(new Set(values2.map((entry) => entry.trim()).filter(Boolean)));
2564
1808
  return cleaned.length > 0 ? cleaned : void 0;
2565
1809
  };
2566
- var DOCTOR_TARGET_SPECS = [
2567
- {
2568
- target: "product",
2569
- field: "productIds",
2570
- group: "products",
2571
- pickMessage: "Pick products to validate:",
2572
- emptyMessage: "No products selected. What now?",
2573
- manualMessage: "Product IDs to validate (comma-separated, leave empty to skip):",
2574
- validate: optional("Product ID")
2575
- },
2576
- {
2577
- target: "webhook",
2578
- field: "webhookUrls",
2579
- group: "webhooks",
2580
- pickMessage: "Pick webhook URLs to validate:",
2581
- emptyMessage: "No webhook URLs selected. What now?",
2582
- manualMessage: "Webhook URLs to validate (comma-separated, leave empty to skip):",
2583
- validate: optionalWebhookUrls
2584
- },
2585
- {
2586
- target: "discount",
2587
- field: "discountIds",
2588
- group: "discounts",
2589
- pickMessage: "Pick discounts to validate:",
2590
- emptyMessage: "No discounts selected. What now?",
2591
- manualMessage: "Discount IDs to validate (comma-separated, leave empty to skip):",
2592
- validate: optional("Discount ID")
2593
- },
2594
- {
2595
- target: "license-key",
2596
- field: "licenseKeyIds",
2597
- group: "licenseKeys",
2598
- pickMessage: "Pick license keys to validate:",
2599
- emptyMessage: "No license keys selected. What now?",
2600
- manualMessage: "License key IDs to validate (comma-separated, leave empty to skip):",
2601
- validate: optional("License key ID")
2602
- },
2603
- {
2604
- target: "subscription-plan",
2605
- field: "variantIds",
2606
- group: "subscriptionPlans",
2607
- pickMessage: "Pick subscription plans to validate:",
2608
- emptyMessage: "No subscription plans selected. What now?",
2609
- manualMessage: "Subscription plan variant IDs to validate (comma-separated, leave empty to skip):",
2610
- validate: optional("Variant ID")
2611
- }
2612
- ];
2613
- var askForDoctorTargetValues = async (targets, choices = EMPTY_INIT_RESOURCE_CHOICES) => {
2614
- 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 {};
2615
1833
  const answers = {};
2616
- const manualQuestions = [];
2617
- for (const spec of DOCTOR_TARGET_SPECS) {
2618
- if (!targets.includes(spec.target)) continue;
2619
- await resolveTargetValue({
2620
- answers,
2621
- manualQuestions,
2622
- group: choices[spec.group],
2623
- field: spec.field,
2624
- pickMessage: spec.pickMessage,
2625
- emptyMessage: spec.emptyMessage,
2626
- manualMessage: spec.manualMessage,
2627
- validate: spec.validate
2628
- });
2629
- }
2630
- for (const question of manualQuestions) {
2631
- answers[question.field] = await input({
2632
- 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):`,
2633
1844
  theme: PROMPT_THEME,
2634
- validate: question.validate
1845
+ validate: row.validate
2635
1846
  });
2636
1847
  }
2637
- return {
2638
- productIds: cleanList(answers.productIds),
2639
- webhookUrls: cleanList(answers.webhookUrls),
2640
- discountIds: cleanList(answers.discountIds),
2641
- licenseKeyIds: cleanList(answers.licenseKeyIds),
2642
- variantIds: cleanList(answers.variantIds)
2643
- };
1848
+ const targetValues = {};
1849
+ for (const row of OPTIONAL_VALIDATORS) targetValues[row.field] = cleanList(answers[row.field]);
1850
+ return targetValues;
2644
1851
  };
2645
1852
  var confirmLiveModeRun = async () => {
2646
1853
  return confirm({
@@ -2663,46 +1870,6 @@ var confirmResourceOp = async (message) => {
2663
1870
  theme: PROMPT_THEME
2664
1871
  });
2665
1872
  };
2666
- var resolveTargetValue = async (input2) => {
2667
- if (input2.group.choices.length === 0) {
2668
- input2.manualQuestions.push({
2669
- field: input2.field,
2670
- message: input2.manualMessage,
2671
- validate: input2.validate
2672
- });
2673
- return;
2674
- }
2675
- const values2 = await checkbox({
2676
- message: input2.pickMessage,
2677
- theme: PROMPT_THEME,
2678
- choices: input2.group.choices.map((choice) => ({
2679
- name: choice.label,
2680
- value: choice.value
2681
- }))
2682
- });
2683
- if (values2.length > 0) {
2684
- input2.answers[input2.field] = values2;
2685
- return;
2686
- }
2687
- const action = await askEmptyTargetAction(input2.emptyMessage);
2688
- if (action === "manual") {
2689
- input2.manualQuestions.push({
2690
- field: input2.field,
2691
- message: input2.manualMessage,
2692
- validate: input2.validate
2693
- });
2694
- }
2695
- };
2696
- var askEmptyTargetAction = async (message) => {
2697
- return select({
2698
- message,
2699
- theme: PROMPT_THEME,
2700
- choices: [
2701
- { name: "Enter manually", value: "manual" },
2702
- { name: "Skip this check", value: "skip" }
2703
- ]
2704
- });
2705
- };
2706
1873
  var formatPromptPath = (filePath) => {
2707
1874
  const relative = path.relative(process.cwd(), filePath);
2708
1875
  if (relative && !relative.startsWith("..") && !path.isAbsolute(relative)) return relative;
@@ -2710,10 +1877,41 @@ var formatPromptPath = (filePath) => {
2710
1877
  };
2711
1878
  var isPromptCancel = (error) => {
2712
1879
  if (!(error instanceof Error)) return false;
2713
- return error.name === "ExitPromptError" || error.message.includes("User force closed");
1880
+ return error.name === "ExitPromptError";
1881
+ };
1882
+ var pickManageOperation = async (ops) => {
1883
+ return select({
1884
+ message: "Which operation?",
1885
+ theme: PROMPT_THEME,
1886
+ choices: ops.map((op) => ({ name: `${op.verb} ${op.resource}`, value: op }))
1887
+ });
1888
+ };
1889
+ var askParentId = async (resource) => {
1890
+ const value = await input({
1891
+ message: `Parent id to list ${resource} items from:`,
1892
+ theme: PROMPT_THEME,
1893
+ validate: (entry) => entry.trim().length > 0 ? true : "A parent id is required."
1894
+ });
1895
+ return value.trim();
1896
+ };
1897
+ var pickBulkTargets = async (verb, items) => {
1898
+ return checkbox({
1899
+ message: `Pick items to ${verb} (space toggles, a selects all, enter confirms):`,
1900
+ theme: PROMPT_THEME,
1901
+ choices: items.map((item) => ({ name: `${item.label} \u2014 id ${item.id}`, value: item.id })),
1902
+ validate: (selected) => selected.length > 0 ? true : "Pick at least one item."
1903
+ });
2714
1904
  };
2715
1905
 
2716
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
+ };
2717
1915
  var resolveStores = async (client, input2) => {
2718
1916
  if (input2.storeIds && input2.storeIds.length > 0) {
2719
1917
  return { storeIds: dedupe(input2.storeIds), skipped: false };
@@ -2741,17 +1939,7 @@ var resolveStores = async (client, input2) => {
2741
1939
  if (ids.length === 1) {
2742
1940
  return { storeIds: ids, skipped: false };
2743
1941
  }
2744
- const detailed = await Promise.all(
2745
- ids.map(async (id) => {
2746
- const result = await client.validateStore(id);
2747
- return {
2748
- id,
2749
- name: result.resource?.name ?? "(unnamed)",
2750
- slug: result.resource?.slug ?? ""
2751
- };
2752
- })
2753
- );
2754
- const picked = await pickStores(detailed);
1942
+ const picked = await pickStores(await listStoreChoices(client));
2755
1943
  if (picked.length === 0) {
2756
1944
  throw new FreshSqueezyError({
2757
1945
  code: "NO_SELECTION",
@@ -2778,13 +1966,6 @@ var dedupe = (values2) => {
2778
1966
  };
2779
1967
 
2780
1968
  // src/cli/commands/doctor.ts
2781
- var ALL_RESOURCE_TARGETS = [
2782
- "product",
2783
- "webhook",
2784
- "discount",
2785
- "license-key",
2786
- "subscription-plan"
2787
- ];
2788
1969
  var runDoctorCommand = async (options) => {
2789
1970
  try {
2790
1971
  const client = createFreshSqueezy({ mode: options.mode });
@@ -2798,8 +1979,8 @@ var runDoctorCommand = async (options) => {
2798
1979
  }
2799
1980
  const reports = await Promise.all(
2800
1981
  resolved.storeIds.map(async (storeId) => {
2801
- const targets = await resolveDoctorTargets(client, storeId, options);
2802
- return client.doctor({ storeId, ...targets });
1982
+ const validatorIds = await resolveValidatorIds(client, storeId, options);
1983
+ return client.doctor({ storeId, ...validatorIds });
2803
1984
  })
2804
1985
  );
2805
1986
  const ok = reports.every((report) => report.ok);
@@ -2825,55 +2006,47 @@ var runDoctorCommand = async (options) => {
2825
2006
  return 2;
2826
2007
  }
2827
2008
  };
2828
- var resolveDoctorTargets = async (client, storeId, options) => {
2829
- const explicit = {
2830
- productIds: one(options.productId),
2831
- webhookUrls: one(options.webhookUrl),
2832
- discountIds: one(options.discountId),
2833
- licenseKeyIds: one(options.licenseKeyId),
2834
- variantIds: one(options.variantId)
2835
- };
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);
2836
2024
  if (!options.allResources) return explicit;
2837
- const discovered = await discoverInitResourceChoices(client, storeId, ALL_RESOURCE_TARGETS);
2838
- reportDiscoveryErrors(discovered);
2839
- if (!options.json) {
2840
- process.stderr.write(
2841
- `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}
2842
2032
  `
2843
- );
2033
+ );
2034
+ }
2844
2035
  }
2845
- return {
2846
- productIds: mergeValues(explicit.productIds, values(discovered.products)),
2847
- webhookUrls: mergeValues(explicit.webhookUrls, values(discovered.webhooks)),
2848
- discountIds: mergeValues(explicit.discountIds, values(discovered.discounts)),
2849
- licenseKeyIds: mergeValues(explicit.licenseKeyIds, values(discovered.licenseKeys)),
2850
- variantIds: mergeValues(explicit.variantIds, values(discovered.subscriptionPlans))
2851
- };
2852
- };
2853
- var DISCOVERY_LABELS = [
2854
- ["products", "products"],
2855
- ["webhooks", "webhooks"],
2856
- ["discounts", "discounts"],
2857
- ["licenseKeys", "license keys"],
2858
- ["subscriptionPlans", "subscription plans"]
2859
- ];
2860
- var formatDiscoveryCounts = (choices) => DISCOVERY_LABELS.map(([key, label]) => `${label} ${choices[key].choices.length}`).join(", ");
2861
- var reportDiscoveryErrors = (choices) => {
2862
- for (const [key, label] of DISCOVERY_LABELS) {
2863
- const error = choices[key].error;
2864
- 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}.
2865
2041
  `);
2866
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;
2867
2048
  };
2868
- var values = (group) => group.choices.length > 0 ? group.choices.map((choice) => choice.value) : void 0;
2869
- var one = (value) => value ? [value] : void 0;
2870
- var mergeValues = (explicit, discovered) => {
2871
- const merged = Array.from(/* @__PURE__ */ new Set([...explicit ?? [], ...discovered ?? []]));
2872
- return merged.length > 0 ? merged : void 0;
2873
- };
2874
- var hasExplicitResourceSelection = (options) => Boolean(
2875
- options.productId || options.webhookUrl || options.discountId || options.licenseKeyId || options.variantId
2876
- );
2049
+ var hasExplicitResourceSelection = (options) => Object.values(explicitValidatorIds(options)).some((ids) => ids !== void 0);
2877
2050
  var runConnectionOnly = async (client, options) => {
2878
2051
  const connection = await client.validateConnection();
2879
2052
  const report = {
@@ -2910,59 +2083,7 @@ var writeFatal = (err, asJson) => {
2910
2083
  // src/cli/commands/init.ts
2911
2084
  import fs from "fs/promises";
2912
2085
  import path2 from "path";
2913
- import chalk3 from "chalk";
2914
-
2915
- // src/cli/brand.ts
2916
2086
  import chalk2 from "chalk";
2917
- var brand = chalk2.green.bold("fresh-squeezy");
2918
- var divider = chalk2.dim("\u2022");
2919
- var renderBrandHeader = (title, subtitle) => {
2920
- const lines = [`${brand} ${divider} ${chalk2.bold(title)}`];
2921
- if (subtitle) lines.push(chalk2.dim(subtitle));
2922
- lines.push("");
2923
- return lines.join("\n");
2924
- };
2925
- var renderStep = (index, total, title, detail) => {
2926
- const suffix = detail ? ` ${chalk2.dim(detail)}` : "";
2927
- const prefix = index > 1 ? "\n" : "";
2928
- return `${prefix}${chalk2.yellow("\u25CF")} ${chalk2.dim(`${index}/${total}`)} ${chalk2.bold(title)}${suffix}
2929
- `;
2930
- };
2931
- var renderDetected = (label, value, source) => {
2932
- const suffix = source ? ` ${chalk2.dim(`from ${source}`)}` : "";
2933
- return ` ${chalk2.green("\u2713")} ${chalk2.bold(label)} ${value}${suffix}
2934
- `;
2935
- };
2936
- var renderCommandExamples = () => {
2937
- return [
2938
- renderBrandHeader("Command examples", "Direct commands stay stable for scripts and CI."),
2939
- `${chalk2.bold("Guided setup")}`,
2940
- " fresh-squeezy init",
2941
- "",
2942
- `${chalk2.bold("Full doctor")}`,
2943
- " fresh-squeezy doctor --all-stores",
2944
- " fresh-squeezy doctor --store-ids 12 --product-id 987 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
2945
- "",
2946
- `${chalk2.bold("Single checks")}`,
2947
- " fresh-squeezy validate connection",
2948
- " fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy",
2949
- "",
2950
- `${chalk2.bold("Bulk ops (delete / cancel / refund)")}`,
2951
- " fresh-squeezy delete discount --all --store-ids 12 --dry-run",
2952
- " fresh-squeezy delete discount --all --store-ids 12 --match TEST --yes",
2953
- " fresh-squeezy delete webhook --ids 1,2,3 --yes",
2954
- "",
2955
- `${chalk2.bold("Automation")}`,
2956
- " fresh-squeezy doctor --all-stores --json",
2957
- ""
2958
- ].join("\n");
2959
- };
2960
- var renderCancelMessage = () => {
2961
- return `${chalk2.dim("fresh-squeezy cancelled.")}
2962
- `;
2963
- };
2964
-
2965
- // src/cli/commands/init.ts
2966
2087
  var runInitCommand = async (options = {}) => {
2967
2088
  try {
2968
2089
  return await runInitFlow(options);
@@ -2993,7 +2114,8 @@ var runInitFlow = async (options) => {
2993
2114
  process.stdout.write(renderStep(1, 5, "Credentials", "reuse env when available"));
2994
2115
  const apiKey = await resolveApiKey();
2995
2116
  process.stdout.write(renderStep(2, 5, "Account probe", "detect mode and reachable stores"));
2996
- const { client, mode, connection } = await createDetectedClient(apiKey);
2117
+ const { client, connection } = await connectWithDetectedMode(apiKey);
2118
+ const mode = client.mode;
2997
2119
  if (!connection.ok) {
2998
2120
  process.stdout.write(`${renderReport({ ok: false, mode, results: [connection] })}
2999
2121
  `);
@@ -3003,81 +2125,60 @@ var runInitFlow = async (options) => {
3003
2125
  process.stderr.write(renderCancelMessage());
3004
2126
  return 130;
3005
2127
  }
3006
- const storeIds = connection.resource?.storeIds ?? [];
3007
- if (storeIds.length === 0) {
2128
+ const stores = await listStoreChoices(client);
2129
+ if (stores.length === 0) {
3008
2130
  process.stdout.write(
3009
- chalk3.yellow(
2131
+ chalk2.yellow(
3010
2132
  "No stores reachable with this key. Create a store in Lemon Squeezy and retry.\n"
3011
2133
  )
3012
2134
  );
3013
2135
  return 1;
3014
2136
  }
3015
- process.stdout.write(renderDetected("Stores", String(storeIds.length), "Lemon Squeezy API"));
3016
- const stores = await Promise.all(storeIds.map((id) => client.validateStore(id)));
3017
- const pickable = stores.filter((entry) => entry.ok && entry.resource).map((entry, index) => ({
3018
- id: storeIds[index] ?? "",
3019
- name: entry.resource?.name ?? "(unnamed)",
3020
- slug: entry.resource?.slug ?? ""
3021
- })).filter((entry) => entry.id !== "");
3022
- if (pickable.length === 0) {
3023
- process.stdout.write(
3024
- chalk3.yellow(
3025
- "Stores were discovered, but none could be validated. Check account access and retry.\n"
3026
- )
3027
- );
3028
- return 1;
3029
- }
2137
+ process.stdout.write(renderDetected("Stores", String(stores.length), "Lemon Squeezy API"));
3030
2138
  process.stdout.write(renderStep(3, 5, "Store selection", "auto-select when unambiguous"));
3031
- const storeId = await resolveStoreSelection(pickable);
2139
+ const storeId = await resolveStoreSelection(stores);
3032
2140
  process.stdout.write(renderStep(4, 5, "Optional checks", "pick resources before manual IDs"));
3033
- const selectedTargets = await selectDoctorTargets();
3034
- const resourceChoices = await discoverChoices(client, storeId, selectedTargets);
3035
- process.stdout.write(renderDiscoverySummary(resourceChoices, selectedTargets));
3036
- 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);
3037
2145
  const envPath = path2.resolve(process.cwd(), options.envFile ?? ".env");
3038
- const checkNames = getDoctorCheckNames(doctorTargets);
2146
+ const checkNames = listValidatorNames(validatorIds);
3039
2147
  process.stdout.write(renderSetupSummary({ envPath, mode, storeId, checkNames }));
3040
2148
  const shouldWrite = await confirmWriteEnvFile(envPath);
3041
2149
  if (shouldWrite) {
3042
2150
  await writeEnvFile(envPath, { apiKey, mode, storeId });
3043
- process.stdout.write(chalk3.green(`Wrote ${envPath}
2151
+ process.stdout.write(chalk2.green(`Wrote ${envPath}
3044
2152
  `));
3045
2153
  }
3046
- process.stdout.write(chalk3.dim(`
2154
+ process.stdout.write(chalk2.dim(`
3047
2155
  Running doctor (${checkNames.join(", ")})...
3048
2156
 
3049
2157
  `));
3050
- const report = await client.doctor({ storeId, ...doctorTargets });
2158
+ const report = await client.doctor({ storeId, ...validatorIds });
3051
2159
  process.stdout.write(`${renderReport(report)}
3052
2160
  `);
3053
2161
  return report.ok ? 0 : 1;
3054
2162
  };
3055
- var resolveStoreSelection = async (pickable) => {
3056
- if (pickable.length === 1) {
3057
- const store = pickable[0];
3058
- if (!store) throw new Error("Expected one reachable store.");
3059
- process.stdout.write(
3060
- renderDetected("Store", `${store.name} (${store.slug})`, `id ${store.id}`)
3061
- );
3062
- 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;
3063
2168
  }
3064
- return pickStore(pickable);
3065
- };
3066
- var discoverChoices = async (client, storeId, selectedTargets) => {
3067
- if (selectedTargets.length === 0) return EMPTY_INIT_RESOURCE_CHOICES;
3068
- return discoverInitResourceChoices(client, storeId, selectedTargets);
2169
+ return pickStore(stores);
3069
2170
  };
3070
2171
  var resolveApiKey = async () => {
3071
2172
  const envApiKey = process.env[ENV_KEYS.apiKey]?.trim();
3072
2173
  if (envApiKey) {
3073
- process.stdout.write(chalk3.dim(`Using ${ENV_KEYS.apiKey} from environment.
2174
+ process.stdout.write(chalk2.dim(`Using ${ENV_KEYS.apiKey} from environment.
3074
2175
  `));
3075
2176
  return envApiKey;
3076
2177
  }
3077
2178
  const answers = await askForApiKey();
3078
2179
  return answers.apiKey;
3079
2180
  };
3080
- var createDetectedClient = async (apiKey) => {
2181
+ var connectWithDetectedMode = async (apiKey) => {
3081
2182
  const envMode = parseEnvMode();
3082
2183
  const initialMode = envMode ?? "test";
3083
2184
  let client = createFreshSqueezy({ apiKey, mode: initialMode });
@@ -3085,58 +2186,53 @@ var createDetectedClient = async (apiKey) => {
3085
2186
  const actualMode = connection.resource?.actualMode;
3086
2187
  if (actualMode && actualMode !== initialMode) {
3087
2188
  const message = envMode ? `Detected ${actualMode}-mode API key; ignoring ${ENV_KEYS.mode}=${initialMode} for this run.` : `Detected ${actualMode}-mode API key.`;
3088
- process.stdout.write(chalk3.yellow(`${message}
2189
+ process.stdout.write(chalk2.yellow(`${message}
3089
2190
  `));
3090
2191
  client = createFreshSqueezy({ apiKey, mode: actualMode });
3091
2192
  connection = await client.validateConnection();
3092
- return { client, mode: actualMode, connection };
2193
+ return { client, connection };
3093
2194
  }
3094
2195
  if (actualMode) {
3095
- process.stdout.write(chalk3.dim(`Detected ${actualMode}-mode API key.
2196
+ process.stdout.write(chalk2.dim(`Detected ${actualMode}-mode API key.
3096
2197
  `));
3097
- return { client, mode: actualMode, connection };
2198
+ return { client, connection };
3098
2199
  }
3099
2200
  if (envMode) {
3100
2201
  process.stdout.write(
3101
- 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.
3102
2203
  `)
3103
2204
  );
3104
- return { client, mode: envMode, connection };
2205
+ return { client, connection };
3105
2206
  }
3106
- process.stdout.write(chalk3.dim("Could not auto-detect key mode; using test mode.\n"));
3107
- 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 };
3108
2209
  };
3109
2210
  var parseEnvMode = () => {
3110
2211
  const value = process.env[ENV_KEYS.mode]?.trim();
3111
2212
  if (value === "test" || value === "live") return value;
3112
2213
  if (value) {
3113
2214
  process.stdout.write(
3114
- 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.
3115
2216
  `)
3116
2217
  );
3117
2218
  }
3118
2219
  return void 0;
3119
2220
  };
3120
- var getDoctorCheckNames = (targets) => {
2221
+ var listValidatorNames = (validatorIds) => {
3121
2222
  const names = ["connection", "store"];
3122
- pushCheckName(names, "product", targets.productIds);
3123
- pushCheckName(names, "webhook", targets.webhookUrls);
3124
- pushCheckName(names, "discount", targets.discountIds);
3125
- pushCheckName(names, "licenseKey", targets.licenseKeyIds);
3126
- 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
+ }
3127
2227
  return names;
3128
2228
  };
3129
- var pushCheckName = (names, name, values2) => {
3130
- if (!values2 || values2.length === 0) return;
3131
- names.push(values2.length === 1 ? name : `${name} x${values2.length}`);
3132
- };
3133
2229
  var renderSetupSummary = (input2) => {
3134
2230
  return [
3135
2231
  renderStep(5, 5, "Ready to verify", "review before writing env"),
3136
- ` ${chalk3.dim("mode")} ${input2.mode}`,
3137
- ` ${chalk3.dim("store")} ${input2.storeId}`,
3138
- ` ${chalk3.dim("checks")} ${input2.checkNames.join(", ")}`,
3139
- ` ${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}`,
3140
2236
  ""
3141
2237
  ].join("\n");
3142
2238
  };
@@ -3178,32 +2274,25 @@ var readEnvFile = async (envPath) => {
3178
2274
  throw err;
3179
2275
  }
3180
2276
  };
3181
- var renderDiscoverySummary = (choices, selectedTargets) => {
3182
- if (selectedTargets.length === 0) return chalk3.dim(" No optional resource checks selected.\n");
3183
- const groups = [
3184
- ["product", "Products", choices.products],
3185
- ["webhook", "Webhooks", choices.webhooks],
3186
- ["discount", "Discounts", choices.discounts],
3187
- ["license-key", "License keys", choices.licenseKeys],
3188
- ["subscription-plan", "Subscription plans", choices.subscriptionPlans]
3189
- ];
3190
- const lines = [];
3191
- for (const [target, label, group] of groups) {
3192
- if (!selectedTargets.includes(target)) continue;
3193
- if (group.error) {
3194
- lines.push(chalk3.yellow(` ! ${label} discovery failed; manual entry is available.`));
3195
- } else {
3196
- lines.push(
3197
- renderDetected(label, String(group.choices.length), "Lemon Squeezy API").trimEnd()
3198
- );
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();
3199
2288
  }
3200
- }
2289
+ );
3201
2290
  return `${lines.join("\n")}
3202
2291
  `;
3203
2292
  };
3204
2293
 
3205
2294
  // src/cli/commands/launcher.ts
3206
- import chalk4 from "chalk";
2295
+ import chalk3 from "chalk";
3207
2296
 
3208
2297
  // src/cli/projectInstall.ts
3209
2298
  import { spawn } from "child_process";
@@ -3276,100 +2365,25 @@ var buildInstallCommand = (packageManager, packageSpec) => {
3276
2365
  return ["npm", "install", "--save-dev", packageSpec];
3277
2366
  };
3278
2367
  var runPackageCommand = (command, args, options) => {
3279
- return new Promise((resolve2, reject) => {
3280
- const child = spawn(command, args, {
3281
- cwd: options.cwd,
3282
- stdio: "inherit",
3283
- shell: false
3284
- });
3285
- child.on("error", reject);
3286
- child.on("close", (code) => resolve2(code ?? 1));
3287
- });
3288
- };
3289
- var pathExists = async (filePath) => {
3290
- try {
3291
- await fs2.access(filePath);
3292
- return true;
3293
- } catch {
3294
- return false;
3295
- }
3296
- };
3297
-
3298
- // src/cli/commands/launcher.ts
3299
- var runLauncherCommand = async (options) => {
3300
- if (!options.isInteractive) return 2;
3301
- try {
3302
- process.stdout.write(
3303
- renderBrandHeader(
3304
- "Project bootstrap",
3305
- "Install the billing doctor locally, then verify Lemon Squeezy in one guided run."
3306
- )
3307
- );
3308
- process.stdout.write(renderStep(1, 2, "Project dependency", "add fresh-squeezy when missing"));
3309
- const installResult = await ensureFreshSqueezyDevDependency({
3310
- skipInstall: options.install === false,
3311
- packageVersion: options.packageVersion
3312
- });
3313
- process.stdout.write(renderProjectInstallResult(installResult));
3314
- if (!process.env[ENV_KEYS.apiKey]?.trim()) {
3315
- return startGuidedSetup();
3316
- }
3317
- return routeLauncherAction(await pickLauncherAction());
3318
- } catch (err) {
3319
- if (isPromptCancel(err)) {
3320
- process.stderr.write(renderCancelMessage());
3321
- return 130;
3322
- }
3323
- const message = err instanceof Error ? err.message : String(err);
3324
- process.stderr.write(`${chalk4.red("fresh-squeezy bootstrap failed:")} ${message}
3325
-
3326
- `);
3327
- process.stderr.write(renderCommandExamples());
3328
- return 1;
3329
- }
3330
- };
3331
- var routeLauncherAction = async (action) => {
3332
- switch (action) {
3333
- case "init":
3334
- return startGuidedSetup();
3335
- case "doctor":
3336
- return runDoctorCommand({ isInteractive: true });
3337
- case "examples":
3338
- process.stdout.write(renderCommandExamples());
3339
- return 0;
3340
- case "exit":
3341
- return 0;
3342
- }
3343
- };
3344
- var startGuidedSetup = () => {
3345
- process.stdout.write(
3346
- renderStep(2, 2, "Guided setup", "detect credentials, stores, and resources")
3347
- );
3348
- return runInitCommand({ isInteractive: true });
2368
+ return new Promise((resolve2, reject) => {
2369
+ const child = spawn(command, args, {
2370
+ cwd: options.cwd,
2371
+ stdio: "inherit",
2372
+ shell: false
2373
+ });
2374
+ child.on("error", reject);
2375
+ child.on("close", (code) => resolve2(code ?? 1));
2376
+ });
3349
2377
  };
3350
- var renderProjectInstallResult = (result) => {
3351
- if (result.status === "installed") {
3352
- return renderDetected(
3353
- "Dev dependency",
3354
- "fresh-squeezy",
3355
- `${result.packageManager ?? "npm"} install`
3356
- );
3357
- }
3358
- if (result.status === "already-present") {
3359
- return renderDetected("Dev dependency", "fresh-squeezy", "package.json");
3360
- }
3361
- if (result.status === "self-package") {
3362
- return renderDetected("Project", "fresh-squeezy source repo", "package.json");
3363
- }
3364
- if (result.status === "skipped") {
3365
- return chalk4.dim(" Skipped dev dependency install (--no-install).\n");
2378
+ var pathExists = async (filePath) => {
2379
+ try {
2380
+ await fs2.access(filePath);
2381
+ return true;
2382
+ } catch {
2383
+ return false;
3366
2384
  }
3367
- return chalk4.yellow(" No package.json found; running without installing a dev dependency.\n");
3368
2385
  };
3369
2386
 
3370
- // src/cli/commands/resourceOps.ts
3371
- import { readFileSync } from "fs";
3372
-
3373
2387
  // src/cli/commands/bulkOps.ts
3374
2388
  var supportsBulk = (spec) => Boolean(spec.destructive) && spec.idRole === "id";
3375
2389
  var describeBulkTarget = (item) => {
@@ -3456,7 +2470,166 @@ ${tail}
3456
2470
  `;
3457
2471
  };
3458
2472
 
2473
+ // src/cli/commands/manage.ts
2474
+ var listManageOperations = () => resourceRegistry.filter((spec) => supportsBulk(spec) && findResourceVerb(spec.resource, "list")).map((spec) => ({ resource: spec.resource, verb: spec.verb }));
2475
+ var renderEquivalentCommand = (input2) => {
2476
+ const parts = ["fresh-squeezy", input2.verb, input2.resource];
2477
+ if (input2.picked.length === input2.total) {
2478
+ parts.push("--all");
2479
+ if (input2.storeId) parts.push("--store-ids", input2.storeId);
2480
+ if (input2.parentId) parts.push("--parent-id", input2.parentId);
2481
+ } else {
2482
+ parts.push("--ids", input2.picked.join(","));
2483
+ }
2484
+ parts.push("--yes");
2485
+ if (input2.mode === "live") parts.push("--mode", "live");
2486
+ return parts.join(" ");
2487
+ };
2488
+ var runManageCommand = async () => {
2489
+ try {
2490
+ const client = createFreshSqueezy();
2491
+ const http = new HttpClient(resolveConfig());
2492
+ const op = await pickManageOperation(listManageOperations());
2493
+ const spec = findResourceVerb(op.resource, op.verb);
2494
+ const listSpec = findResourceVerb(op.resource, "list");
2495
+ if (!spec || !listSpec) {
2496
+ throw new FreshSqueezyError({
2497
+ code: "UNKNOWN_OP",
2498
+ message: `${op.verb} ${op.resource} is not available.`
2499
+ });
2500
+ }
2501
+ process.stdout.write(`
2502
+ ${op.verb} ${op.resource} \u2014 mode=${client.mode}
2503
+ `);
2504
+ let scopes = [{}];
2505
+ if (listSpec.idRole === "store") {
2506
+ const { storeIds } = await resolveStores(client, { isInteractive: true });
2507
+ scopes = storeIds.map((storeId) => ({ storeId }));
2508
+ } else if (listSpec.idRole === "parent") {
2509
+ scopes = [{ parentId: await askParentId(op.resource) }];
2510
+ }
2511
+ let exitCode = 0;
2512
+ for (const scope of scopes) {
2513
+ const targets = await collectBulkTargets(http, spec, { all: true, ...scope });
2514
+ if (targets.length === 0) {
2515
+ process.stdout.write(
2516
+ `No ${op.resource} items found${scope.storeId ? ` in store ${scope.storeId}` : ""}.
2517
+ `
2518
+ );
2519
+ continue;
2520
+ }
2521
+ const pickedIds = await pickBulkTargets(op.verb, targets);
2522
+ const picked = targets.filter((t) => pickedIds.includes(t.id));
2523
+ const mark = client.mode === "live" ? "LIVE mode: " : "";
2524
+ const go = await confirmResourceOp(
2525
+ `${mark}${op.verb} ${picked.length} ${op.resource} item(s) (${previewTargets(picked)})?`
2526
+ );
2527
+ if (!go) {
2528
+ process.stdout.write("Skipped \u2014 nothing changed.\n");
2529
+ continue;
2530
+ }
2531
+ const summary = await executeBulk(http, spec, picked);
2532
+ process.stdout.write(renderBulkSummary(spec, summary));
2533
+ process.stdout.write(
2534
+ `
2535
+ Next time:
2536
+ ${renderEquivalentCommand({
2537
+ verb: op.verb,
2538
+ resource: op.resource,
2539
+ mode: client.mode,
2540
+ ...scope,
2541
+ picked: pickedIds,
2542
+ total: targets.length
2543
+ })}
2544
+ `
2545
+ );
2546
+ if (summary.failed > 0) exitCode = 1;
2547
+ }
2548
+ return exitCode;
2549
+ } catch (err) {
2550
+ if (isPromptCancel(err)) throw err;
2551
+ const message = err instanceof Error ? err.message : String(err);
2552
+ process.stderr.write(renderCliError(message, ["fresh-squeezy ops --list"]));
2553
+ return 2;
2554
+ }
2555
+ };
2556
+
2557
+ // src/cli/commands/launcher.ts
2558
+ var runLauncherCommand = async (options) => {
2559
+ if (!options.isInteractive) return 2;
2560
+ try {
2561
+ process.stdout.write(
2562
+ renderBrandHeader(
2563
+ "Project bootstrap",
2564
+ "Install the billing doctor locally, then verify Lemon Squeezy in one guided run."
2565
+ )
2566
+ );
2567
+ process.stdout.write(renderStep(1, 2, "Project dependency", "add fresh-squeezy when missing"));
2568
+ const installResult = await ensureFreshSqueezyDevDependency({
2569
+ skipInstall: options.install === false,
2570
+ packageVersion: options.packageVersion
2571
+ });
2572
+ process.stdout.write(renderProjectInstallResult(installResult));
2573
+ if (!process.env[ENV_KEYS.apiKey]?.trim()) {
2574
+ return startGuidedSetup();
2575
+ }
2576
+ return routeLauncherAction(await pickLauncherAction());
2577
+ } catch (err) {
2578
+ if (isPromptCancel(err)) {
2579
+ process.stderr.write(renderCancelMessage());
2580
+ return 130;
2581
+ }
2582
+ const message = err instanceof Error ? err.message : String(err);
2583
+ process.stderr.write(`${chalk3.red("fresh-squeezy bootstrap failed:")} ${message}
2584
+
2585
+ `);
2586
+ process.stderr.write(renderCommandExamples());
2587
+ return 1;
2588
+ }
2589
+ };
2590
+ var routeLauncherAction = async (action) => {
2591
+ switch (action) {
2592
+ case "init":
2593
+ return startGuidedSetup();
2594
+ case "doctor":
2595
+ return runDoctorCommand({ isInteractive: true });
2596
+ case "manage":
2597
+ return runManageCommand();
2598
+ case "examples":
2599
+ process.stdout.write(renderCommandExamples());
2600
+ return 0;
2601
+ case "exit":
2602
+ return 0;
2603
+ }
2604
+ };
2605
+ var startGuidedSetup = () => {
2606
+ process.stdout.write(
2607
+ renderStep(2, 2, "Guided setup", "detect credentials, stores, and resources")
2608
+ );
2609
+ return runInitCommand({ isInteractive: true });
2610
+ };
2611
+ var renderProjectInstallResult = (result) => {
2612
+ if (result.status === "installed") {
2613
+ return renderDetected(
2614
+ "Dev dependency",
2615
+ "fresh-squeezy",
2616
+ `${result.packageManager ?? "npm"} install`
2617
+ );
2618
+ }
2619
+ if (result.status === "already-present") {
2620
+ return renderDetected("Dev dependency", "fresh-squeezy", "package.json");
2621
+ }
2622
+ if (result.status === "self-package") {
2623
+ return renderDetected("Project", "fresh-squeezy source repo", "package.json");
2624
+ }
2625
+ if (result.status === "skipped") {
2626
+ return chalk3.dim(" Skipped dev dependency install (--no-install).\n");
2627
+ }
2628
+ return chalk3.yellow(" No package.json found; running without installing a dev dependency.\n");
2629
+ };
2630
+
3459
2631
  // src/cli/commands/resourceOps.ts
2632
+ import { readFileSync } from "fs";
3460
2633
  var runResourceOpCommand = async (options) => {
3461
2634
  if (options.listCatalog) {
3462
2635
  writeCatalog(options.json ?? false);
@@ -3489,14 +2662,13 @@ var runResourceOpCommand = async (options) => {
3489
2662
  message: "This op requires a JSON body (--body, --body-file, or stdin)."
3490
2663
  });
3491
2664
  }
3492
- const client = createFreshSqueezy({ mode: options.mode });
3493
2665
  const config = resolveConfig({ mode: options.mode });
3494
2666
  const http = new HttpClient(config);
3495
2667
  const storeId = options.storeIds?.[0] ?? config.storeId;
3496
2668
  if (options.all || options.ids) {
3497
- return await runBulk(http, spec, client.mode, options, { storeId, body });
2669
+ return await runBulk(http, spec, config.mode, options, { storeId, body });
3498
2670
  }
3499
- const allowed = await assertWriteSafety(spec, client.mode, options);
2671
+ const allowed = await assertWriteSafety(spec, config.mode, options);
3500
2672
  if (!allowed) return 2;
3501
2673
  const opBody = await invokeOp(http, options.resource, options.verb, {
3502
2674
  id: options.id,
@@ -3506,7 +2678,7 @@ var runResourceOpCommand = async (options) => {
3506
2678
  });
3507
2679
  const envelope = {
3508
2680
  ok: true,
3509
- mode: client.mode,
2681
+ mode: config.mode,
3510
2682
  resource: spec.resource,
3511
2683
  verb: spec.verb,
3512
2684
  docs: `https://docs.lemonsqueezy.com/api/${spec.docsPath}`,
@@ -3517,7 +2689,7 @@ var runResourceOpCommand = async (options) => {
3517
2689
  `);
3518
2690
  } else {
3519
2691
  process.stdout.write(
3520
- `${spec.verb} ${spec.resource} \u2014 ok (mode=${client.mode})
2692
+ `${spec.verb} ${spec.resource} \u2014 ok (mode=${config.mode})
3521
2693
  ${JSON.stringify(opBody ?? null, null, 2)}
3522
2694
  `
3523
2695
  );
@@ -3657,44 +2829,15 @@ var writeFatal2 = (err, json) => {
3657
2829
  };
3658
2830
 
3659
2831
  // src/cli/commands/validate.ts
3660
- var runValidateCommand = async (target, options) => {
3661
- try {
3662
- const client = createFreshSqueezy({ mode: options.mode });
3663
- if (target === "connection") {
3664
- return emit(await client.validateConnection(), options);
3665
- }
3666
- if (target === "product") {
3667
- return emit(await runProduct(client, options), options);
3668
- }
3669
- if (target === "discount") {
3670
- return emit(await runDiscount(client, options), options);
3671
- }
3672
- if (target === "license-key") {
3673
- return emit(await runLicenseKey(client, options), options);
3674
- }
3675
- if (target === "subscription-plan") {
3676
- return emit(await runSubscriptionPlan(client, options), options);
3677
- }
3678
- const storeIds = await resolveStoresForTarget(client, target, options);
3679
- const results = await runPerStore(client, target, storeIds, options);
3680
- if (options.json) {
3681
- process.stdout.write(`${JSON.stringify(results, null, 2)}
3682
- `);
3683
- } else {
3684
- for (const result of results) {
3685
- process.stdout.write(`${renderResult(result)}
3686
-
3687
- `);
3688
- }
3689
- }
3690
- return results.every((result) => result.ok) ? 0 : 1;
3691
- } catch (err) {
3692
- const message = err instanceof Error ? err.message : String(err);
3693
- process.stderr.write(renderCliError(message, getValidateHints(err, target)));
3694
- 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 });
3695
2836
  }
2837
+ return value;
3696
2838
  };
3697
- 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) => {
3698
2841
  const resolved = await resolveStores(client, {
3699
2842
  storeIds: options.storeIds,
3700
2843
  allStores: options.allStores,
@@ -3708,72 +2851,127 @@ var resolveStoresForTarget = async (client, target, options) => {
3708
2851
  }
3709
2852
  return resolved.storeIds;
3710
2853
  };
3711
- var runPerStore = async (client, target, storeIds, options) => {
3712
- if (target === "store") {
3713
- 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
+ })
3714
2941
  }
3715
- if (target === "webhook") {
3716
- const url = required2(options.webhookUrl, "--webhook-url is required for `validate webhook`.");
3717
- 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
+ ];
3718
2951
  }
2952
+ if (err.code === "MISSING_ARG") return VALIDATE_SUBCOMMANDS[target].examples;
3719
2953
  return [];
3720
2954
  };
3721
- var runProduct = async (client, options) => {
3722
- const productId = required2(options.productId, "--product-id is required for `validate product`.");
3723
- const expected = options.storeIds?.[0];
3724
- return client.validateProduct({
3725
- productId,
3726
- expectedStoreId: expected
3727
- });
3728
- };
3729
- var runDiscount = async (client, options) => {
3730
- const discountId = required2(
3731
- options.discountId,
3732
- "--discount-id is required for `validate discount`."
3733
- );
3734
- const storeId = required2(
3735
- options.storeIds?.[0],
3736
- "--store-ids is required for `validate discount`."
3737
- );
3738
- return client.validateDiscount({ storeId, discountId });
3739
- };
3740
- var runLicenseKey = async (client, options) => {
3741
- const licenseKeyId = required2(
3742
- options.licenseKeyId,
3743
- "--license-key-id is required for `validate license-key`."
3744
- );
3745
- const storeId = required2(
3746
- options.storeIds?.[0],
3747
- "--store-ids is required for `validate license-key`."
3748
- );
3749
- return client.validateLicenseKey({ storeId, licenseKeyId });
3750
- };
3751
- var runSubscriptionPlan = async (client, options) => {
3752
- const variantId = required2(
3753
- options.variantId,
3754
- "--variant-id is required for `validate subscription-plan`."
3755
- );
3756
- const storeId = required2(
3757
- options.storeIds?.[0],
3758
- "--store-ids is required for `validate subscription-plan`."
3759
- );
3760
- return client.validateSubscriptionPlan({ storeId, variantId });
3761
- };
3762
- var emit = (result, options) => {
2955
+ var writeResults = (results, options) => {
2956
+ const list = Array.isArray(results) ? results : [results];
3763
2957
  if (options.json) {
3764
- process.stdout.write(`${JSON.stringify(result, null, 2)}
2958
+ process.stdout.write(`${JSON.stringify(results, null, 2)}
3765
2959
  `);
3766
2960
  } else {
3767
- process.stdout.write(`${renderResult(result)}
3768
- `);
2961
+ const separator = Array.isArray(results) ? "\n\n" : "\n";
2962
+ for (const result of list) process.stdout.write(`${renderResult(result)}${separator}`);
3769
2963
  }
3770
- return result.ok ? 0 : 1;
2964
+ return list.every((result) => result.ok) ? 0 : 1;
3771
2965
  };
3772
- var required2 = (value, message) => {
3773
- if (value === void 0 || value === null || value === "") {
3774
- 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;
3775
2974
  }
3776
- return value;
3777
2975
  };
3778
2976
 
3779
2977
  // src/cli/main.ts
@@ -3791,7 +2989,10 @@ var OP_VERBS = [
3791
2989
  ];
3792
2990
  var parseMode = (value) => {
3793
2991
  if (value === "test" || value === "live") return value;
3794
- 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
+ });
3795
2996
  };
3796
2997
  var parseCsv = (value) => value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3797
2998
  var readPackageVersion = () => {
@@ -3810,69 +3011,9 @@ var readPackageVersion = () => {
3810
3011
  };
3811
3012
  var isInteractive = Boolean(process.stdin.isTTY);
3812
3013
  var packageVersion = readPackageVersion();
3813
- var VALIDATE_COMMANDS = [
3814
- {
3815
- name: "connection",
3816
- description: "Check that the API key authenticates",
3817
- stores: "none"
3818
- },
3819
- {
3820
- name: "store",
3821
- description: "Check one or more stores are reachable",
3822
- stores: "multi",
3823
- storeIdsHelp: "Comma-separated store IDs"
3824
- },
3825
- {
3826
- name: "product",
3827
- description: "Check a product is published with at least one variant",
3828
- stores: "ownership",
3829
- required: ["--product-id <id>", "Product ID to validate"],
3830
- storeIdsHelp: "Expected owning store IDs (first is used for cross-check)"
3831
- },
3832
- {
3833
- name: "webhook",
3834
- description: "Check a webhook is registered with the recommended events",
3835
- stores: "multi",
3836
- required: ["--webhook-url <url>", "Public webhook URL"],
3837
- storeIdsHelp: "Comma-separated store IDs"
3838
- },
3839
- {
3840
- name: "discount",
3841
- description: "Check a discount code is valid and redeemable",
3842
- stores: "ownership",
3843
- required: ["--discount-id <id>", "Discount ID to validate"],
3844
- storeIdsHelp: "Store ID for ownership check (first ID used)"
3845
- },
3846
- {
3847
- name: "license-key",
3848
- description: "Check a license key is active and not at its activation limit",
3849
- stores: "ownership",
3850
- required: ["--license-key-id <id>", "License key ID to validate"],
3851
- storeIdsHelp: "Store ID for ownership check (first ID used)"
3852
- },
3853
- {
3854
- name: "subscription-plan",
3855
- description: "Check a subscription plan variant has valid billing interval and trial config",
3856
- stores: "ownership",
3857
- required: ["--variant-id <id>", "Variant ID of the subscription plan"],
3858
- storeIdsHelp: "Store ID for ownership check (first ID used)"
3859
- }
3860
- ];
3861
3014
  var exitWith = async (code) => {
3862
- process.exit(await code);
3863
- };
3864
- var toValidateOptions = (opts) => ({
3865
- mode: opts.mode,
3866
- storeIds: opts.storeIds,
3867
- allStores: Boolean(opts.allStores),
3868
- productId: opts.productId,
3869
- webhookUrl: opts.webhookUrl,
3870
- discountId: opts.discountId,
3871
- licenseKeyId: opts.licenseKeyId,
3872
- variantId: opts.variantId,
3873
- json: Boolean(opts.json),
3874
- isInteractive
3875
- });
3015
+ process.exitCode = await code;
3016
+ };
3876
3017
  var attachModeJson = (cmd) => cmd.option("-m, --mode <mode>", "test or live", parseMode).option("--json", "Emit machine-readable JSON");
3877
3018
  var attachValidateFlags = (cmd, spec) => {
3878
3019
  if (spec.required) cmd.requiredOption(spec.required[0], spec.required[1]);
@@ -3924,7 +3065,8 @@ program.action(async (opts) => {
3924
3065
  "fresh-squeezy validate connection"
3925
3066
  ])
3926
3067
  );
3927
- process.exit(2);
3068
+ process.exitCode = 2;
3069
+ return;
3928
3070
  }
3929
3071
  await exitWith(
3930
3072
  runLauncherCommand({
@@ -3938,12 +3080,7 @@ var doctor2 = program.command("doctor").description("Run the full integration do
3938
3080
  "--all-resources",
3939
3081
  "Discover and validate every supported resource in each selected store"
3940
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) => {
3941
- await exitWith(
3942
- runDoctorCommand({
3943
- ...toValidateOptions(opts),
3944
- allResources: Boolean(opts.allResources)
3945
- })
3946
- );
3083
+ await exitWith(runDoctorCommand({ ...opts, isInteractive }));
3947
3084
  });
3948
3085
  doctor2.addHelpText(
3949
3086
  "after",
@@ -3966,13 +3103,13 @@ Examples:
3966
3103
  fresh-squeezy validate webhook --store-ids 12 --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
3967
3104
 
3968
3105
  Targets:
3969
- connection, store, product, webhook, discount, license-key, subscription-plan
3106
+ ${Object.keys(VALIDATE_SUBCOMMANDS).join(", ")}
3970
3107
  `
3971
3108
  );
3972
- for (const spec of VALIDATE_COMMANDS) {
3973
- 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(
3974
3111
  async (opts) => {
3975
- await exitWith(runValidateCommand(spec.name, toValidateOptions(opts)));
3112
+ await exitWith(runValidateCommand(name, { ...opts, isInteractive }));
3976
3113
  }
3977
3114
  );
3978
3115
  }
@@ -4053,6 +3190,6 @@ program.parseAsync(process.argv).catch((err) => {
4053
3190
  const message = err instanceof Error ? err.message : String(err);
4054
3191
  const hints = message.includes("Mode must be") ? ["fresh-squeezy doctor --mode test", "fresh-squeezy doctor --mode live"] : ["fresh-squeezy --help"];
4055
3192
  process.stderr.write(renderCliError(message, hints));
4056
- process.exit(2);
3193
+ process.exitCode = 2;
4057
3194
  });
4058
3195
  //# sourceMappingURL=cli.js.map