hookwright 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -8
- package/dist/cli.js +1154 -345
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ var init_package = __esm({
|
|
|
24
24
|
"package.json"() {
|
|
25
25
|
package_default = {
|
|
26
26
|
name: "hookwright",
|
|
27
|
-
version: "1.
|
|
27
|
+
version: "1.1.1",
|
|
28
28
|
description: "Build real, signed e-commerce webhooks from a live Shopify catalogue \u2014 interactive terminal UI, no backend",
|
|
29
29
|
keywords: [
|
|
30
30
|
"webhook",
|
|
@@ -63,6 +63,7 @@ var init_package = __esm({
|
|
|
63
63
|
react: "^19.2.8"
|
|
64
64
|
},
|
|
65
65
|
devDependencies: {
|
|
66
|
+
"curl-to-postmanv2": "^1.8.7",
|
|
66
67
|
esbuild: "^0.28.2"
|
|
67
68
|
}
|
|
68
69
|
};
|
|
@@ -70,11 +71,11 @@ var init_package = __esm({
|
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
// src/cli.jsx
|
|
73
|
-
import
|
|
74
|
+
import React16 from "react";
|
|
74
75
|
import { render } from "ink";
|
|
75
76
|
|
|
76
77
|
// src/ui/App.jsx
|
|
77
|
-
import
|
|
78
|
+
import React15, { useState as useState8 } from "react";
|
|
78
79
|
import { useApp as useApp2 } from "ink";
|
|
79
80
|
|
|
80
81
|
// src/ui/screens/Home.jsx
|
|
@@ -330,8 +331,8 @@ function Home({ config, onPick, onQuit }) {
|
|
|
330
331
|
const ready = canSend(config);
|
|
331
332
|
const shop = config.shopify.shop;
|
|
332
333
|
const options = [
|
|
333
|
-
{ label: "Setup".padEnd(18) + (ready ? "Shopify and webhook destination" : "connect Shopify and set the webhook URL"), value: "setup" },
|
|
334
334
|
{ label: "Integrations".padEnd(18) + "build and send a provider webhook", value: "integrations" },
|
|
335
|
+
{ label: "Setup".padEnd(18) + (ready ? "Shopify and webhook destination" : "connect Shopify and set the webhook URL"), value: "setup" },
|
|
335
336
|
{ label: "History".padEnd(18) + "review or re-send a saved payload", value: "history" },
|
|
336
337
|
{ label: "Doctor".padEnd(18) + "check credentials and connectivity", value: "doctor" },
|
|
337
338
|
{ label: "Clear".padEnd(18) + "remove cached data", value: "clear" },
|
|
@@ -551,6 +552,20 @@ var editableFields = [
|
|
|
551
552
|
{ group: "UTM", path: "data.utm_parameters.utm_content", label: "UTM content", optional: true },
|
|
552
553
|
{ group: "UTM", path: "data.utm_parameters.fbclid", label: "Facebook click id", optional: true }
|
|
553
554
|
];
|
|
555
|
+
function summarize(payload) {
|
|
556
|
+
const d = payload?.data ?? {};
|
|
557
|
+
const items = Array.isArray(d.line_items) ? d.line_items : [];
|
|
558
|
+
return {
|
|
559
|
+
event: "abandoned checkout",
|
|
560
|
+
store: d.store_url,
|
|
561
|
+
customer: `${d.customer?.shipping_address?.customer_name ?? ""} \xB7 ${d.phone ?? ""}`.trim(),
|
|
562
|
+
items: items.map((i) => `${i.name} x${i.quantity}`).join(", "),
|
|
563
|
+
total: d.total_price,
|
|
564
|
+
currency: items[0]?.currency,
|
|
565
|
+
extra: `was ${d.original_total_price}, discount ${d.total_discount}`,
|
|
566
|
+
link: d.abandoned_checkout_url
|
|
567
|
+
};
|
|
568
|
+
}
|
|
554
569
|
var cashfree_occ_default = {
|
|
555
570
|
id: "cashfree-occ",
|
|
556
571
|
urlKey: "cashfree_occ",
|
|
@@ -564,7 +579,461 @@ var cashfree_occ_default = {
|
|
|
564
579
|
buildPayload,
|
|
565
580
|
sign,
|
|
566
581
|
webhookUrl,
|
|
567
|
-
webhookSecret
|
|
582
|
+
webhookSecret,
|
|
583
|
+
summarize
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// src/providers/razorpay-magic.mjs
|
|
587
|
+
import crypto2 from "node:crypto";
|
|
588
|
+
var GATE_PATH2 = "abandoned_checkout_url";
|
|
589
|
+
var fieldMap2 = [
|
|
590
|
+
{ source: "abandoned_checkout_url", target: "\xABgate\xBB + abandonedCheckoutUrl", required: true, note: "must be non-empty or the event is dropped" },
|
|
591
|
+
{ source: "created_at", target: "eventTime" },
|
|
592
|
+
{ source: "shop_id", target: "storeUrl", required: true, note: "shopify_domain / store_url also accepted" },
|
|
593
|
+
{ source: "email", target: "email" },
|
|
594
|
+
{ source: "phone", target: "phone", required: true, note: "falls back to Shipping_address.Phone" },
|
|
595
|
+
{ source: "currency", target: "pricing.currency", required: true, note: "read from the ROOT, unlike Cashfree" },
|
|
596
|
+
{ source: "line_items_total", target: "pricing.totalPrice", required: true, note: "documented as a STRING in paise \u2014 the consumer parseFloats it and divides by 100" },
|
|
597
|
+
{ source: "platform", target: "\u2014", note: "documented root field" },
|
|
598
|
+
{ source: "utm_parameters", target: "\u2014", note: "documented root field" },
|
|
599
|
+
{ source: "customer.first_name", target: "customer.firstName" },
|
|
600
|
+
{ source: "customer.last_name", target: "customer.lastName" },
|
|
601
|
+
{ source: "customer.email", target: "customer.email" },
|
|
602
|
+
{ source: "customer.Shipping_address.Name", target: "customer.name" },
|
|
603
|
+
{ source: "customer.Shipping_address.First_name", target: "customer.firstName fallback" },
|
|
604
|
+
{ source: "customer.Shipping_address.Last_name", target: "customer.lastName fallback" },
|
|
605
|
+
{ source: "customer.Shipping_address.Phone", target: "phone fallback" },
|
|
606
|
+
{ source: "customer.Shipping_address.Address1", target: "customer.address1" },
|
|
607
|
+
{ source: "customer.Shipping_address.Address2", target: "customer.address2" },
|
|
608
|
+
{ source: "customer.Shipping_address.City", target: "customer.city" },
|
|
609
|
+
{ source: "customer.Shipping_address.Province", target: "customer.province" },
|
|
610
|
+
{ source: "customer.Shipping_address.Country_name", target: "customer.country" },
|
|
611
|
+
{ source: "customer.Shipping_address.Country_code", target: "customer.countryCode", required: true, note: "region used to parse the phone" },
|
|
612
|
+
{ source: "customer.Shipping_address.Zip", target: "customer.zip" },
|
|
613
|
+
{ source: "line_items[0].name", target: "cart.productName", required: true },
|
|
614
|
+
{ source: "line_items[0].image_url", target: "cart.image", required: true },
|
|
615
|
+
{ source: "line_items[0].quantity", target: "cart.quantity" }
|
|
616
|
+
];
|
|
617
|
+
var editableFields2 = [
|
|
618
|
+
{ group: "Checkout", path: "abandoned_checkout_url", label: "Abandoned checkout URL", hint: "must be non-empty \u2014 this is the schema gate" },
|
|
619
|
+
{ group: "Checkout", path: "created_at", label: "Created at" },
|
|
620
|
+
{ group: "Checkout", path: "shop_id", label: "Shop domain" },
|
|
621
|
+
{ group: "Checkout", path: "cart_token", label: "Cart token" },
|
|
622
|
+
{ group: "Pricing", path: "line_items_total", label: "Line items total (paise, string)", hint: "documented as a string; the consumer divides by 100" },
|
|
623
|
+
{ group: "Pricing", path: "currency", label: "Currency" },
|
|
624
|
+
{ group: "Customer", path: "customer.first_name", label: "First name" },
|
|
625
|
+
{ group: "Customer", path: "customer.last_name", label: "Last name" },
|
|
626
|
+
{ group: "Customer", path: "email", label: "Email" },
|
|
627
|
+
{ group: "Customer", path: "phone", label: "Phone" },
|
|
628
|
+
{ group: "Shipping", path: "customer.Shipping_address.Name", label: "Ship-to name" },
|
|
629
|
+
{ group: "Shipping", path: "customer.Shipping_address.Address1", label: "Address line 1" },
|
|
630
|
+
{ group: "Shipping", path: "customer.Shipping_address.Address2", label: "Address line 2", optional: true },
|
|
631
|
+
{ group: "Shipping", path: "customer.Shipping_address.City", label: "City" },
|
|
632
|
+
{ group: "Shipping", path: "customer.Shipping_address.Province", label: "Province / state" },
|
|
633
|
+
{ group: "Shipping", path: "customer.Shipping_address.Province_code", label: "Province code", optional: true },
|
|
634
|
+
{ group: "Shipping", path: "customer.Shipping_address.Country_name", label: "Country" },
|
|
635
|
+
{ group: "Shipping", path: "customer.Shipping_address.Country_code", label: "Country code", hint: "region used to parse the phone" },
|
|
636
|
+
{ group: "Shipping", path: "customer.Shipping_address.Zip", label: "Zip / postcode" }
|
|
637
|
+
];
|
|
638
|
+
function buildPayload2(ctx) {
|
|
639
|
+
const { config, items, phone } = ctx;
|
|
640
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
641
|
+
const currency = ctx.currency || config.defaults.currency;
|
|
642
|
+
const domain = config.shopify.domain;
|
|
643
|
+
const cust = config.customer;
|
|
644
|
+
const lineItems = items.map(({ product, variant, quantity }) => {
|
|
645
|
+
const price = money2(variant?.price ?? 0);
|
|
646
|
+
return {
|
|
647
|
+
image_url: imageForVariant(product, variant),
|
|
648
|
+
name: product.title,
|
|
649
|
+
title: product.title,
|
|
650
|
+
price,
|
|
651
|
+
variant_price: price,
|
|
652
|
+
product_id: String(product.id),
|
|
653
|
+
variant_id: variant ? String(variant.id) : null,
|
|
654
|
+
variant_title: variant?.title ?? null,
|
|
655
|
+
quantity,
|
|
656
|
+
sku: variant?.sku || null,
|
|
657
|
+
tax_amount: 0,
|
|
658
|
+
taxable: true,
|
|
659
|
+
store_name: config.shopify.shop?.name ?? domain,
|
|
660
|
+
gram: "0"
|
|
661
|
+
};
|
|
662
|
+
});
|
|
663
|
+
const total = money2(lineItems.reduce((sum, li) => sum + li.price * li.quantity, 0));
|
|
664
|
+
const discount = money2(Math.min(ctx.discount ?? 0, total));
|
|
665
|
+
const net2 = money2(total - discount);
|
|
666
|
+
const checkoutUrl = cartPermalink(domain, items.map(({ variant, quantity }) => ({ variantId: variant?.id, quantity })));
|
|
667
|
+
return {
|
|
668
|
+
shop_id: domain,
|
|
669
|
+
shopify_domain: domain,
|
|
670
|
+
platform: config.defaults.platform,
|
|
671
|
+
token: crypto2.randomBytes(16).toString("hex"),
|
|
672
|
+
cart_token: crypto2.randomBytes(16).toString("hex"),
|
|
673
|
+
email: cust.email,
|
|
674
|
+
phone,
|
|
675
|
+
abandoned_checkout_url: checkoutUrl,
|
|
676
|
+
currency,
|
|
677
|
+
// Documented as a string, in the smallest currency unit (paise).
|
|
678
|
+
// The consumer parses it with parseFloat and divides by 100.
|
|
679
|
+
line_items_total: String(Math.round(net2 * 100)),
|
|
680
|
+
line_items: lineItems,
|
|
681
|
+
tax_details: {},
|
|
682
|
+
promotions: [],
|
|
683
|
+
utm_parameters: { ...config.defaults.utm },
|
|
684
|
+
// Not in the documented field list, but the consumer reads it for eventTime.
|
|
685
|
+
created_at: now.toISOString(),
|
|
686
|
+
customer: {
|
|
687
|
+
email: cust.email,
|
|
688
|
+
first_name: cust.firstName,
|
|
689
|
+
last_name: cust.lastName,
|
|
690
|
+
created_at: now.toISOString(),
|
|
691
|
+
Shipping_address: {
|
|
692
|
+
Name: `${cust.firstName} ${cust.lastName}`.trim(),
|
|
693
|
+
First_name: cust.firstName,
|
|
694
|
+
Last_name: cust.lastName,
|
|
695
|
+
Phone: phone,
|
|
696
|
+
Address1: cust.address1,
|
|
697
|
+
Address2: cust.address2,
|
|
698
|
+
City: cust.city,
|
|
699
|
+
Province: cust.province,
|
|
700
|
+
Province_code: cust.provinceCode,
|
|
701
|
+
Country: cust.country,
|
|
702
|
+
Country_name: cust.country,
|
|
703
|
+
Country_code: cust.countryCode,
|
|
704
|
+
Zip: cust.zip
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
function sign2({ body, secret }) {
|
|
710
|
+
const signature = crypto2.createHmac("sha256", String(secret)).update(body).digest("hex");
|
|
711
|
+
return {
|
|
712
|
+
signature,
|
|
713
|
+
timestamp: null,
|
|
714
|
+
headers: {
|
|
715
|
+
"content-type": "application/json",
|
|
716
|
+
"x-razorpay-signature": signature
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function webhookUrl2(config) {
|
|
721
|
+
return String(targetFor(config, "razorpay-magic").webhookUrl ?? "").trim();
|
|
722
|
+
}
|
|
723
|
+
function webhookSecret2(config) {
|
|
724
|
+
return String(targetFor(config, "razorpay-magic").webhookSecret ?? "");
|
|
725
|
+
}
|
|
726
|
+
function summarize2(payload) {
|
|
727
|
+
const items = Array.isArray(payload?.line_items) ? payload.line_items : [];
|
|
728
|
+
const ship = payload?.customer?.Shipping_address ?? {};
|
|
729
|
+
return {
|
|
730
|
+
event: "abandoned checkout",
|
|
731
|
+
store: payload?.shop_id,
|
|
732
|
+
customer: `${ship.Name ?? ""} \xB7 ${payload?.phone ?? ""}`.trim(),
|
|
733
|
+
items: items.map((i) => `${i.name} x${i.quantity}`).join(", "),
|
|
734
|
+
total: Number.parseFloat(payload?.line_items_total ?? "0") / 100,
|
|
735
|
+
currency: payload?.currency,
|
|
736
|
+
extra: `${payload?.line_items_total} paise`,
|
|
737
|
+
link: payload?.abandoned_checkout_url
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
var razorpay_magic_default = {
|
|
741
|
+
id: "razorpay-magic",
|
|
742
|
+
urlKey: "razorpay_magic",
|
|
743
|
+
label: "Razorpay Magic",
|
|
744
|
+
eventType: "razorpay_magic_abandoned_checkout",
|
|
745
|
+
gate: { path: GATE_PATH2, value: void 0, nonEmpty: true },
|
|
746
|
+
signatureScheme: "hex HMAC-SHA256 over the body",
|
|
747
|
+
signatureHeader: "x-razorpay-signature",
|
|
748
|
+
fieldMap: fieldMap2,
|
|
749
|
+
editableFields: editableFields2,
|
|
750
|
+
buildPayload: buildPayload2,
|
|
751
|
+
sign: sign2,
|
|
752
|
+
webhookUrl: webhookUrl2,
|
|
753
|
+
webhookSecret: webhookSecret2,
|
|
754
|
+
summarize: summarize2
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// src/providers/nitro.mjs
|
|
758
|
+
import crypto3 from "node:crypto";
|
|
759
|
+
var EVENTS = [
|
|
760
|
+
{ eventName: "view", type: "nitro_view", label: "Page View" },
|
|
761
|
+
{ eventName: "category_view", type: "nitro_category_view", label: "Category View" },
|
|
762
|
+
{ eventName: "product_view", type: "nitro_product_view", label: "Product View" },
|
|
763
|
+
{ eventName: "addtocart", type: "nitro_addtocart", label: "Add To Cart" },
|
|
764
|
+
{ eventName: "removefromcart", type: "nitro_removefromcart", label: "Remove From Cart" },
|
|
765
|
+
{ eventName: "checkout", type: "nitro_checkout", label: "Checkout" },
|
|
766
|
+
{ eventName: "orders/create", type: "nitro_orders_create", label: "Order Created" },
|
|
767
|
+
{ eventName: "orders/updated", type: "nitro_orders_updated", label: "Order Updated" }
|
|
768
|
+
];
|
|
769
|
+
var DEFAULT_EVENT = "addtocart";
|
|
770
|
+
var COMMON_FIELDS = [
|
|
771
|
+
{ source: "eventName", target: "\xABgate\xBB", required: true, note: "must match the schema exactly" },
|
|
772
|
+
{ source: "country", target: "phone region", required: true, note: "used to parse eventVal.customer.phone" },
|
|
773
|
+
{ source: "eventVal.customer.phone", target: "phone", required: true, note: "missing phone is rejected on every event" }
|
|
774
|
+
];
|
|
775
|
+
var PAGE_FIELDS = [
|
|
776
|
+
{ source: "eventVal.page", target: "eventVal.page" },
|
|
777
|
+
{ source: "eventVal.h", target: "eventVal.h", note: "host" },
|
|
778
|
+
{ source: "eventVal.l", target: "eventVal.l", note: "location" },
|
|
779
|
+
{ source: "eventVal._ss", target: "eventVal._ss", note: "session" }
|
|
780
|
+
];
|
|
781
|
+
var FIELD_MAPS = {
|
|
782
|
+
view: [...COMMON_FIELDS, ...PAGE_FIELDS],
|
|
783
|
+
category_view: [
|
|
784
|
+
...COMMON_FIELDS,
|
|
785
|
+
...PAGE_FIELDS,
|
|
786
|
+
{ source: "eventVal.resource_id", target: "eventVal.resource_id", required: true },
|
|
787
|
+
{ source: "eventVal.resource", target: "eventVal.resource" },
|
|
788
|
+
{ source: "eventVal.domain", target: "eventVal.domain" }
|
|
789
|
+
],
|
|
790
|
+
product_view: [
|
|
791
|
+
...COMMON_FIELDS,
|
|
792
|
+
...PAGE_FIELDS,
|
|
793
|
+
{ source: "eventVal.title", target: "eventVal.title", required: true },
|
|
794
|
+
{ source: "eventVal.image", target: "eventVal.image", required: true },
|
|
795
|
+
{ source: "eventVal.price", target: "eventVal.price", required: true },
|
|
796
|
+
{ source: "eventVal.resource_id", target: "eventVal.resource_id", required: true },
|
|
797
|
+
{ source: "eventVal.resource", target: "eventVal.resource" },
|
|
798
|
+
{ source: "eventVal.domain", target: "eventVal.domain" }
|
|
799
|
+
],
|
|
800
|
+
addtocart: [
|
|
801
|
+
...COMMON_FIELDS,
|
|
802
|
+
{ source: "eventVal.cart_value", target: "eventVal.cart_value", required: true },
|
|
803
|
+
{ source: "eventVal.init_cart", target: "eventVal.init_cart" },
|
|
804
|
+
{ source: "eventVal.empty_cart", target: "eventVal.empty_cart" },
|
|
805
|
+
{ source: "eventVal.bik_customer_id", target: "eventVal.bik_customer_id" },
|
|
806
|
+
{ source: "eventVal.line_items[0].name", target: "cart enrichment", required: true }
|
|
807
|
+
],
|
|
808
|
+
removefromcart: [
|
|
809
|
+
...COMMON_FIELDS,
|
|
810
|
+
{ source: "eventVal.cart_value", target: "eventVal.cart_value", required: true },
|
|
811
|
+
{ source: "eventVal.init_cart", target: "eventVal.init_cart" },
|
|
812
|
+
{ source: "eventVal.empty_cart", target: "eventVal.empty_cart" },
|
|
813
|
+
{ source: "eventVal.bik_customer_id", target: "eventVal.bik_customer_id" },
|
|
814
|
+
{ source: "eventVal.line_items[0].name", target: "cart enrichment" }
|
|
815
|
+
],
|
|
816
|
+
checkout: [
|
|
817
|
+
...COMMON_FIELDS,
|
|
818
|
+
{ source: "eventVal.checkout", target: "eventVal.checkout", required: true },
|
|
819
|
+
{ source: "eventVal.cart_value", target: "eventVal.cart_value", required: true },
|
|
820
|
+
{ source: "eventVal.bik_customer_id", target: "eventVal.bik_customer_id" }
|
|
821
|
+
],
|
|
822
|
+
"orders/create": [
|
|
823
|
+
...COMMON_FIELDS,
|
|
824
|
+
{ source: "eventVal.order_id", target: "eventVal.order_id", required: true },
|
|
825
|
+
{ source: "eventVal.order_number", target: "eventVal.order_number" },
|
|
826
|
+
{ source: "eventVal.order_name", target: "eventVal.order_name" },
|
|
827
|
+
{ source: "eventVal.price", target: "eventVal.price", required: true },
|
|
828
|
+
{ source: "eventVal.currency", target: "eventVal.currency", required: true },
|
|
829
|
+
{ source: "eventVal.url", target: "eventVal.url" },
|
|
830
|
+
{ source: "eventVal.order_created_at", target: "eventVal.order_created_at" },
|
|
831
|
+
{ source: "eventVal.bik_customer_id", target: "eventVal.bik_customer_id" }
|
|
832
|
+
]
|
|
833
|
+
};
|
|
834
|
+
FIELD_MAPS["orders/updated"] = FIELD_MAPS["orders/create"];
|
|
835
|
+
var fieldMap3 = FIELD_MAPS[DEFAULT_EVENT];
|
|
836
|
+
var EDITABLE = {
|
|
837
|
+
common: [
|
|
838
|
+
{ group: "Event", path: "eventName", label: "Event name", hint: "the schema gate \u2014 must match exactly" },
|
|
839
|
+
{ group: "Event", path: "country", label: "Country code", hint: "region used to parse the phone" },
|
|
840
|
+
{ group: "Event", path: "timestamp", label: "Timestamp" },
|
|
841
|
+
{ group: "Customer", path: "eventVal.customer.phone", label: "Phone", hint: "required on every Nitro event" },
|
|
842
|
+
{ group: "Customer", path: "eventVal.customer.email", label: "Email" },
|
|
843
|
+
{ group: "Customer", path: "eventVal.customer.name", label: "Name" }
|
|
844
|
+
],
|
|
845
|
+
page: [
|
|
846
|
+
{ group: "Page", path: "eventVal.page", label: "Page" },
|
|
847
|
+
{ group: "Page", path: "eventVal.h", label: "Host" },
|
|
848
|
+
{ group: "Page", path: "eventVal.l", label: "Location URL" },
|
|
849
|
+
{ group: "Page", path: "eventVal._ss", label: "Session id" }
|
|
850
|
+
],
|
|
851
|
+
cart: [
|
|
852
|
+
{ group: "Cart", path: "eventVal.cart_value", label: "Cart value", type: "number" },
|
|
853
|
+
{ group: "Cart", path: "eventVal.init_cart", label: "Init cart" },
|
|
854
|
+
{ group: "Cart", path: "eventVal.empty_cart", label: "Empty cart" },
|
|
855
|
+
{ group: "Cart", path: "eventVal.bik_customer_id", label: "Bik customer id" }
|
|
856
|
+
],
|
|
857
|
+
order: [
|
|
858
|
+
{ group: "Order", path: "eventVal.order_id", label: "Order id" },
|
|
859
|
+
{ group: "Order", path: "eventVal.order_number", label: "Order number" },
|
|
860
|
+
{ group: "Order", path: "eventVal.order_name", label: "Order name" },
|
|
861
|
+
{ group: "Order", path: "eventVal.price", label: "Price", type: "number" },
|
|
862
|
+
{ group: "Order", path: "eventVal.currency", label: "Currency" },
|
|
863
|
+
{ group: "Order", path: "eventVal.url", label: "Order URL" }
|
|
864
|
+
]
|
|
865
|
+
};
|
|
866
|
+
function editableFieldsFor(eventName) {
|
|
867
|
+
const e = EDITABLE;
|
|
868
|
+
if (eventName === "view") return [...e.common, ...e.page];
|
|
869
|
+
if (eventName === "category_view" || eventName === "product_view") return [...e.common, ...e.page];
|
|
870
|
+
if (eventName === "addtocart" || eventName === "removefromcart") return [...e.common, ...e.cart];
|
|
871
|
+
if (eventName === "checkout") return [...e.common, ...e.cart];
|
|
872
|
+
if (eventName.startsWith("orders/")) return [...e.common, ...e.order];
|
|
873
|
+
return e.common;
|
|
874
|
+
}
|
|
875
|
+
var editableFields3 = editableFieldsFor(DEFAULT_EVENT);
|
|
876
|
+
function buildPayload3(ctx) {
|
|
877
|
+
const { config, items, phone } = ctx;
|
|
878
|
+
const eventName = ctx.eventName ?? DEFAULT_EVENT;
|
|
879
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
880
|
+
const currency = ctx.currency || config.defaults.currency;
|
|
881
|
+
const domain = config.shopify.domain;
|
|
882
|
+
const cust = config.customer;
|
|
883
|
+
const session = crypto3.randomBytes(8).toString("hex");
|
|
884
|
+
const first = items[0];
|
|
885
|
+
const lineItems = items.map(({ product, variant, quantity }) => ({
|
|
886
|
+
id: variant ? String(variant.id) : null,
|
|
887
|
+
product_id: String(product.id),
|
|
888
|
+
name: product.title,
|
|
889
|
+
title: product.title,
|
|
890
|
+
quantity,
|
|
891
|
+
price: money2(variant?.price ?? 0),
|
|
892
|
+
image: imageForVariant(product, variant),
|
|
893
|
+
url: `https://${domain}/products/${product.handle ?? ""}`
|
|
894
|
+
}));
|
|
895
|
+
const cartValue = money2(lineItems.reduce((sum, li) => sum + li.price * li.quantity, 0));
|
|
896
|
+
const productUrl = first ? `https://${domain}/products/${first.product.handle ?? ""}` : `https://${domain}`;
|
|
897
|
+
const customer = {
|
|
898
|
+
phone,
|
|
899
|
+
email: cust.email,
|
|
900
|
+
name: `${cust.firstName} ${cust.lastName}`.trim(),
|
|
901
|
+
first_name: cust.firstName,
|
|
902
|
+
last_name: cust.lastName
|
|
903
|
+
};
|
|
904
|
+
const base = {
|
|
905
|
+
org_token: config.defaults.orgToken ?? crypto3.randomBytes(12).toString("hex"),
|
|
906
|
+
eventName,
|
|
907
|
+
userId: crypto3.randomUUID(),
|
|
908
|
+
timestamp: now.toISOString(),
|
|
909
|
+
country: cust.countryCode,
|
|
910
|
+
domain
|
|
911
|
+
};
|
|
912
|
+
const page = {
|
|
913
|
+
page: eventName === "product_view" ? "product" : eventName === "category_view" ? "collection" : "home",
|
|
914
|
+
h: domain,
|
|
915
|
+
l: productUrl,
|
|
916
|
+
_ss: session,
|
|
917
|
+
domain
|
|
918
|
+
};
|
|
919
|
+
let eventVal;
|
|
920
|
+
switch (eventName) {
|
|
921
|
+
case "view":
|
|
922
|
+
eventVal = { ...page, page: "home", l: `https://${domain}`, customer };
|
|
923
|
+
break;
|
|
924
|
+
case "category_view":
|
|
925
|
+
eventVal = {
|
|
926
|
+
...page,
|
|
927
|
+
resource: "collection",
|
|
928
|
+
resource_id: String(first?.product?.id ?? ""),
|
|
929
|
+
l: `https://${domain}/collections/all`,
|
|
930
|
+
customer
|
|
931
|
+
};
|
|
932
|
+
break;
|
|
933
|
+
case "product_view":
|
|
934
|
+
eventVal = {
|
|
935
|
+
...page,
|
|
936
|
+
resource: "product",
|
|
937
|
+
resource_id: String(first?.product?.id ?? ""),
|
|
938
|
+
title: first?.product?.title,
|
|
939
|
+
image: first ? imageForVariant(first.product, first.variant) : void 0,
|
|
940
|
+
price: money2(first?.variant?.price ?? 0),
|
|
941
|
+
customer
|
|
942
|
+
};
|
|
943
|
+
break;
|
|
944
|
+
case "addtocart":
|
|
945
|
+
case "removefromcart":
|
|
946
|
+
eventVal = {
|
|
947
|
+
cart_value: cartValue,
|
|
948
|
+
init_cart: eventName === "addtocart",
|
|
949
|
+
empty_cart: eventName === "removefromcart" && items.length === 0,
|
|
950
|
+
bik_customer_id: crypto3.randomUUID(),
|
|
951
|
+
line_items: lineItems,
|
|
952
|
+
[eventName === "addtocart" ? "items_added" : "items_removed"]: lineItems,
|
|
953
|
+
customer
|
|
954
|
+
};
|
|
955
|
+
break;
|
|
956
|
+
case "checkout":
|
|
957
|
+
eventVal = {
|
|
958
|
+
checkout: cartPermalink(domain, items.map(({ variant, quantity }) => ({ variantId: variant?.id, quantity }))),
|
|
959
|
+
cart_value: cartValue,
|
|
960
|
+
bik_customer_id: crypto3.randomUUID(),
|
|
961
|
+
line_items: lineItems,
|
|
962
|
+
customer
|
|
963
|
+
};
|
|
964
|
+
break;
|
|
965
|
+
case "orders/create":
|
|
966
|
+
case "orders/updated": {
|
|
967
|
+
const orderNumber = 1e3 + Math.floor(Math.random() * 9e3);
|
|
968
|
+
eventVal = {
|
|
969
|
+
url: `https://${domain}/admin/orders/${orderNumber}`,
|
|
970
|
+
order_id: String(Date.now()),
|
|
971
|
+
order_number: orderNumber,
|
|
972
|
+
order_name: `#${orderNumber}`,
|
|
973
|
+
price: cartValue,
|
|
974
|
+
currency,
|
|
975
|
+
order_created_at: now.toISOString(),
|
|
976
|
+
bik_customer_id: crypto3.randomUUID(),
|
|
977
|
+
line_items: lineItems,
|
|
978
|
+
customer
|
|
979
|
+
};
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
982
|
+
default:
|
|
983
|
+
eventVal = { customer };
|
|
984
|
+
}
|
|
985
|
+
return { ...base, eventVal };
|
|
986
|
+
}
|
|
987
|
+
function sign3({ secret }) {
|
|
988
|
+
return {
|
|
989
|
+
signature: null,
|
|
990
|
+
timestamp: null,
|
|
991
|
+
headers: {
|
|
992
|
+
"content-type": "application/json",
|
|
993
|
+
authorization: `Bearer ${secret}`
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
function webhookUrl3(config) {
|
|
998
|
+
return String(targetFor(config, "nitro").webhookUrl ?? "").trim();
|
|
999
|
+
}
|
|
1000
|
+
function webhookSecret3(config) {
|
|
1001
|
+
return String(targetFor(config, "nitro").webhookSecret ?? "");
|
|
1002
|
+
}
|
|
1003
|
+
function summarize3(payload) {
|
|
1004
|
+
const ev = payload?.eventVal ?? {};
|
|
1005
|
+
const items = Array.isArray(ev.line_items) ? ev.line_items : [];
|
|
1006
|
+
return {
|
|
1007
|
+
event: payload?.eventName,
|
|
1008
|
+
store: payload?.domain,
|
|
1009
|
+
customer: `${ev.customer?.name ?? ""} \xB7 ${ev.customer?.phone ?? ""}`.trim(),
|
|
1010
|
+
items: items.map((i) => `${i.name} x${i.quantity}`).join(", ") || "\u2014",
|
|
1011
|
+
total: ev.cart_value ?? ev.price ?? void 0,
|
|
1012
|
+
currency: ev.currency,
|
|
1013
|
+
extra: ev.order_name ? `order ${ev.order_name}` : ev.checkout ? "checkout started" : "",
|
|
1014
|
+
link: ev.checkout ?? ev.url ?? ev.l
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
var nitro_default = {
|
|
1018
|
+
id: "nitro",
|
|
1019
|
+
urlKey: "nitro",
|
|
1020
|
+
label: "Nitro",
|
|
1021
|
+
events: EVENTS,
|
|
1022
|
+
defaultEvent: DEFAULT_EVENT,
|
|
1023
|
+
eventType: "nitro_addtocart",
|
|
1024
|
+
gate: { path: "eventName", value: DEFAULT_EVENT },
|
|
1025
|
+
gateFor: (eventName) => ({ path: "eventName", value: eventName }),
|
|
1026
|
+
signatureScheme: "static bearer token (no body signature)",
|
|
1027
|
+
signatureHeader: "authorization",
|
|
1028
|
+
fieldMap: fieldMap3,
|
|
1029
|
+
fieldMapFor: (eventName) => FIELD_MAPS[eventName] ?? FIELD_MAPS[DEFAULT_EVENT],
|
|
1030
|
+
editableFields: editableFields3,
|
|
1031
|
+
editableFieldsFor,
|
|
1032
|
+
buildPayload: buildPayload3,
|
|
1033
|
+
sign: sign3,
|
|
1034
|
+
webhookUrl: webhookUrl3,
|
|
1035
|
+
webhookSecret: webhookSecret3,
|
|
1036
|
+
summarize: summarize3
|
|
568
1037
|
};
|
|
569
1038
|
|
|
570
1039
|
// src/providers/index.mjs
|
|
@@ -581,18 +1050,21 @@ var PROVIDER_META = [
|
|
|
581
1050
|
label: "Razorpay Magic",
|
|
582
1051
|
signature: "hex HMAC-SHA256 over the body",
|
|
583
1052
|
header: "x-razorpay-signature",
|
|
584
|
-
available:
|
|
1053
|
+
available: true
|
|
585
1054
|
},
|
|
586
1055
|
{
|
|
587
1056
|
id: "nitro",
|
|
588
1057
|
label: "Nitro",
|
|
589
1058
|
signature: "static bearer token",
|
|
590
1059
|
header: "authorization",
|
|
591
|
-
available:
|
|
1060
|
+
available: true,
|
|
1061
|
+
events: nitro_default.events
|
|
592
1062
|
}
|
|
593
1063
|
];
|
|
594
1064
|
var providers = {
|
|
595
|
-
[cashfree_occ_default.id]: cashfree_occ_default
|
|
1065
|
+
[cashfree_occ_default.id]: cashfree_occ_default,
|
|
1066
|
+
[razorpay_magic_default.id]: razorpay_magic_default,
|
|
1067
|
+
[nitro_default.id]: nitro_default
|
|
596
1068
|
};
|
|
597
1069
|
function getProvider(id) {
|
|
598
1070
|
const provider = providers[id];
|
|
@@ -671,8 +1143,8 @@ function Integrations({ config, onPick, onBack, onSetup }) {
|
|
|
671
1143
|
}
|
|
672
1144
|
|
|
673
1145
|
// src/ui/screens/Build.jsx
|
|
674
|
-
import
|
|
675
|
-
import { Box as
|
|
1146
|
+
import React8, { useEffect, useState } from "react";
|
|
1147
|
+
import { Box as Box8, Text as Text8, useApp, useInput as useInput3 } from "ink";
|
|
676
1148
|
import { Spinner, Select as Select4, MultiSelect, TextInput, ConfirmInput, Alert, Badge } from "@inkjs/ui";
|
|
677
1149
|
|
|
678
1150
|
// src/ui/components/CheckList.jsx
|
|
@@ -723,6 +1195,40 @@ function FieldTable({ coverage: coverage2, limit }) {
|
|
|
723
1195
|
] });
|
|
724
1196
|
}
|
|
725
1197
|
|
|
1198
|
+
// src/ui/components/CommandBar.jsx
|
|
1199
|
+
import React7 from "react";
|
|
1200
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1201
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1202
|
+
function CommandBar({ commands, width: width2 = 78 }) {
|
|
1203
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1204
|
+
/* @__PURE__ */ jsx7(Text7, { color: palette.dim, children: "\u2500".repeat(width2) }),
|
|
1205
|
+
/* @__PURE__ */ jsx7(Box7, { flexWrap: "wrap", children: commands.map((c2, i) => /* @__PURE__ */ jsxs7(Box7, { marginRight: 2, children: [
|
|
1206
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: c2.disabled ? palette.dim : c2.active ? palette.ok : palette.accent, children: c2.key }),
|
|
1207
|
+
/* @__PURE__ */ jsx7(Text7, { color: palette.dim, children: ` ${c2.label}` }),
|
|
1208
|
+
i < commands.length - 1 ? /* @__PURE__ */ jsx7(Text7, { color: palette.dim, children: " \xB7" }) : null
|
|
1209
|
+
] }, c2.key)) })
|
|
1210
|
+
] });
|
|
1211
|
+
}
|
|
1212
|
+
function StatusRow({ label, ok, warn, value, detail, labelWidth = 12 }) {
|
|
1213
|
+
const colour = ok === void 0 ? palette.dim : ok ? palette.ok : warn ? palette.warn : palette.bad;
|
|
1214
|
+
const mark = ok === void 0 ? " " : ok ? "\u2714" : warn ? "\u25B2" : "\u2716";
|
|
1215
|
+
return /* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1216
|
+
/* @__PURE__ */ jsx7(Text7, { color: colour, children: ` ${mark} ` }),
|
|
1217
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, children: String(label).padEnd(labelWidth) }),
|
|
1218
|
+
/* @__PURE__ */ jsx7(Text7, { color: colour, children: value }),
|
|
1219
|
+
detail ? /* @__PURE__ */ jsx7(Text7, { color: palette.dim, children: ` ${detail}` }) : null
|
|
1220
|
+
] });
|
|
1221
|
+
}
|
|
1222
|
+
function Panel({ title, hint, children }) {
|
|
1223
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
1224
|
+
/* @__PURE__ */ jsxs7(Box7, { children: [
|
|
1225
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: palette.heading, children: title }),
|
|
1226
|
+
hint ? /* @__PURE__ */ jsx7(Text7, { color: palette.dim, children: ` ${hint}` }) : null
|
|
1227
|
+
] }),
|
|
1228
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: palette.dim, paddingX: 1, children })
|
|
1229
|
+
] });
|
|
1230
|
+
}
|
|
1231
|
+
|
|
726
1232
|
// src/errors.mjs
|
|
727
1233
|
var ForgeError = class extends Error {
|
|
728
1234
|
constructor(message, { hint, cause } = {}) {
|
|
@@ -988,6 +1494,113 @@ function setPath(obj, path6, value) {
|
|
|
988
1494
|
return obj;
|
|
989
1495
|
}
|
|
990
1496
|
|
|
1497
|
+
// src/core/reachability.mjs
|
|
1498
|
+
import dns from "node:dns/promises";
|
|
1499
|
+
import net from "node:net";
|
|
1500
|
+
function parseEndpoint(url) {
|
|
1501
|
+
const raw = String(url ?? "").trim();
|
|
1502
|
+
if (!raw) return { ok: false, reason: "no webhook URL configured" };
|
|
1503
|
+
let parsed;
|
|
1504
|
+
try {
|
|
1505
|
+
parsed = new URL(raw);
|
|
1506
|
+
} catch {
|
|
1507
|
+
return { ok: false, reason: `"${raw}" is not a valid URL` };
|
|
1508
|
+
}
|
|
1509
|
+
if (!/^https?:$/.test(parsed.protocol)) {
|
|
1510
|
+
return { ok: false, reason: `unsupported protocol "${parsed.protocol}" \u2014 use http or https` };
|
|
1511
|
+
}
|
|
1512
|
+
if (!parsed.hostname) return { ok: false, reason: "URL has no host" };
|
|
1513
|
+
return { ok: true, url: parsed };
|
|
1514
|
+
}
|
|
1515
|
+
async function resolves(hostname) {
|
|
1516
|
+
if (net.isIP(hostname)) return { ok: true, address: hostname };
|
|
1517
|
+
try {
|
|
1518
|
+
const { address } = await dns.lookup(hostname);
|
|
1519
|
+
return { ok: true, address };
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
if (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN") {
|
|
1522
|
+
return { ok: false, code: err.code, reason: `host "${hostname}" does not resolve` };
|
|
1523
|
+
}
|
|
1524
|
+
return { ok: false, code: err.code, reason: `DNS lookup failed: ${err.message}` };
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function connects(host, port, timeoutMs) {
|
|
1528
|
+
return new Promise((resolve) => {
|
|
1529
|
+
const socket = net.connect({ host, port });
|
|
1530
|
+
const done = (result) => {
|
|
1531
|
+
socket.removeAllListeners();
|
|
1532
|
+
socket.destroy();
|
|
1533
|
+
resolve(result);
|
|
1534
|
+
};
|
|
1535
|
+
socket.setTimeout(timeoutMs);
|
|
1536
|
+
socket.once("connect", () => done({ ok: true }));
|
|
1537
|
+
socket.once("timeout", () => done({ ok: false, timedOut: true }));
|
|
1538
|
+
socket.once("error", (err) => done({ ok: false, code: err.code, message: err.message }));
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
async function respond(url, method, timeoutMs) {
|
|
1542
|
+
const controller = new AbortController();
|
|
1543
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1544
|
+
try {
|
|
1545
|
+
const res = await fetch(url, { method, signal: controller.signal, redirect: "manual" });
|
|
1546
|
+
clearTimeout(timer);
|
|
1547
|
+
return { responded: true, status: res.status };
|
|
1548
|
+
} catch (err) {
|
|
1549
|
+
clearTimeout(timer);
|
|
1550
|
+
return {
|
|
1551
|
+
responded: false,
|
|
1552
|
+
aborted: err.name === "AbortError",
|
|
1553
|
+
code: err.cause?.code ?? err.code,
|
|
1554
|
+
message: err.message
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async function checkEndpointExists(url, { timeoutMs = 8e3 } = {}) {
|
|
1559
|
+
const name = "Endpoint exists";
|
|
1560
|
+
const parsed = parseEndpoint(url);
|
|
1561
|
+
if (!parsed.ok) return { name, ok: false, exists: false, detail: parsed.reason };
|
|
1562
|
+
const target = parsed.url;
|
|
1563
|
+
const dnsResult = await resolves(target.hostname);
|
|
1564
|
+
if (!dnsResult.ok) {
|
|
1565
|
+
return { name, ok: false, exists: false, detail: `${dnsResult.reason} \u2014 check the URL for a typo` };
|
|
1566
|
+
}
|
|
1567
|
+
const port = Number(target.port) || (target.protocol === "https:" ? 443 : 80);
|
|
1568
|
+
const tcp = await connects(dnsResult.address, port, timeoutMs);
|
|
1569
|
+
if (!tcp.ok && tcp.code === "ECONNREFUSED") {
|
|
1570
|
+
return { name, ok: false, exists: false, detail: `${target.host} refused the connection on port ${port} \u2014 nothing is listening there` };
|
|
1571
|
+
}
|
|
1572
|
+
if (!tcp.ok && (tcp.code === "EHOSTUNREACH" || tcp.code === "ENETUNREACH")) {
|
|
1573
|
+
return { name, ok: false, exists: false, detail: `${target.host} is unreachable (${tcp.code})` };
|
|
1574
|
+
}
|
|
1575
|
+
if (!tcp.ok && tcp.timedOut) {
|
|
1576
|
+
return { name, ok: true, warn: true, exists: void 0, detail: `${target.host}:${port} did not accept a connection within ${timeoutMs}ms \u2014 sending anyway` };
|
|
1577
|
+
}
|
|
1578
|
+
let attempt = await respond(target, "OPTIONS", timeoutMs);
|
|
1579
|
+
if (!attempt.responded) attempt = await respond(target, "HEAD", timeoutMs);
|
|
1580
|
+
if (attempt.responded) {
|
|
1581
|
+
if (attempt.status === 404) {
|
|
1582
|
+
return {
|
|
1583
|
+
name,
|
|
1584
|
+
ok: false,
|
|
1585
|
+
exists: true,
|
|
1586
|
+
detail: `${target.host} is reachable but returned 404 for ${target.pathname} \u2014 the host is right, the path is not`
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
return { name, ok: true, exists: true, detail: `${target.host} responded (HTTP ${attempt.status}) at ${target.pathname}` };
|
|
1590
|
+
}
|
|
1591
|
+
if (attempt.aborted) {
|
|
1592
|
+
return { name, ok: true, warn: true, exists: void 0, detail: `${target.host} did not answer within ${timeoutMs}ms \u2014 sending anyway` };
|
|
1593
|
+
}
|
|
1594
|
+
const code = attempt.code;
|
|
1595
|
+
if (code === "ENOTFOUND") {
|
|
1596
|
+
return { name, ok: false, exists: false, detail: `${target.host} does not resolve` };
|
|
1597
|
+
}
|
|
1598
|
+
if (code === "CERT_HAS_EXPIRED" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE" || code === "DEPTH_ZERO_SELF_SIGNED_CERT") {
|
|
1599
|
+
return { name, ok: true, warn: true, exists: true, detail: `${target.host} is up but its TLS certificate is not trusted (${code})` };
|
|
1600
|
+
}
|
|
1601
|
+
return { name, ok: true, warn: true, exists: void 0, detail: `could not confirm ${target.host} (${code ?? attempt.message}) \u2014 sending anyway` };
|
|
1602
|
+
}
|
|
1603
|
+
|
|
991
1604
|
// src/core/validate.mjs
|
|
992
1605
|
function checkRoundTrip(body) {
|
|
993
1606
|
let reparsed;
|
|
@@ -1006,20 +1619,45 @@ function checkRoundTrip(body) {
|
|
|
1006
1619
|
detail: `re-serialized body differs at offset ${at.index}: sent ${JSON.stringify(at.a)} vs recomputed ${JSON.stringify(at.b)}`
|
|
1007
1620
|
};
|
|
1008
1621
|
}
|
|
1009
|
-
function checkGate(payload, provider) {
|
|
1010
|
-
const
|
|
1011
|
-
const
|
|
1622
|
+
function checkGate(payload, provider, eventName) {
|
|
1623
|
+
const gate = eventName && provider.gateFor ? provider.gateFor(eventName) : provider.gate;
|
|
1624
|
+
const actual = getPath(payload, gate.path);
|
|
1625
|
+
if (gate.nonEmpty) {
|
|
1626
|
+
const ok2 = Boolean(actual);
|
|
1627
|
+
return {
|
|
1628
|
+
name: "Schema gate",
|
|
1629
|
+
ok: ok2,
|
|
1630
|
+
detail: ok2 ? `${gate.path} is set` : `${gate.path} is empty \u2014 the schema filter requires a non-empty value, so the event would be silently ignored`
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
const ok = actual === gate.value;
|
|
1012
1634
|
return {
|
|
1013
1635
|
name: "Schema gate",
|
|
1014
1636
|
ok,
|
|
1015
|
-
detail: ok ? `${
|
|
1637
|
+
detail: ok ? `${gate.path} = "${actual}"` : `${gate.path} is ${JSON.stringify(actual)} but the schema filter requires "${gate.value}" \u2014 the event would be silently ignored`
|
|
1016
1638
|
};
|
|
1017
1639
|
}
|
|
1640
|
+
function locatePhone(payload) {
|
|
1641
|
+
const candidates = [
|
|
1642
|
+
// cashfree-occ
|
|
1643
|
+
["data.phone", "data.customer.shipping_address.country_code"],
|
|
1644
|
+
["data.customer.shipping_address.phone", "data.customer.shipping_address.country_code"],
|
|
1645
|
+
// razorpay-magic
|
|
1646
|
+
["phone", "customer.Shipping_address.Country_code"],
|
|
1647
|
+
["customer.Shipping_address.Phone", "customer.Shipping_address.Country_code"],
|
|
1648
|
+
// nitro
|
|
1649
|
+
["eventVal.customer.phone", "country"]
|
|
1650
|
+
];
|
|
1651
|
+
for (const [phonePath, regionPath] of candidates) {
|
|
1652
|
+
const phone = getPath(payload, phonePath);
|
|
1653
|
+
if (phone) return { phone, region: getPath(payload, regionPath) };
|
|
1654
|
+
}
|
|
1655
|
+
return { phone: void 0, region: void 0 };
|
|
1656
|
+
}
|
|
1018
1657
|
function checkPhone(payload) {
|
|
1019
|
-
const phone
|
|
1020
|
-
const region = getPath(payload, "data.customer.shipping_address.country_code");
|
|
1658
|
+
const { phone, region } = locatePhone(payload);
|
|
1021
1659
|
if (!phone) {
|
|
1022
|
-
return { name: "Phone", ok: false, detail: "no phone in
|
|
1660
|
+
return { name: "Phone", ok: false, detail: "no phone found in the payload \u2014 the consumer throws BadPayloadError" };
|
|
1023
1661
|
}
|
|
1024
1662
|
const parsed = toE164(phone, region);
|
|
1025
1663
|
return {
|
|
@@ -1033,8 +1671,8 @@ function checkAllowedPhone(payload, config) {
|
|
|
1033
1671
|
if (!allowed.length) {
|
|
1034
1672
|
return { name: "Phone allow-list", ok: true, detail: "no allow-list configured (skipped)", warn: true };
|
|
1035
1673
|
}
|
|
1036
|
-
const phone =
|
|
1037
|
-
const parsed = toE164(phone,
|
|
1674
|
+
const { phone, region } = locatePhone(payload);
|
|
1675
|
+
const parsed = toE164(phone, region);
|
|
1038
1676
|
const normalized = parsed.ok ? parsed.value : phone;
|
|
1039
1677
|
const ok = allowed.some((p) => toE164(p, "IN").value === normalized || p === normalized);
|
|
1040
1678
|
return {
|
|
@@ -1043,8 +1681,8 @@ function checkAllowedPhone(payload, config) {
|
|
|
1043
1681
|
detail: ok ? `${normalized} is an approved test handset` : `${normalized} is NOT in allowedPhones \u2014 refusing to risk messaging a real shopper`
|
|
1044
1682
|
};
|
|
1045
1683
|
}
|
|
1046
|
-
function coverage(payload,
|
|
1047
|
-
return
|
|
1684
|
+
function coverage(payload, fieldMap4) {
|
|
1685
|
+
return fieldMap4.map((f) => {
|
|
1048
1686
|
const value = getPath(payload, f.source);
|
|
1049
1687
|
const present = value !== void 0 && value !== null && value !== "";
|
|
1050
1688
|
return {
|
|
@@ -1058,10 +1696,10 @@ function coverage(payload, fieldMap2) {
|
|
|
1058
1696
|
});
|
|
1059
1697
|
}
|
|
1060
1698
|
async function checkImages(payload, { timeoutMs = 1e4 } = {}) {
|
|
1061
|
-
const items = getPath(payload, "data.line_items") ?? [];
|
|
1062
|
-
const urls = [...new Set(items.map((i) => i?.image_url).filter(Boolean))];
|
|
1699
|
+
const items = getPath(payload, "data.line_items") ?? getPath(payload, "line_items") ?? getPath(payload, "eventVal.line_items") ?? [];
|
|
1700
|
+
const urls = [...new Set(items.map((i) => i?.image_url ?? i?.image).filter(Boolean))];
|
|
1063
1701
|
if (!urls.length) {
|
|
1064
|
-
return [{ name: "Product images", ok:
|
|
1702
|
+
return [{ name: "Product images", ok: true, warn: true, detail: "no line item carries an image \u2014 cart.image will be undefined" }];
|
|
1065
1703
|
}
|
|
1066
1704
|
const results = await Promise.all(
|
|
1067
1705
|
urls.map(async (url) => {
|
|
@@ -1083,10 +1721,12 @@ async function checkImages(payload, { timeoutMs = 1e4 } = {}) {
|
|
|
1083
1721
|
detail: `${r.ok ? "HTTP " + r.status : "unreachable (" + (r.error ?? r.status) + ")"} \u2014 ${shorten(r.url)}`
|
|
1084
1722
|
}));
|
|
1085
1723
|
}
|
|
1086
|
-
async function runAll({ payload, body, provider, config, checkImageUrls = true }) {
|
|
1087
|
-
const checks = [checkGate(payload, provider), checkRoundTrip(body), checkPhone(payload), checkAllowedPhone(payload, config)];
|
|
1724
|
+
async function runAll({ payload, body, provider, config, checkImageUrls = true, checkEndpoint = true, eventName }) {
|
|
1725
|
+
const checks = [checkGate(payload, provider, eventName), checkRoundTrip(body), checkPhone(payload), checkAllowedPhone(payload, config)];
|
|
1088
1726
|
if (checkImageUrls) checks.push(...await checkImages(payload));
|
|
1089
|
-
|
|
1727
|
+
if (checkEndpoint) checks.push(await checkEndpointExists(provider.webhookUrl(config)));
|
|
1728
|
+
const fields = eventName && provider.fieldMapFor ? provider.fieldMapFor(eventName) : provider.fieldMap;
|
|
1729
|
+
const cov = coverage(payload, fields);
|
|
1090
1730
|
const missing = cov.filter((c2) => c2.status === "MISSING");
|
|
1091
1731
|
checks.push({
|
|
1092
1732
|
name: "Required field coverage",
|
|
@@ -1168,18 +1808,21 @@ function loadPayload(file) {
|
|
|
1168
1808
|
}
|
|
1169
1809
|
|
|
1170
1810
|
// src/core/build.mjs
|
|
1171
|
-
function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, phone }) {
|
|
1811
|
+
function draftPayload({ cfg, providerId = "cashfree-occ", items, discount = 0, phone, eventName }) {
|
|
1172
1812
|
const provider = getProvider(providerId);
|
|
1813
|
+
const event = eventName ?? provider.defaultEvent;
|
|
1173
1814
|
const resolvedPhone = phone ?? cfg.customer.phone;
|
|
1174
1815
|
const parsed = toE164(resolvedPhone, cfg.customer.countryCode);
|
|
1175
1816
|
const payload = provider.buildPayload({
|
|
1176
1817
|
config: cfg,
|
|
1177
1818
|
items,
|
|
1178
1819
|
discount,
|
|
1820
|
+
eventName: event,
|
|
1179
1821
|
phone: parsed.ok ? parsed.value : String(resolvedPhone ?? "")
|
|
1180
1822
|
});
|
|
1181
|
-
const
|
|
1182
|
-
|
|
1823
|
+
const editable = provider.editableFieldsFor ? provider.editableFieldsFor(event) : provider.editableFields;
|
|
1824
|
+
const fields = (editable ?? []).map((f) => ({ ...f, value: getPath(payload, f.path) }));
|
|
1825
|
+
return { provider, payload, fields, eventName: event };
|
|
1183
1826
|
}
|
|
1184
1827
|
function applyEdits(payload, fields, edits) {
|
|
1185
1828
|
for (const field of fields) {
|
|
@@ -1190,15 +1833,17 @@ function applyEdits(payload, fields, edits) {
|
|
|
1190
1833
|
}
|
|
1191
1834
|
return payload;
|
|
1192
1835
|
}
|
|
1193
|
-
async function
|
|
1836
|
+
async function buildPayload4({ cfg, providerId = "cashfree-occ", items, discount = 0, phone, checkImageUrls = true, payload: prebuilt, eventName }) {
|
|
1194
1837
|
if (!items?.length) throw new ConfigError("no products selected");
|
|
1195
1838
|
const provider = getProvider(providerId);
|
|
1196
1839
|
const resolvedPhone = phone ?? cfg.customer.phone;
|
|
1197
1840
|
const parsed = toE164(resolvedPhone, cfg.customer.countryCode);
|
|
1198
1841
|
if (!parsed.ok) throw new ConfigError(`phone is unusable: ${parsed.reason}`);
|
|
1199
|
-
const
|
|
1842
|
+
const event = eventName ?? provider.defaultEvent;
|
|
1843
|
+
const payload = prebuilt ?? provider.buildPayload({ config: cfg, items, discount, phone: parsed.value, eventName: event });
|
|
1200
1844
|
const body = JSON.stringify(payload);
|
|
1201
|
-
const report = await runAll({ payload, body, provider, config: cfg, checkImageUrls });
|
|
1845
|
+
const report = await runAll({ payload, body, provider, config: cfg, checkImageUrls, eventName: event });
|
|
1846
|
+
const summary = provider.summarize ? provider.summarize(payload) : {};
|
|
1202
1847
|
const endpoint = provider.webhookUrl(cfg);
|
|
1203
1848
|
let host = "";
|
|
1204
1849
|
try {
|
|
@@ -1208,17 +1853,18 @@ async function buildPayload2({ cfg, providerId = "cashfree-occ", items, discount
|
|
|
1208
1853
|
}
|
|
1209
1854
|
const meta = {
|
|
1210
1855
|
provider: provider.id,
|
|
1856
|
+
event,
|
|
1211
1857
|
endpoint,
|
|
1212
1858
|
environment: host,
|
|
1213
1859
|
store: cfg.shopify.domain,
|
|
1214
1860
|
items: items.map((i) => ({ title: i.product.title, variant: i.variant?.title, qty: i.quantity, price: i.variant?.price })),
|
|
1215
|
-
totalPrice:
|
|
1216
|
-
currency:
|
|
1861
|
+
totalPrice: summary.total,
|
|
1862
|
+
currency: summary.currency,
|
|
1217
1863
|
valid: report.ok
|
|
1218
1864
|
};
|
|
1219
1865
|
const file = savePayload({ provider: provider.id, payload, body, meta });
|
|
1220
1866
|
audit("payload.built", { provider: provider.id, valid: report.ok, file });
|
|
1221
|
-
return { provider, payload, body, report, file, meta };
|
|
1867
|
+
return { provider, payload, body, report, file, meta, summary };
|
|
1222
1868
|
}
|
|
1223
1869
|
|
|
1224
1870
|
// src/core/send.mjs
|
|
@@ -1230,18 +1876,32 @@ function looksLikeProduction(origin) {
|
|
|
1230
1876
|
}
|
|
1231
1877
|
function buildRequest({ provider, config, body, timestamp }) {
|
|
1232
1878
|
const url = provider.webhookUrl(config);
|
|
1879
|
+
if (!url) {
|
|
1880
|
+
const err = new Error(`no webhook URL configured for ${provider.label}`);
|
|
1881
|
+
err.hint = "set it in Setup \u2192 Webhook (or edit targets in config.json)";
|
|
1882
|
+
throw err;
|
|
1883
|
+
}
|
|
1233
1884
|
const secret = provider.webhookSecret ? provider.webhookSecret(config) : "";
|
|
1234
1885
|
const signed = provider.sign({ body, secret, timestamp });
|
|
1235
1886
|
return { url, headers: signed.headers, signature: signed.signature, timestamp: signed.timestamp, body };
|
|
1236
1887
|
}
|
|
1237
|
-
function
|
|
1238
|
-
|
|
1888
|
+
function shellQuote(value) {
|
|
1889
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
1890
|
+
}
|
|
1891
|
+
function toCurl({ url, headers, body, method = "POST" }) {
|
|
1892
|
+
const lines = [`curl --location --request ${method} ${shellQuote(url)} \\`];
|
|
1239
1893
|
for (const [k, v] of Object.entries(headers)) {
|
|
1240
|
-
lines.push(
|
|
1894
|
+
lines.push(`--header ${shellQuote(`${k}: ${v}`)} \\`);
|
|
1241
1895
|
}
|
|
1242
|
-
lines.push(
|
|
1896
|
+
lines.push(`--data ${shellQuote(body)}`);
|
|
1243
1897
|
return lines.join("\n");
|
|
1244
1898
|
}
|
|
1899
|
+
function writeCurlFile(command, name = "last-curl.sh") {
|
|
1900
|
+
fs4.mkdirSync(STATE_DIR, { recursive: true });
|
|
1901
|
+
const file = path4.join(STATE_DIR, name);
|
|
1902
|
+
fs4.writeFileSync(file, command + "\n");
|
|
1903
|
+
return file;
|
|
1904
|
+
}
|
|
1245
1905
|
function writeBodyFile(body, name = "last-body.json") {
|
|
1246
1906
|
fs4.mkdirSync(STATE_DIR, { recursive: true });
|
|
1247
1907
|
const file = path4.join(STATE_DIR, name);
|
|
@@ -1279,28 +1939,42 @@ async function send({ url, headers, body, timeoutMs = 2e4 }) {
|
|
|
1279
1939
|
}
|
|
1280
1940
|
|
|
1281
1941
|
// src/core/dispatch.mjs
|
|
1282
|
-
async function dispatch({ cfg, record, force = false, dryRun = false, revalidate = true }) {
|
|
1942
|
+
async function dispatch({ cfg, record, force = false, dryRun = false, revalidate = true, checkEndpoint = true }) {
|
|
1283
1943
|
const rec = record ?? loadLast();
|
|
1284
1944
|
if (!rec) throw new ForgeError("nothing to send", { hint: "build a payload first" });
|
|
1285
1945
|
const provider = getProvider(rec.provider);
|
|
1286
1946
|
let report = null;
|
|
1287
1947
|
if (revalidate) {
|
|
1288
|
-
report = await runAll({ payload: rec.payload, body: rec.body, provider, config: cfg, checkImageUrls: false });
|
|
1948
|
+
report = await runAll({ payload: rec.payload, body: rec.body, provider, config: cfg, checkImageUrls: false, eventName: rec.meta?.event });
|
|
1289
1949
|
if (!report.ok && !force) {
|
|
1290
|
-
|
|
1291
|
-
hint:
|
|
1950
|
+
const err = new ForgeError("refusing to send a payload that fails pre-flight", {
|
|
1951
|
+
hint: "fix the problems below, or pass --force to send regardless"
|
|
1292
1952
|
});
|
|
1953
|
+
err.failures = report.blocking;
|
|
1954
|
+
throw err;
|
|
1293
1955
|
}
|
|
1294
1956
|
}
|
|
1295
1957
|
const request = buildRequest({ provider, config: cfg, body: rec.body });
|
|
1296
1958
|
if (looksLikeProduction(request.url) && !force) {
|
|
1297
1959
|
throw new ForgeError(`target looks like production: ${request.url}`, { hint: "pass --force if you really mean it" });
|
|
1298
1960
|
}
|
|
1961
|
+
let endpoint = null;
|
|
1962
|
+
if (checkEndpoint && !dryRun) {
|
|
1963
|
+
endpoint = await checkEndpointExists(request.url);
|
|
1964
|
+
if (!endpoint.ok && !force) {
|
|
1965
|
+
const err = new ForgeError(`endpoint check failed: ${endpoint.detail}`, {
|
|
1966
|
+
hint: "fix the URL in Setup \u2192 Webhook, or pass --force to send regardless"
|
|
1967
|
+
});
|
|
1968
|
+
err.failures = [endpoint];
|
|
1969
|
+
throw err;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1299
1972
|
const bodyFile = writeBodyFile(rec.body);
|
|
1300
|
-
const curl = toCurl({ url: request.url, headers: request.headers,
|
|
1301
|
-
|
|
1973
|
+
const curl = toCurl({ url: request.url, headers: request.headers, body: rec.body });
|
|
1974
|
+
const curlFile = writeCurlFile(curl);
|
|
1975
|
+
if (dryRun) return { dryRun: true, request, bodyFile, curl, curlFile, report, provider, endpoint };
|
|
1302
1976
|
const result = await send({ url: request.url, headers: request.headers, body: rec.body });
|
|
1303
|
-
return { ...result, dryRun: false, request, bodyFile, curl, report, provider };
|
|
1977
|
+
return { ...result, dryRun: false, request, bodyFile, curl, curlFile, report, provider, endpoint };
|
|
1304
1978
|
}
|
|
1305
1979
|
function explainStatus(status) {
|
|
1306
1980
|
if (status >= 200 && status < 300) return "accepted \u2014 a 2xx only means it was received; check eventData to confirm the event landed";
|
|
@@ -1362,8 +2036,9 @@ async function copy(text, { allowOsc52 = true } = {}) {
|
|
|
1362
2036
|
}
|
|
1363
2037
|
|
|
1364
2038
|
// src/ui/screens/Build.jsx
|
|
1365
|
-
import { jsx as
|
|
2039
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1366
2040
|
var STEP = {
|
|
2041
|
+
EVENT: "event",
|
|
1367
2042
|
LOADING: "loading",
|
|
1368
2043
|
PICK: "pick",
|
|
1369
2044
|
VARIANT: "variant",
|
|
@@ -1380,7 +2055,10 @@ var STEP = {
|
|
|
1380
2055
|
};
|
|
1381
2056
|
function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
1382
2057
|
const { exit } = useApp();
|
|
1383
|
-
const
|
|
2058
|
+
const provider = getProvider(providerId);
|
|
2059
|
+
const multiEvent = Array.isArray(provider.events) && provider.events.length > 1;
|
|
2060
|
+
const [eventName, setEventName] = useState(provider.defaultEvent);
|
|
2061
|
+
const [step, setStep] = useState(multiEvent ? STEP.EVENT : STEP.LOADING);
|
|
1384
2062
|
const [products, setProducts] = useState([]);
|
|
1385
2063
|
const [chosen, setChosen] = useState([]);
|
|
1386
2064
|
const [cursor, setCursor] = useState(0);
|
|
@@ -1391,18 +2069,69 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1391
2069
|
const [edits, setEdits] = useState({});
|
|
1392
2070
|
const [built, setBuilt] = useState(null);
|
|
1393
2071
|
const [copied, setCopied] = useState(null);
|
|
1394
|
-
const [
|
|
2072
|
+
const [request, setRequest] = useState(null);
|
|
2073
|
+
const [curl, setCurl] = useState("");
|
|
2074
|
+
const [panel, setPanel] = useState(null);
|
|
1395
2075
|
const [sendResult, setSendResult] = useState(null);
|
|
1396
2076
|
const [error, setError] = useState(null);
|
|
2077
|
+
const goBack = () => {
|
|
2078
|
+
switch (step) {
|
|
2079
|
+
case STEP.EVENT:
|
|
2080
|
+
return onDone();
|
|
2081
|
+
case STEP.PICK:
|
|
2082
|
+
return multiEvent ? setStep(STEP.EVENT) : onDone();
|
|
2083
|
+
case STEP.VARIANT:
|
|
2084
|
+
if (cursor === 0) return setStep(STEP.PICK);
|
|
2085
|
+
setItems(items.slice(0, -1));
|
|
2086
|
+
return stepInto(cursor - 1);
|
|
2087
|
+
case STEP.QUANTITY:
|
|
2088
|
+
if (chosen[cursor]?.variants.length > 1) return setStep(STEP.VARIANT);
|
|
2089
|
+
if (cursor === 0) return setStep(STEP.PICK);
|
|
2090
|
+
setItems(items.slice(0, -1));
|
|
2091
|
+
return stepInto(cursor - 1);
|
|
2092
|
+
case STEP.DISCOUNT: {
|
|
2093
|
+
const last = chosen.length - 1;
|
|
2094
|
+
setItems(items.slice(0, -1));
|
|
2095
|
+
return stepInto(last);
|
|
2096
|
+
}
|
|
2097
|
+
case STEP.MODE:
|
|
2098
|
+
return setStep(STEP.DISCOUNT);
|
|
2099
|
+
case STEP.FIELDS:
|
|
2100
|
+
if (fieldIndex === 0) return setStep(STEP.MODE);
|
|
2101
|
+
return setFieldIndex(fieldIndex - 1);
|
|
2102
|
+
case STEP.REPORT:
|
|
2103
|
+
return setStep(STEP.MODE);
|
|
2104
|
+
case STEP.RESULT:
|
|
2105
|
+
case STEP.ERROR:
|
|
2106
|
+
return onDone();
|
|
2107
|
+
default:
|
|
2108
|
+
return onDone();
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2111
|
+
const stepInto = (index) => {
|
|
2112
|
+
setCursor(index);
|
|
2113
|
+
const product = chosen[index];
|
|
2114
|
+
if (!product) return setStep(STEP.PICK);
|
|
2115
|
+
if (product.variants.length > 1) return setStep(STEP.VARIANT);
|
|
2116
|
+
setPendingVariant(product.variants[0]);
|
|
2117
|
+
setStep(STEP.QUANTITY);
|
|
2118
|
+
};
|
|
1397
2119
|
useInput3((input, key) => {
|
|
1398
|
-
if (key.escape)
|
|
2120
|
+
if (key.escape) return goBack();
|
|
1399
2121
|
if ((step === STEP.RESULT || step === STEP.ERROR) && (key.return || input === "q")) onDone();
|
|
1400
|
-
if (step
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
2122
|
+
if (step !== STEP.REPORT) return;
|
|
2123
|
+
const toggle = (name) => setPanel((v) => v === name ? null : name);
|
|
2124
|
+
if (input === "p") toggle("payload");
|
|
2125
|
+
if (input === "f") toggle("fields");
|
|
2126
|
+
if (input === "v") toggle("checks");
|
|
2127
|
+
if (input === "r") toggle("request");
|
|
2128
|
+
if (input === "u") toggle("curl");
|
|
2129
|
+
if (input === "c" && curl) copy(curl).then((r) => setCopied({ ...r, what: "curl" }));
|
|
2130
|
+
if (input === "j" && built) copy(built.body).then((r) => setCopied({ ...r, what: "payload JSON" }));
|
|
2131
|
+
if ((input === "s" || key.return) && built?.report.ok) doSend();
|
|
1404
2132
|
});
|
|
1405
2133
|
useEffect(() => {
|
|
2134
|
+
if (step === STEP.EVENT) return;
|
|
1406
2135
|
let alive = true;
|
|
1407
2136
|
fetchCatalogue(config).then((list) => {
|
|
1408
2137
|
if (!alive) return;
|
|
@@ -1421,7 +2150,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1421
2150
|
return () => {
|
|
1422
2151
|
alive = false;
|
|
1423
2152
|
};
|
|
1424
|
-
}, []);
|
|
2153
|
+
}, [step === STEP.EVENT]);
|
|
1425
2154
|
const advance = (nextItems, nextCursor) => {
|
|
1426
2155
|
if (nextCursor >= chosen.length) {
|
|
1427
2156
|
setItems(nextItems);
|
|
@@ -1465,7 +2194,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1465
2194
|
const onDiscount = (raw) => {
|
|
1466
2195
|
const discount = Math.max(0, Number.parseFloat(raw) || 0);
|
|
1467
2196
|
try {
|
|
1468
|
-
const next = draftPayload({ cfg: config, providerId, items, discount });
|
|
2197
|
+
const next = draftPayload({ cfg: config, providerId, items, discount, eventName });
|
|
1469
2198
|
setDraft({ ...next, discount });
|
|
1470
2199
|
setStep(STEP.MODE);
|
|
1471
2200
|
} catch (err) {
|
|
@@ -1475,10 +2204,20 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1475
2204
|
};
|
|
1476
2205
|
const finalise = (payload) => {
|
|
1477
2206
|
setStep(STEP.BUILDING);
|
|
1478
|
-
|
|
2207
|
+
buildPayload4({ cfg: config, providerId, items, discount: draft.discount, payload, eventName }).then(async (result) => {
|
|
1479
2208
|
setBuilt(result);
|
|
1480
|
-
|
|
1481
|
-
|
|
2209
|
+
let req = null;
|
|
2210
|
+
let command = "";
|
|
2211
|
+
try {
|
|
2212
|
+
req = buildRequest({ provider: result.provider, config, body: result.body });
|
|
2213
|
+
command = toCurl({ url: req.url, headers: req.headers, body: result.body });
|
|
2214
|
+
} catch {
|
|
2215
|
+
req = null;
|
|
2216
|
+
}
|
|
2217
|
+
setRequest(req);
|
|
2218
|
+
setCurl(command);
|
|
2219
|
+
if (config.defaults.copyToClipboard !== false && command) {
|
|
2220
|
+
setCopied({ ...await copy(command), what: "curl" });
|
|
1482
2221
|
}
|
|
1483
2222
|
setStep(STEP.REPORT);
|
|
1484
2223
|
}).catch((err) => {
|
|
@@ -1514,22 +2253,37 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1514
2253
|
};
|
|
1515
2254
|
const store = config.shopify.domain;
|
|
1516
2255
|
const currency = config.defaults.currency;
|
|
1517
|
-
|
|
1518
|
-
|
|
2256
|
+
const coverageMissing = built ? built.report.coverage.filter((c2) => c2.status === "MISSING").length : 0;
|
|
2257
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2258
|
+
/* @__PURE__ */ jsx8(
|
|
1519
2259
|
Header,
|
|
1520
2260
|
{
|
|
1521
2261
|
title: "Build payload",
|
|
1522
|
-
subtitle: `${metaFor(providerId).label} \xB7 abandoned checkout`,
|
|
2262
|
+
subtitle: `${metaFor(providerId).label} \xB7 ${multiEvent ? eventName : "abandoned checkout"}`,
|
|
1523
2263
|
right: store
|
|
1524
2264
|
}
|
|
1525
2265
|
),
|
|
1526
|
-
step === STEP.
|
|
1527
|
-
|
|
1528
|
-
/* @__PURE__ */
|
|
2266
|
+
step === STEP.EVENT && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2267
|
+
/* @__PURE__ */ jsx8(Text8, { children: "Which event do you want to send?" }),
|
|
2268
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(
|
|
2269
|
+
Select4,
|
|
2270
|
+
{
|
|
2271
|
+
visibleOptionCount: provider.events.length,
|
|
2272
|
+
options: provider.events.map((e) => ({ label: `${e.label.padEnd(20)} ${e.eventName}`, value: e.eventName })),
|
|
2273
|
+
onChange: (value) => {
|
|
2274
|
+
setEventName(value);
|
|
2275
|
+
setStep(STEP.LOADING);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
) })
|
|
2279
|
+
] }),
|
|
2280
|
+
step === STEP.LOADING && /* @__PURE__ */ jsx8(Spinner, { label: `fetching live products from ${store}\u2026` }),
|
|
2281
|
+
step === STEP.PICK && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2282
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1529
2283
|
`Select the products in the abandoned cart `,
|
|
1530
|
-
/* @__PURE__ */
|
|
2284
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "(space toggles \xB7 enter confirms)" })
|
|
1531
2285
|
] }),
|
|
1532
|
-
/* @__PURE__ */
|
|
2286
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(
|
|
1533
2287
|
MultiSelect,
|
|
1534
2288
|
{
|
|
1535
2289
|
visibleOptionCount: 10,
|
|
@@ -1541,12 +2295,12 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1541
2295
|
}
|
|
1542
2296
|
) })
|
|
1543
2297
|
] }),
|
|
1544
|
-
step === STEP.VARIANT && /* @__PURE__ */
|
|
1545
|
-
/* @__PURE__ */
|
|
2298
|
+
step === STEP.VARIANT && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2299
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1546
2300
|
`Variant for `,
|
|
1547
|
-
/* @__PURE__ */
|
|
2301
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: palette.accent, children: chosen[cursor]?.title })
|
|
1548
2302
|
] }),
|
|
1549
|
-
/* @__PURE__ */
|
|
2303
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(
|
|
1550
2304
|
Select4,
|
|
1551
2305
|
{
|
|
1552
2306
|
visibleOptionCount: 8,
|
|
@@ -1559,33 +2313,33 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1559
2313
|
`variant-${cursor}`
|
|
1560
2314
|
) })
|
|
1561
2315
|
] }),
|
|
1562
|
-
step === STEP.QUANTITY && /* @__PURE__ */
|
|
1563
|
-
/* @__PURE__ */
|
|
2316
|
+
step === STEP.QUANTITY && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2317
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1564
2318
|
`Quantity for `,
|
|
1565
|
-
/* @__PURE__ */
|
|
1566
|
-
/* @__PURE__ */
|
|
2319
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: palette.accent, children: chosen[cursor]?.title }),
|
|
2320
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: pendingVariant?.title ? ` \xB7 ${pendingVariant.title}` : "" })
|
|
1567
2321
|
] }),
|
|
1568
|
-
/* @__PURE__ */
|
|
1569
|
-
/* @__PURE__ */
|
|
1570
|
-
/* @__PURE__ */
|
|
2322
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
2323
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.accent, children: "\u276F " }),
|
|
2324
|
+
/* @__PURE__ */ jsx8(TextInput, { defaultValue: "1", placeholder: "1", onSubmit: onQuantity }, `qty-${cursor}`)
|
|
1571
2325
|
] })
|
|
1572
2326
|
] }),
|
|
1573
|
-
step === STEP.DISCOUNT && /* @__PURE__ */
|
|
1574
|
-
/* @__PURE__ */
|
|
2327
|
+
step === STEP.DISCOUNT && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2328
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
1575
2329
|
`Cart discount in ${currency} `,
|
|
1576
|
-
/* @__PURE__ */
|
|
2330
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "(0 for none)" })
|
|
1577
2331
|
] }),
|
|
1578
|
-
/* @__PURE__ */
|
|
1579
|
-
/* @__PURE__ */
|
|
1580
|
-
/* @__PURE__ */
|
|
2332
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
2333
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.accent, children: "\u276F " }),
|
|
2334
|
+
/* @__PURE__ */ jsx8(TextInput, { defaultValue: "0", placeholder: "0", onSubmit: onDiscount })
|
|
1581
2335
|
] })
|
|
1582
2336
|
] }),
|
|
1583
|
-
step === STEP.MODE && draft && /* @__PURE__ */
|
|
1584
|
-
/* @__PURE__ */
|
|
1585
|
-
/* @__PURE__ */
|
|
1586
|
-
/* @__PURE__ */
|
|
2337
|
+
step === STEP.MODE && draft && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2338
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: palette.accent, paddingX: 1, marginBottom: 1, children: [
|
|
2339
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: `${draft.fields.length} fields have been filled in from Shopify and your profile.` }),
|
|
2340
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "Review them one by one, or accept the suggestions as they are." })
|
|
1587
2341
|
] }),
|
|
1588
|
-
/* @__PURE__ */
|
|
2342
|
+
/* @__PURE__ */ jsx8(
|
|
1589
2343
|
Select4,
|
|
1590
2344
|
{
|
|
1591
2345
|
options: [
|
|
@@ -1596,7 +2350,7 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1596
2350
|
}
|
|
1597
2351
|
)
|
|
1598
2352
|
] }),
|
|
1599
|
-
step === STEP.FIELDS && draft && /* @__PURE__ */
|
|
2353
|
+
step === STEP.FIELDS && draft && /* @__PURE__ */ jsx8(
|
|
1600
2354
|
FieldPrompt,
|
|
1601
2355
|
{
|
|
1602
2356
|
field: draft.fields[fieldIndex],
|
|
@@ -1606,37 +2360,79 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1606
2360
|
onSubmit: onFieldSubmit
|
|
1607
2361
|
}
|
|
1608
2362
|
),
|
|
1609
|
-
step === STEP.BUILDING && /* @__PURE__ */
|
|
1610
|
-
(step === STEP.REPORT || step === STEP.CONFIRM) && built && /* @__PURE__ */
|
|
1611
|
-
/* @__PURE__ */
|
|
1612
|
-
/* @__PURE__ */
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
2363
|
+
step === STEP.BUILDING && /* @__PURE__ */ jsx8(Spinner, { label: "assembling payload, verifying images and signature fidelity\u2026" }),
|
|
2364
|
+
(step === STEP.REPORT || step === STEP.CONFIRM) && built && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2365
|
+
/* @__PURE__ */ jsx8(Summary, { built, currency }),
|
|
2366
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
2367
|
+
/* @__PURE__ */ jsx8(
|
|
2368
|
+
StatusRow,
|
|
2369
|
+
{
|
|
2370
|
+
label: "Pre-flight",
|
|
2371
|
+
ok: built.report.ok,
|
|
2372
|
+
value: built.report.ok ? `${built.report.checks.length} checks passed` : `${built.report.blocking.length} blocking`,
|
|
2373
|
+
detail: built.report.ok ? "" : built.report.blocking.map((b) => b.name).join(", ")
|
|
2374
|
+
}
|
|
2375
|
+
),
|
|
2376
|
+
/* @__PURE__ */ jsx8(
|
|
2377
|
+
StatusRow,
|
|
2378
|
+
{
|
|
2379
|
+
label: "Coverage",
|
|
2380
|
+
ok: coverageMissing === 0,
|
|
2381
|
+
value: `${built.report.coverage.length - coverageMissing} of ${built.report.coverage.length} fields resolved`,
|
|
2382
|
+
detail: coverageMissing ? "missing fields render blank in templates" : ""
|
|
2383
|
+
}
|
|
2384
|
+
),
|
|
2385
|
+
/* @__PURE__ */ jsx8(
|
|
2386
|
+
StatusRow,
|
|
2387
|
+
{
|
|
2388
|
+
label: "Request",
|
|
2389
|
+
ok: Boolean(request),
|
|
2390
|
+
value: request ? `POST \xB7 ${built.body.length} bytes` : "no destination configured",
|
|
2391
|
+
detail: request ? truncate(request.url, 44) : "Setup \u2192 Webhook"
|
|
2392
|
+
}
|
|
2393
|
+
),
|
|
2394
|
+
/* @__PURE__ */ jsx8(
|
|
2395
|
+
StatusRow,
|
|
1623
2396
|
{
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
2397
|
+
label: "Clipboard",
|
|
2398
|
+
ok: copied?.ok,
|
|
2399
|
+
warn: !copied?.ok,
|
|
2400
|
+
value: copied?.ok ? `${copied.what} copied` : "not copied",
|
|
2401
|
+
detail: copied?.ok ? `via ${copied.via}` : "press c"
|
|
1627
2402
|
}
|
|
1628
2403
|
)
|
|
1629
2404
|
] }),
|
|
1630
|
-
|
|
2405
|
+
panel === "checks" && /* @__PURE__ */ jsx8(Panel, { title: "Pre-flight checks", children: /* @__PURE__ */ jsx8(CheckList, { checks: built.report.checks }) }),
|
|
2406
|
+
panel === "fields" && /* @__PURE__ */ jsx8(Panel, { title: "Field coverage", hint: "what the receiver will read", children: /* @__PURE__ */ jsx8(FieldTable, { coverage: built.report.coverage }) }),
|
|
2407
|
+
panel === "request" && /* @__PURE__ */ jsx8(Panel, { title: "Request", children: /* @__PURE__ */ jsx8(RequestPreview, { request, bytes: built.body.length }) }),
|
|
2408
|
+
panel === "curl" && curl && /* @__PURE__ */ jsx8(Panel, { title: "curl", hint: "Postman \u2192 Import \u2192 Raw text", children: /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: curl }) }),
|
|
2409
|
+
panel === "payload" && /* @__PURE__ */ jsx8(Panel, { title: "Payload", hint: `${built.body.length} bytes`, children: /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: JSON.stringify(built.payload, null, 2) }) }),
|
|
2410
|
+
!built.report.ok && /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Alert, { variant: "error", children: `cannot send \u2014 ${built.report.blocking.map((b) => b.detail).join("; ")}` }) }),
|
|
2411
|
+
/* @__PURE__ */ jsx8(
|
|
2412
|
+
CommandBar,
|
|
2413
|
+
{
|
|
2414
|
+
commands: [
|
|
2415
|
+
{ key: "s", label: "send", disabled: !built.report.ok },
|
|
2416
|
+
{ key: "c", label: "copy curl" },
|
|
2417
|
+
{ key: "j", label: "copy json" },
|
|
2418
|
+
{ key: "u", label: "curl", active: panel === "curl" },
|
|
2419
|
+
{ key: "p", label: "payload", active: panel === "payload" },
|
|
2420
|
+
{ key: "f", label: "fields", active: panel === "fields" },
|
|
2421
|
+
{ key: "v", label: "checks", active: panel === "checks" },
|
|
2422
|
+
{ key: "r", label: "request", active: panel === "request" },
|
|
2423
|
+
{ key: "esc", label: "back" }
|
|
2424
|
+
]
|
|
2425
|
+
}
|
|
2426
|
+
)
|
|
1631
2427
|
] }),
|
|
1632
|
-
step === STEP.SENDING && /* @__PURE__ */
|
|
1633
|
-
step === STEP.RESULT && sendResult && /* @__PURE__ */
|
|
1634
|
-
/* @__PURE__ */
|
|
1635
|
-
sendResult.ok ? /* @__PURE__ */
|
|
1636
|
-
/* @__PURE__ */
|
|
1637
|
-
/* @__PURE__ */
|
|
2428
|
+
step === STEP.SENDING && /* @__PURE__ */ jsx8(Spinner, { label: "signing and POSTing the webhook\u2026" }),
|
|
2429
|
+
step === STEP.RESULT && sendResult && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2430
|
+
/* @__PURE__ */ jsxs8(Box8, { marginBottom: 1, children: [
|
|
2431
|
+
sendResult.ok ? /* @__PURE__ */ jsx8(Badge, { color: "green", children: `HTTP ${sendResult.status}` }) : /* @__PURE__ */ jsx8(Badge, { color: "red", children: sendResult.error ? "FAILED" : `HTTP ${sendResult.status}` }),
|
|
2432
|
+
/* @__PURE__ */ jsx8(Text8, { children: " " }),
|
|
2433
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: sendResult.error ?? explainStatus(sendResult.status) })
|
|
1638
2434
|
] }),
|
|
1639
|
-
/* @__PURE__ */
|
|
2435
|
+
/* @__PURE__ */ jsx8(
|
|
1640
2436
|
KeyValue,
|
|
1641
2437
|
{
|
|
1642
2438
|
rows: [
|
|
@@ -1649,28 +2445,22 @@ function Build({ config, providerId = "cashfree-occ", onDone }) {
|
|
|
1649
2445
|
]
|
|
1650
2446
|
}
|
|
1651
2447
|
),
|
|
1652
|
-
/* @__PURE__ */
|
|
1653
|
-
/* @__PURE__ */
|
|
1654
|
-
/* @__PURE__ */
|
|
2448
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2449
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "reproduce:" }),
|
|
2450
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: sendResult.curl })
|
|
1655
2451
|
] }),
|
|
1656
|
-
/* @__PURE__ */
|
|
2452
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "press enter to return to the menu" }) })
|
|
1657
2453
|
] }),
|
|
1658
|
-
step === STEP.ERROR && error && /* @__PURE__ */
|
|
1659
|
-
/* @__PURE__ */
|
|
1660
|
-
error.hint ? /* @__PURE__ */
|
|
1661
|
-
/* @__PURE__ */
|
|
2454
|
+
step === STEP.ERROR && error && /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2455
|
+
/* @__PURE__ */ jsx8(Alert, { variant: "error", children: error.message }),
|
|
2456
|
+
error.hint ? /* @__PURE__ */ jsx8(Text8, { color: palette.warn, children: ` ${error.hint}` }) : null,
|
|
2457
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "press enter to return to the menu" }) })
|
|
1662
2458
|
] })
|
|
1663
2459
|
] });
|
|
1664
2460
|
}
|
|
1665
|
-
function RequestPreview({
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
request = buildRequest({ provider: built.provider, config, body: built.body });
|
|
1669
|
-
} catch {
|
|
1670
|
-
request = null;
|
|
1671
|
-
}
|
|
1672
|
-
if (!request) return /* @__PURE__ */ jsx7(Text7, { color: palette.dim, children: " destination not configured" });
|
|
1673
|
-
return /* @__PURE__ */ jsx7(
|
|
2461
|
+
function RequestPreview({ request, bytes: bytes2 }) {
|
|
2462
|
+
if (!request) return /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: " destination not configured" });
|
|
2463
|
+
return /* @__PURE__ */ jsx8(
|
|
1674
2464
|
KeyValue,
|
|
1675
2465
|
{
|
|
1676
2466
|
keyWidth: 22,
|
|
@@ -1678,27 +2468,27 @@ function RequestPreview({ config, built }) {
|
|
|
1678
2468
|
["POST", truncate(request.url, 56)],
|
|
1679
2469
|
["x-webhook-timestamp", request.timestamp],
|
|
1680
2470
|
["x-webhook-signature", truncate(request.signature, 46)],
|
|
1681
|
-
["body", `${
|
|
2471
|
+
["body", `${bytes2} bytes`]
|
|
1682
2472
|
]
|
|
1683
2473
|
}
|
|
1684
2474
|
);
|
|
1685
2475
|
}
|
|
1686
2476
|
function FieldPrompt({ field, index, total, value, onSubmit }) {
|
|
1687
2477
|
const filled = Math.round(index / total * 30);
|
|
1688
|
-
return /* @__PURE__ */
|
|
1689
|
-
/* @__PURE__ */
|
|
1690
|
-
/* @__PURE__ */
|
|
1691
|
-
/* @__PURE__ */
|
|
1692
|
-
/* @__PURE__ */
|
|
2478
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
2479
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
2480
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.accent, children: "\u2588".repeat(filled) }),
|
|
2481
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "\u2591".repeat(30 - filled) }),
|
|
2482
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: ` ${index + 1}/${total} ${field.group}` })
|
|
1693
2483
|
] }),
|
|
1694
|
-
/* @__PURE__ */
|
|
1695
|
-
/* @__PURE__ */
|
|
1696
|
-
field.hint ? /* @__PURE__ */
|
|
1697
|
-
field.optional ? /* @__PURE__ */
|
|
2484
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
|
|
2485
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: palette.heading, children: field.label }),
|
|
2486
|
+
field.hint ? /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: ` ${field.hint}` }) : null,
|
|
2487
|
+
field.optional ? /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: " optional" }) : null
|
|
1698
2488
|
] }),
|
|
1699
|
-
/* @__PURE__ */
|
|
1700
|
-
/* @__PURE__ */
|
|
1701
|
-
/* @__PURE__ */
|
|
2489
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
2490
|
+
/* @__PURE__ */ jsx8(Text8, { color: palette.accent, children: "\u276F " }),
|
|
2491
|
+
/* @__PURE__ */ jsx8(
|
|
1702
2492
|
TextInput,
|
|
1703
2493
|
{
|
|
1704
2494
|
defaultValue: value == null ? "" : String(value),
|
|
@@ -1708,32 +2498,29 @@ function FieldPrompt({ field, index, total, value, onSubmit }) {
|
|
|
1708
2498
|
field.path
|
|
1709
2499
|
)
|
|
1710
2500
|
] }),
|
|
1711
|
-
/* @__PURE__ */
|
|
2501
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { color: palette.dim, children: "enter accepts the suggestion \xB7 edit the text to change it \xB7 esc goes back a field" }) })
|
|
1712
2502
|
] });
|
|
1713
2503
|
}
|
|
1714
2504
|
function Summary({ built, currency }) {
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
]
|
|
1728
|
-
}
|
|
1729
|
-
) });
|
|
2505
|
+
const s = built.summary ?? (built.provider.summarize ? built.provider.summarize(built.payload) : {});
|
|
2506
|
+
const rows = [
|
|
2507
|
+
["provider", built.provider.label],
|
|
2508
|
+
["event", s.event ?? "\u2014"],
|
|
2509
|
+
["store", s.store ?? "\u2014"],
|
|
2510
|
+
["customer", s.customer || "\u2014"],
|
|
2511
|
+
["items", s.items || "\u2014"],
|
|
2512
|
+
["total", s.total == null ? "\u2014" : `${money(s.total, s.currency ?? currency)}${s.extra ? ` (${s.extra})` : ""}`]
|
|
2513
|
+
];
|
|
2514
|
+
if (s.link) rows.push(["link", truncate(s.link, 58)]);
|
|
2515
|
+
rows.push(["saved", built.file]);
|
|
2516
|
+
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: palette.ok, paddingX: 1, children: /* @__PURE__ */ jsx8(KeyValue, { rows }) });
|
|
1730
2517
|
}
|
|
1731
2518
|
|
|
1732
2519
|
// src/ui/screens/Configure.jsx
|
|
1733
|
-
import
|
|
1734
|
-
import { Box as
|
|
2520
|
+
import React9, { useState as useState2 } from "react";
|
|
2521
|
+
import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
|
|
1735
2522
|
import { Spinner as Spinner2, TextInput as TextInput2, PasswordInput, Select as Select5, Alert as Alert2 } from "@inkjs/ui";
|
|
1736
|
-
import { jsx as
|
|
2523
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1737
2524
|
var FIELDS = [
|
|
1738
2525
|
{ group: "Customer", key: "customer.firstName", label: "First name", placeholder: "Test" },
|
|
1739
2526
|
{ group: "Customer", key: "customer.lastName", label: "Last name", placeholder: "Shopper" },
|
|
@@ -1808,9 +2595,9 @@ function Configure({ config, section: initialSection, onDone }) {
|
|
|
1808
2595
|
});
|
|
1809
2596
|
};
|
|
1810
2597
|
if (!section) {
|
|
1811
|
-
return /* @__PURE__ */
|
|
1812
|
-
/* @__PURE__ */
|
|
1813
|
-
/* @__PURE__ */
|
|
2598
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2599
|
+
/* @__PURE__ */ jsx9(Header, { title: "Configure", subtitle: "pick what you want to change", right: "esc to go back" }),
|
|
2600
|
+
/* @__PURE__ */ jsx9(
|
|
1814
2601
|
Select5,
|
|
1815
2602
|
{
|
|
1816
2603
|
visibleOptionCount: 8,
|
|
@@ -1827,29 +2614,29 @@ function Configure({ config, section: initialSection, onDone }) {
|
|
|
1827
2614
|
] });
|
|
1828
2615
|
}
|
|
1829
2616
|
if (phase === "verifying") {
|
|
1830
|
-
return /* @__PURE__ */
|
|
1831
|
-
/* @__PURE__ */
|
|
1832
|
-
/* @__PURE__ */
|
|
2617
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2618
|
+
/* @__PURE__ */ jsx9(Header, { title: "Configure", subtitle: "verifying credentials" }),
|
|
2619
|
+
/* @__PURE__ */ jsx9(Spinner2, { label: "contacting Shopify\u2026" })
|
|
1833
2620
|
] });
|
|
1834
2621
|
}
|
|
1835
2622
|
if (phase === "done") {
|
|
1836
|
-
return /* @__PURE__ */
|
|
1837
|
-
/* @__PURE__ */
|
|
1838
|
-
/* @__PURE__ */
|
|
1839
|
-
verifyMsg?.hint ? /* @__PURE__ */
|
|
1840
|
-
/* @__PURE__ */
|
|
1841
|
-
/* @__PURE__ */
|
|
2623
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2624
|
+
/* @__PURE__ */ jsx9(Header, { title: "Configure", subtitle: "saved" }),
|
|
2625
|
+
/* @__PURE__ */ jsx9(Alert2, { variant: verifyMsg?.ok ? "success" : "warning", children: verifyMsg?.text ?? "saved" }),
|
|
2626
|
+
verifyMsg?.hint ? /* @__PURE__ */ jsx9(Text9, { color: palette.warn, children: ` ${verifyMsg.hint}` }) : null,
|
|
2627
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: palette.dim, children: `config written to ${verifyMsg?.file} (chmod 600)` }) }),
|
|
2628
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { color: palette.dim, children: "press enter to return to the menu" }) })
|
|
1842
2629
|
] });
|
|
1843
2630
|
}
|
|
1844
2631
|
const current = get(draft, field.key) ?? "";
|
|
1845
|
-
return /* @__PURE__ */
|
|
1846
|
-
/* @__PURE__ */
|
|
1847
|
-
/* @__PURE__ */
|
|
1848
|
-
/* @__PURE__ */
|
|
1849
|
-
field.hint ? /* @__PURE__ */
|
|
1850
|
-
/* @__PURE__ */
|
|
1851
|
-
/* @__PURE__ */
|
|
1852
|
-
field.type === "select" ? /* @__PURE__ */
|
|
2632
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
2633
|
+
/* @__PURE__ */ jsx9(Header, { title: "Configure", subtitle: `${field.group} \xB7 step ${index + 1} of ${fields.length}`, right: "esc to cancel" }),
|
|
2634
|
+
/* @__PURE__ */ jsx9(Progress, { index, total: fields.length }),
|
|
2635
|
+
/* @__PURE__ */ jsxs9(Section, { title: field.label, children: [
|
|
2636
|
+
field.hint ? /* @__PURE__ */ jsx9(Text9, { color: palette.dim, children: ` ${field.hint}` }) : null,
|
|
2637
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, children: [
|
|
2638
|
+
/* @__PURE__ */ jsx9(Text9, { color: palette.accent, children: `${glyph.pointer} ` }),
|
|
2639
|
+
field.type === "select" ? /* @__PURE__ */ jsx9(
|
|
1853
2640
|
Select5,
|
|
1854
2641
|
{
|
|
1855
2642
|
options: field.options,
|
|
@@ -1857,7 +2644,7 @@ function Configure({ config, section: initialSection, onDone }) {
|
|
|
1857
2644
|
onChange: commit
|
|
1858
2645
|
},
|
|
1859
2646
|
field.key
|
|
1860
|
-
) : field.secret ? /* @__PURE__ */
|
|
2647
|
+
) : field.secret ? /* @__PURE__ */ jsx9(PasswordInput, { placeholder: current ? mask(current) : "\u2022\u2022\u2022\u2022\u2022\u2022", onSubmit: (v) => commit(v || current) }, field.key) : /* @__PURE__ */ jsx9(
|
|
1861
2648
|
TextInput2,
|
|
1862
2649
|
{
|
|
1863
2650
|
defaultValue: current,
|
|
@@ -1872,18 +2659,18 @@ function Configure({ config, section: initialSection, onDone }) {
|
|
|
1872
2659
|
}
|
|
1873
2660
|
function Progress({ index, total }) {
|
|
1874
2661
|
const filled = Math.round(index / total * 30);
|
|
1875
|
-
return /* @__PURE__ */
|
|
1876
|
-
/* @__PURE__ */
|
|
1877
|
-
/* @__PURE__ */
|
|
1878
|
-
/* @__PURE__ */
|
|
2662
|
+
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
2663
|
+
/* @__PURE__ */ jsx9(Text9, { color: palette.accent, children: "\u2588".repeat(filled) }),
|
|
2664
|
+
/* @__PURE__ */ jsx9(Text9, { color: palette.dim, children: "\u2591".repeat(30 - filled) }),
|
|
2665
|
+
/* @__PURE__ */ jsx9(Text9, { color: palette.dim, children: ` ${index}/${total}` })
|
|
1879
2666
|
] });
|
|
1880
2667
|
}
|
|
1881
2668
|
|
|
1882
2669
|
// src/ui/screens/ConnectShopify.jsx
|
|
1883
|
-
import
|
|
1884
|
-
import { Box as
|
|
2670
|
+
import React10, { useState as useState3 } from "react";
|
|
2671
|
+
import { Box as Box10, Text as Text10, useInput as useInput5 } from "ink";
|
|
1885
2672
|
import { Spinner as Spinner3, TextInput as TextInput3, PasswordInput as PasswordInput2, Alert as Alert3, Badge as Badge2 } from "@inkjs/ui";
|
|
1886
|
-
import { jsx as
|
|
2673
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1887
2674
|
var STEP2 = { DOMAIN: "domain", TOKEN: "token", VERIFY: "verify", DONE: "done", FAILED: "failed" };
|
|
1888
2675
|
function ConnectShopify({ config, onDone }) {
|
|
1889
2676
|
const [draft] = useState3(() => structuredClone(config));
|
|
@@ -1914,13 +2701,13 @@ function ConnectShopify({ config, onDone }) {
|
|
|
1914
2701
|
setStep(STEP2.FAILED);
|
|
1915
2702
|
});
|
|
1916
2703
|
};
|
|
1917
|
-
return /* @__PURE__ */
|
|
1918
|
-
/* @__PURE__ */
|
|
1919
|
-
step === STEP2.DOMAIN && /* @__PURE__ */
|
|
1920
|
-
/* @__PURE__ */
|
|
1921
|
-
/* @__PURE__ */
|
|
1922
|
-
/* @__PURE__ */
|
|
1923
|
-
/* @__PURE__ */
|
|
2704
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2705
|
+
/* @__PURE__ */ jsx10(Header, { title: "Connect Shopify", subtitle: "the catalogue every payload is built from", right: "esc to cancel" }),
|
|
2706
|
+
step === STEP2.DOMAIN && /* @__PURE__ */ jsxs10(Section, { title: "Store domain", children: [
|
|
2707
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " the myshopify.com domain, not your custom domain" }),
|
|
2708
|
+
/* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
|
|
2709
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.accent, children: `${glyph.pointer} ` }),
|
|
2710
|
+
/* @__PURE__ */ jsx10(
|
|
1924
2711
|
TextInput3,
|
|
1925
2712
|
{
|
|
1926
2713
|
defaultValue: draft.shopify.domain,
|
|
@@ -1930,12 +2717,12 @@ function ConnectShopify({ config, onDone }) {
|
|
|
1930
2717
|
)
|
|
1931
2718
|
] })
|
|
1932
2719
|
] }),
|
|
1933
|
-
step === STEP2.TOKEN && /* @__PURE__ */
|
|
1934
|
-
/* @__PURE__ */
|
|
1935
|
-
/* @__PURE__ */
|
|
1936
|
-
/* @__PURE__ */
|
|
1937
|
-
/* @__PURE__ */
|
|
1938
|
-
/* @__PURE__ */
|
|
2720
|
+
step === STEP2.TOKEN && /* @__PURE__ */ jsxs10(Section, { title: "Admin API access token", children: [
|
|
2721
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " Shopify admin \u2192 Settings \u2192 Apps and sales channels \u2192 Develop apps" }),
|
|
2722
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " the token needs the read_products scope" }),
|
|
2723
|
+
/* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
|
|
2724
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.accent, children: `${glyph.pointer} ` }),
|
|
2725
|
+
/* @__PURE__ */ jsx10(
|
|
1939
2726
|
PasswordInput2,
|
|
1940
2727
|
{
|
|
1941
2728
|
placeholder: draft.shopify.accessToken ? mask(draft.shopify.accessToken) : "shpat_\u2026",
|
|
@@ -1944,14 +2731,14 @@ function ConnectShopify({ config, onDone }) {
|
|
|
1944
2731
|
)
|
|
1945
2732
|
] })
|
|
1946
2733
|
] }),
|
|
1947
|
-
step === STEP2.VERIFY && /* @__PURE__ */
|
|
1948
|
-
step === STEP2.DONE && result && /* @__PURE__ */
|
|
1949
|
-
/* @__PURE__ */
|
|
1950
|
-
/* @__PURE__ */
|
|
1951
|
-
/* @__PURE__ */
|
|
1952
|
-
/* @__PURE__ */
|
|
2734
|
+
step === STEP2.VERIFY && /* @__PURE__ */ jsx10(Spinner3, { label: `connecting to ${draft.shopify.domain}\u2026` }),
|
|
2735
|
+
step === STEP2.DONE && result && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2736
|
+
/* @__PURE__ */ jsxs10(Box10, { marginBottom: 1, children: [
|
|
2737
|
+
/* @__PURE__ */ jsx10(Badge2, { color: "green", children: "CONNECTED" }),
|
|
2738
|
+
/* @__PURE__ */ jsx10(Text10, { children: " " }),
|
|
2739
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, children: result.shop?.name })
|
|
1953
2740
|
] }),
|
|
1954
|
-
/* @__PURE__ */
|
|
2741
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: palette.ok, paddingX: 1, children: /* @__PURE__ */ jsx10(
|
|
1955
2742
|
KeyValue,
|
|
1956
2743
|
{
|
|
1957
2744
|
keyWidth: 16,
|
|
@@ -1967,29 +2754,29 @@ function ConnectShopify({ config, onDone }) {
|
|
|
1967
2754
|
]
|
|
1968
2755
|
}
|
|
1969
2756
|
) }),
|
|
1970
|
-
result.applied.length ? /* @__PURE__ */
|
|
1971
|
-
/* @__PURE__ */
|
|
1972
|
-
/* @__PURE__ */
|
|
2757
|
+
result.applied.length ? /* @__PURE__ */ jsx10(Section, { title: "Picked up from the store", children: result.applied.map((line, i) => /* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: ` ${glyph.tick} ${line}` }, i)) }) : null,
|
|
2758
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Alert3, { variant: "success", children: "Shopify connected \u2014 you can browse the real catalogue now" }) }),
|
|
2759
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: "press enter to continue" }) })
|
|
1973
2760
|
] }),
|
|
1974
|
-
step === STEP2.FAILED && error && /* @__PURE__ */
|
|
1975
|
-
/* @__PURE__ */
|
|
1976
|
-
error.hint ? /* @__PURE__ */
|
|
1977
|
-
/* @__PURE__ */
|
|
1978
|
-
/* @__PURE__ */
|
|
1979
|
-
/* @__PURE__ */
|
|
1980
|
-
/* @__PURE__ */
|
|
1981
|
-
/* @__PURE__ */
|
|
2761
|
+
step === STEP2.FAILED && error && /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
2762
|
+
/* @__PURE__ */ jsx10(Alert3, { variant: "error", children: error.message }),
|
|
2763
|
+
error.hint ? /* @__PURE__ */ jsx10(Text10, { color: palette.warn, children: ` ${error.hint}` }) : null,
|
|
2764
|
+
/* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
|
|
2765
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " common causes:" }),
|
|
2766
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " \xB7 the token is missing the read_products scope" }),
|
|
2767
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " \xB7 the token belongs to a different store" }),
|
|
2768
|
+
/* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: " \xB7 the domain is the custom domain, not the .myshopify.com one" })
|
|
1982
2769
|
] }),
|
|
1983
|
-
/* @__PURE__ */
|
|
2770
|
+
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { color: palette.dim, children: "press enter to try again" }) })
|
|
1984
2771
|
] })
|
|
1985
2772
|
] });
|
|
1986
2773
|
}
|
|
1987
2774
|
|
|
1988
2775
|
// src/ui/screens/Webhook.jsx
|
|
1989
|
-
import
|
|
1990
|
-
import { Box as
|
|
2776
|
+
import React11, { useState as useState4 } from "react";
|
|
2777
|
+
import { Box as Box11, Text as Text11, useInput as useInput6 } from "ink";
|
|
1991
2778
|
import { Select as Select6, TextInput as TextInput4, PasswordInput as PasswordInput3, Alert as Alert4, Badge as Badge3, Spinner as Spinner4 } from "@inkjs/ui";
|
|
1992
|
-
import { jsx as
|
|
2779
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1993
2780
|
var STEP3 = { PICK: "pick", URL: "url", SECRET: "secret", PROBE: "probe", DONE: "done" };
|
|
1994
2781
|
function Webhook({ config, onDone }) {
|
|
1995
2782
|
const [draft] = useState4(() => structuredClone(config));
|
|
@@ -2039,9 +2826,9 @@ function Webhook({ config, onDone }) {
|
|
|
2039
2826
|
}),
|
|
2040
2827
|
{ label: "Back".padEnd(30), value: "__back" }
|
|
2041
2828
|
];
|
|
2042
|
-
return /* @__PURE__ */
|
|
2043
|
-
/* @__PURE__ */
|
|
2044
|
-
/* @__PURE__ */
|
|
2829
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
2830
|
+
/* @__PURE__ */ jsx11(Header, { title: "Webhook destinations", subtitle: "one endpoint and secret per integration", right: "esc to go back" }),
|
|
2831
|
+
/* @__PURE__ */ jsx11(
|
|
2045
2832
|
Select6,
|
|
2046
2833
|
{
|
|
2047
2834
|
visibleOptionCount: options.length,
|
|
@@ -2054,17 +2841,17 @@ function Webhook({ config, onDone }) {
|
|
|
2054
2841
|
}
|
|
2055
2842
|
}
|
|
2056
2843
|
),
|
|
2057
|
-
/* @__PURE__ */
|
|
2844
|
+
/* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: palette.dim, children: `${glyph.dot} set globally \u2014 every build for that provider uses it` }) })
|
|
2058
2845
|
] });
|
|
2059
2846
|
}
|
|
2060
|
-
return /* @__PURE__ */
|
|
2061
|
-
/* @__PURE__ */
|
|
2062
|
-
step === STEP3.URL && /* @__PURE__ */
|
|
2063
|
-
/* @__PURE__ */
|
|
2064
|
-
urlError ? /* @__PURE__ */
|
|
2065
|
-
/* @__PURE__ */
|
|
2066
|
-
/* @__PURE__ */
|
|
2067
|
-
/* @__PURE__ */
|
|
2847
|
+
return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
2848
|
+
/* @__PURE__ */ jsx11(Header, { title: meta.label, subtitle: `signature: ${meta.signature}`, right: "esc to cancel" }),
|
|
2849
|
+
step === STEP3.URL && /* @__PURE__ */ jsxs11(Section, { title: "Webhook URL", children: [
|
|
2850
|
+
/* @__PURE__ */ jsx11(Text11, { color: palette.dim, children: " the full endpoint the provider would call \u2014 paste it as-is" }),
|
|
2851
|
+
urlError ? /* @__PURE__ */ jsx11(Text11, { color: palette.bad, children: ` ${glyph.cross} ${urlError}` }) : null,
|
|
2852
|
+
/* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
|
|
2853
|
+
/* @__PURE__ */ jsx11(Text11, { color: palette.accent, children: `${glyph.pointer} ` }),
|
|
2854
|
+
/* @__PURE__ */ jsx11(
|
|
2068
2855
|
TextInput4,
|
|
2069
2856
|
{
|
|
2070
2857
|
defaultValue: target.webhookUrl,
|
|
@@ -2074,12 +2861,12 @@ function Webhook({ config, onDone }) {
|
|
|
2074
2861
|
)
|
|
2075
2862
|
] })
|
|
2076
2863
|
] }),
|
|
2077
|
-
step === STEP3.SECRET && /* @__PURE__ */
|
|
2078
|
-
/* @__PURE__ */
|
|
2079
|
-
/* @__PURE__ */
|
|
2080
|
-
/* @__PURE__ */
|
|
2081
|
-
/* @__PURE__ */
|
|
2082
|
-
/* @__PURE__ */
|
|
2864
|
+
step === STEP3.SECRET && /* @__PURE__ */ jsxs11(Section, { title: meta.id === "nitro" ? "Bearer token" : "Signing secret", children: [
|
|
2865
|
+
/* @__PURE__ */ jsx11(Text11, { color: palette.dim, children: ` sent as ${meta.header}` }),
|
|
2866
|
+
/* @__PURE__ */ jsx11(Text11, { color: palette.dim, children: " must match what the receiver has stored" }),
|
|
2867
|
+
/* @__PURE__ */ jsxs11(Box11, { marginTop: 1, children: [
|
|
2868
|
+
/* @__PURE__ */ jsx11(Text11, { color: palette.accent, children: `${glyph.pointer} ` }),
|
|
2869
|
+
/* @__PURE__ */ jsx11(
|
|
2083
2870
|
PasswordInput3,
|
|
2084
2871
|
{
|
|
2085
2872
|
placeholder: target.webhookSecret ? mask(target.webhookSecret) : "the signing secret\u2026",
|
|
@@ -2088,14 +2875,14 @@ function Webhook({ config, onDone }) {
|
|
|
2088
2875
|
)
|
|
2089
2876
|
] })
|
|
2090
2877
|
] }),
|
|
2091
|
-
step === STEP3.PROBE && /* @__PURE__ */
|
|
2092
|
-
step === STEP3.DONE && /* @__PURE__ */
|
|
2093
|
-
/* @__PURE__ */
|
|
2094
|
-
/* @__PURE__ */
|
|
2095
|
-
/* @__PURE__ */
|
|
2096
|
-
/* @__PURE__ */
|
|
2878
|
+
step === STEP3.PROBE && /* @__PURE__ */ jsx11(Spinner4, { label: "checking the endpoint is reachable\u2026" }),
|
|
2879
|
+
step === STEP3.DONE && /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
2880
|
+
/* @__PURE__ */ jsxs11(Box11, { marginBottom: 1, children: [
|
|
2881
|
+
/* @__PURE__ */ jsx11(Badge3, { color: probe?.reachable ? "green" : "yellow", children: probe?.reachable ? "REACHABLE" : "UNVERIFIED" }),
|
|
2882
|
+
/* @__PURE__ */ jsx11(Text11, { children: " " }),
|
|
2883
|
+
/* @__PURE__ */ jsx11(Text11, { color: palette.dim, children: probe?.reachable ? `responded HTTP ${probe.status} to an unsigned probe` : probe?.error ?? "could not reach it \u2014 saved anyway" })
|
|
2097
2884
|
] }),
|
|
2098
|
-
/* @__PURE__ */
|
|
2885
|
+
/* @__PURE__ */ jsx11(Box11, { flexDirection: "column", borderStyle: "round", borderColor: palette.ok, paddingX: 1, children: /* @__PURE__ */ jsx11(
|
|
2099
2886
|
KeyValue,
|
|
2100
2887
|
{
|
|
2101
2888
|
keyWidth: 10,
|
|
@@ -2106,8 +2893,8 @@ function Webhook({ config, onDone }) {
|
|
|
2106
2893
|
]
|
|
2107
2894
|
}
|
|
2108
2895
|
) }),
|
|
2109
|
-
!meta.available ? /* @__PURE__ */
|
|
2110
|
-
/* @__PURE__ */
|
|
2896
|
+
!meta.available ? /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Alert4, { variant: "info", children: `saved \u2014 ${meta.label} payload generation is not implemented yet` }) }) : null,
|
|
2897
|
+
/* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text11, { color: palette.dim, children: "press enter to configure another" }) })
|
|
2111
2898
|
] })
|
|
2112
2899
|
] });
|
|
2113
2900
|
}
|
|
@@ -2130,10 +2917,10 @@ async function probeEndpoint(url) {
|
|
|
2130
2917
|
}
|
|
2131
2918
|
|
|
2132
2919
|
// src/ui/screens/Doctor.jsx
|
|
2133
|
-
import
|
|
2134
|
-
import { Box as
|
|
2920
|
+
import React12, { useEffect as useEffect2, useState as useState5 } from "react";
|
|
2921
|
+
import { Box as Box12, Text as Text12, useInput as useInput7 } from "ink";
|
|
2135
2922
|
import { Spinner as Spinner5 } from "@inkjs/ui";
|
|
2136
|
-
import { jsx as
|
|
2923
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2137
2924
|
function Doctor({ config, onDone }) {
|
|
2138
2925
|
const [checks, setChecks] = useState5(null);
|
|
2139
2926
|
useInput7((input, key) => {
|
|
@@ -2146,13 +2933,13 @@ function Doctor({ config, onDone }) {
|
|
|
2146
2933
|
alive = false;
|
|
2147
2934
|
};
|
|
2148
2935
|
}, []);
|
|
2149
|
-
return /* @__PURE__ */
|
|
2150
|
-
/* @__PURE__ */
|
|
2151
|
-
!checks ? /* @__PURE__ */
|
|
2152
|
-
/* @__PURE__ */
|
|
2153
|
-
/* @__PURE__ */
|
|
2154
|
-
/* @__PURE__ */
|
|
2155
|
-
/* @__PURE__ */
|
|
2936
|
+
return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
2937
|
+
/* @__PURE__ */ jsx12(Header, { title: "Doctor", subtitle: "everything the generator depends on" }),
|
|
2938
|
+
!checks ? /* @__PURE__ */ jsx12(Spinner5, { label: "running checks\u2026" }) : /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", children: [
|
|
2939
|
+
/* @__PURE__ */ jsx12(Section, { title: "Configuration", children: /* @__PURE__ */ jsx12(CheckList, { checks: checks.config }) }),
|
|
2940
|
+
/* @__PURE__ */ jsx12(Section, { title: "Connectivity", children: /* @__PURE__ */ jsx12(CheckList, { checks: checks.connectivity }) }),
|
|
2941
|
+
/* @__PURE__ */ jsx12(Section, { title: "Runtime", children: /* @__PURE__ */ jsx12(CheckList, { checks: checks.runtime }) }),
|
|
2942
|
+
/* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { color: palette.dim, children: "press enter to return to the menu" }) })
|
|
2156
2943
|
] })
|
|
2157
2944
|
] });
|
|
2158
2945
|
}
|
|
@@ -2200,10 +2987,10 @@ async function run(config) {
|
|
|
2200
2987
|
}
|
|
2201
2988
|
|
|
2202
2989
|
// src/ui/screens/History.jsx
|
|
2203
|
-
import
|
|
2204
|
-
import { Box as
|
|
2990
|
+
import React13, { useState as useState6 } from "react";
|
|
2991
|
+
import { Box as Box13, Text as Text13, useInput as useInput8 } from "ink";
|
|
2205
2992
|
import { Select as Select7, Spinner as Spinner6, Badge as Badge4, Alert as Alert5, ConfirmInput as ConfirmInput2 } from "@inkjs/ui";
|
|
2206
|
-
import { jsx as
|
|
2993
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2207
2994
|
function History({ config, onDone }) {
|
|
2208
2995
|
const [items] = useState6(() => listPayloads(20));
|
|
2209
2996
|
const [selected, setSelected] = useState6(null);
|
|
@@ -2229,14 +3016,14 @@ function History({ config, onDone }) {
|
|
|
2229
3016
|
});
|
|
2230
3017
|
};
|
|
2231
3018
|
if (!items.length) {
|
|
2232
|
-
return /* @__PURE__ */
|
|
2233
|
-
/* @__PURE__ */
|
|
2234
|
-
/* @__PURE__ */
|
|
3019
|
+
return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3020
|
+
/* @__PURE__ */ jsx13(Header, { title: "History", subtitle: "saved payloads" }),
|
|
3021
|
+
/* @__PURE__ */ jsx13(Text13, { color: palette.dim, children: "nothing built yet \u2014 press esc to go back" })
|
|
2235
3022
|
] });
|
|
2236
3023
|
}
|
|
2237
|
-
return /* @__PURE__ */
|
|
2238
|
-
/* @__PURE__ */
|
|
2239
|
-
phase === "list" && /* @__PURE__ */
|
|
3024
|
+
return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3025
|
+
/* @__PURE__ */ jsx13(Header, { title: "History", subtitle: `${items.length} saved payload${items.length === 1 ? "" : "s"}`, right: "esc to go back" }),
|
|
3026
|
+
phase === "list" && /* @__PURE__ */ jsx13(
|
|
2240
3027
|
Select7,
|
|
2241
3028
|
{
|
|
2242
3029
|
visibleOptionCount: 10,
|
|
@@ -2247,8 +3034,8 @@ function History({ config, onDone }) {
|
|
|
2247
3034
|
onChange: onPick
|
|
2248
3035
|
}
|
|
2249
3036
|
),
|
|
2250
|
-
phase === "detail" && selected && /* @__PURE__ */
|
|
2251
|
-
/* @__PURE__ */
|
|
3037
|
+
phase === "detail" && selected && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3038
|
+
/* @__PURE__ */ jsx13(Section, { title: "Payload", children: /* @__PURE__ */ jsx13(
|
|
2252
3039
|
KeyValue,
|
|
2253
3040
|
{
|
|
2254
3041
|
rows: [
|
|
@@ -2262,31 +3049,31 @@ function History({ config, onDone }) {
|
|
|
2262
3049
|
]
|
|
2263
3050
|
}
|
|
2264
3051
|
) }),
|
|
2265
|
-
/* @__PURE__ */
|
|
2266
|
-
/* @__PURE__ */
|
|
2267
|
-
/* @__PURE__ */
|
|
3052
|
+
/* @__PURE__ */ jsxs13(Box13, { marginTop: 1, children: [
|
|
3053
|
+
/* @__PURE__ */ jsx13(Text13, { children: "Re-send this payload (it will be re-signed with a fresh timestamp)? " }),
|
|
3054
|
+
/* @__PURE__ */ jsx13(ConfirmInput2, { onConfirm: resend, onCancel: () => setPhase("list") })
|
|
2268
3055
|
] })
|
|
2269
3056
|
] }),
|
|
2270
|
-
phase === "sending" && /* @__PURE__ */
|
|
2271
|
-
phase === "result" && result && /* @__PURE__ */
|
|
2272
|
-
/* @__PURE__ */
|
|
2273
|
-
/* @__PURE__ */
|
|
2274
|
-
/* @__PURE__ */
|
|
2275
|
-
/* @__PURE__ */
|
|
3057
|
+
phase === "sending" && /* @__PURE__ */ jsx13(Spinner6, { label: "re-signing and sending\u2026" }),
|
|
3058
|
+
phase === "result" && result && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3059
|
+
/* @__PURE__ */ jsxs13(Box13, { marginBottom: 1, children: [
|
|
3060
|
+
/* @__PURE__ */ jsx13(Badge4, { color: result.ok ? "green" : "red", children: result.error ? "FAILED" : `HTTP ${result.status}` }),
|
|
3061
|
+
/* @__PURE__ */ jsx13(Text13, { children: " " }),
|
|
3062
|
+
/* @__PURE__ */ jsx13(Text13, { color: palette.dim, children: result.error ?? explainStatus(result.status) })
|
|
2276
3063
|
] }),
|
|
2277
|
-
/* @__PURE__ */
|
|
3064
|
+
/* @__PURE__ */ jsx13(Text13, { color: palette.dim, children: "press enter to return" })
|
|
2278
3065
|
] }),
|
|
2279
|
-
phase === "error" && error && /* @__PURE__ */
|
|
2280
|
-
/* @__PURE__ */
|
|
2281
|
-
error.hint ? /* @__PURE__ */
|
|
2282
|
-
/* @__PURE__ */
|
|
3066
|
+
phase === "error" && error && /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", children: [
|
|
3067
|
+
/* @__PURE__ */ jsx13(Alert5, { variant: "error", children: error.message }),
|
|
3068
|
+
error.hint ? /* @__PURE__ */ jsx13(Text13, { color: palette.warn, children: ` ${error.hint}` }) : null,
|
|
3069
|
+
/* @__PURE__ */ jsx13(Text13, { color: palette.dim, children: "press enter to return" })
|
|
2283
3070
|
] })
|
|
2284
3071
|
] });
|
|
2285
3072
|
}
|
|
2286
3073
|
|
|
2287
3074
|
// src/ui/screens/Clear.jsx
|
|
2288
|
-
import
|
|
2289
|
-
import { Box as
|
|
3075
|
+
import React14, { useState as useState7 } from "react";
|
|
3076
|
+
import { Box as Box14, Text as Text14, useInput as useInput9 } from "ink";
|
|
2290
3077
|
import { Select as Select8, ConfirmInput as ConfirmInput3, Alert as Alert6, Badge as Badge5 } from "@inkjs/ui";
|
|
2291
3078
|
|
|
2292
3079
|
// src/core/cleanup.mjs
|
|
@@ -2362,7 +3149,7 @@ var TARGETS = {
|
|
|
2362
3149
|
};
|
|
2363
3150
|
|
|
2364
3151
|
// src/ui/screens/Clear.jsx
|
|
2365
|
-
import { jsx as
|
|
3152
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2366
3153
|
function Clear({ config, onDone }) {
|
|
2367
3154
|
const [state] = useState7(() => inspect());
|
|
2368
3155
|
const [choice, setChoice] = useState7(null);
|
|
@@ -2376,23 +3163,23 @@ function Clear({ config, onDone }) {
|
|
|
2376
3163
|
setResult(outcome);
|
|
2377
3164
|
};
|
|
2378
3165
|
if (result) {
|
|
2379
|
-
return /* @__PURE__ */
|
|
2380
|
-
/* @__PURE__ */
|
|
2381
|
-
/* @__PURE__ */
|
|
2382
|
-
/* @__PURE__ */
|
|
2383
|
-
/* @__PURE__ */
|
|
2384
|
-
/* @__PURE__ */
|
|
3166
|
+
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
3167
|
+
/* @__PURE__ */ jsx14(Header, { title: "Clear", subtitle: "done" }),
|
|
3168
|
+
/* @__PURE__ */ jsxs14(Box14, { marginBottom: 1, children: [
|
|
3169
|
+
/* @__PURE__ */ jsx14(Badge5, { color: "green", children: "CLEARED" }),
|
|
3170
|
+
/* @__PURE__ */ jsx14(Text14, { children: " " }),
|
|
3171
|
+
/* @__PURE__ */ jsx14(Text14, { color: palette.dim, children: choice === "all" ? `${result.payloads} payload(s), ${result.logs} log, ${result.config} config file removed` : `${result.removed} item(s) removed` })
|
|
2385
3172
|
] }),
|
|
2386
|
-
choice === "all" ? /* @__PURE__ */
|
|
2387
|
-
/* @__PURE__ */
|
|
3173
|
+
choice === "all" ? /* @__PURE__ */ jsx14(Alert6, { variant: "info", children: "back to first-run state \u2014 reconnect Shopify to continue" }) : null,
|
|
3174
|
+
/* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: palette.dim, children: "press enter to return" }) })
|
|
2388
3175
|
] });
|
|
2389
3176
|
}
|
|
2390
3177
|
if (choice) {
|
|
2391
3178
|
const target = TARGETS[choice];
|
|
2392
|
-
return /* @__PURE__ */
|
|
2393
|
-
/* @__PURE__ */
|
|
2394
|
-
/* @__PURE__ */
|
|
2395
|
-
/* @__PURE__ */
|
|
3179
|
+
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
3180
|
+
/* @__PURE__ */ jsx14(Header, { title: "Clear", subtitle: target.label, right: "esc to cancel" }),
|
|
3181
|
+
/* @__PURE__ */ jsx14(Alert6, { variant: "warning", children: `This permanently deletes ${target.description}.` }),
|
|
3182
|
+
/* @__PURE__ */ jsx14(Section, { title: "Will be removed", children: choice === "all" ? /* @__PURE__ */ jsx14(
|
|
2396
3183
|
KeyValue,
|
|
2397
3184
|
{
|
|
2398
3185
|
keyWidth: 10,
|
|
@@ -2402,22 +3189,22 @@ function Clear({ config, onDone }) {
|
|
|
2402
3189
|
["config", state.config.label]
|
|
2403
3190
|
]
|
|
2404
3191
|
}
|
|
2405
|
-
) : /* @__PURE__ */
|
|
2406
|
-
choice === "all" || choice === "disconnect" ? /* @__PURE__ */
|
|
2407
|
-
/* @__PURE__ */
|
|
2408
|
-
/* @__PURE__ */
|
|
2409
|
-
/* @__PURE__ */
|
|
3192
|
+
) : /* @__PURE__ */ jsx14(Text14, { color: palette.dim, children: ` ${glyph.dot} ${choice === "payloads" ? state.payloads.label : choice === "logs" ? state.logs.label : target.description}` }) }),
|
|
3193
|
+
choice === "all" || choice === "disconnect" ? /* @__PURE__ */ jsx14(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text14, { color: palette.warn, children: " your Shopify access token will have to be entered again" }) }) : null,
|
|
3194
|
+
/* @__PURE__ */ jsxs14(Box14, { marginTop: 1, children: [
|
|
3195
|
+
/* @__PURE__ */ jsx14(Text14, { children: "Are you sure? " }),
|
|
3196
|
+
/* @__PURE__ */ jsx14(ConfirmInput3, { defaultChoice: "cancel", onConfirm: run2, onCancel: () => setChoice(null) })
|
|
2410
3197
|
] })
|
|
2411
3198
|
] });
|
|
2412
3199
|
}
|
|
2413
|
-
return /* @__PURE__ */
|
|
2414
|
-
/* @__PURE__ */
|
|
2415
|
-
/* @__PURE__ */
|
|
2416
|
-
/* @__PURE__ */
|
|
2417
|
-
/* @__PURE__ */
|
|
2418
|
-
/* @__PURE__ */
|
|
3200
|
+
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
|
|
3201
|
+
/* @__PURE__ */ jsx14(Header, { title: "Clear", subtitle: "remove locally cached data", right: "esc to go back" }),
|
|
3202
|
+
/* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", marginBottom: 1, paddingX: 1, children: [
|
|
3203
|
+
/* @__PURE__ */ jsx14(Text14, { color: palette.dim, children: `${glyph.dot} ${state.payloads.label}` }),
|
|
3204
|
+
/* @__PURE__ */ jsx14(Text14, { color: palette.dim, children: `${glyph.dot} ${state.logs.label}` }),
|
|
3205
|
+
/* @__PURE__ */ jsx14(Text14, { color: palette.dim, children: `${glyph.dot} ${state.config.label}` })
|
|
2419
3206
|
] }),
|
|
2420
|
-
/* @__PURE__ */
|
|
3207
|
+
/* @__PURE__ */ jsx14(
|
|
2421
3208
|
Select8,
|
|
2422
3209
|
{
|
|
2423
3210
|
visibleOptionCount: 8,
|
|
@@ -2432,7 +3219,7 @@ function Clear({ config, onDone }) {
|
|
|
2432
3219
|
}
|
|
2433
3220
|
|
|
2434
3221
|
// src/ui/App.jsx
|
|
2435
|
-
import { jsx as
|
|
3222
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
2436
3223
|
function App({ config: initialConfig, initialScreen = "home" }) {
|
|
2437
3224
|
const { exit } = useApp2();
|
|
2438
3225
|
const [config, setConfig] = useState8(initialConfig);
|
|
@@ -2449,7 +3236,7 @@ function App({ config: initialConfig, initialScreen = "home" }) {
|
|
|
2449
3236
|
const toIntegrations = go("integrations");
|
|
2450
3237
|
switch (screen) {
|
|
2451
3238
|
case "setup":
|
|
2452
|
-
return /* @__PURE__ */
|
|
3239
|
+
return /* @__PURE__ */ jsx15(
|
|
2453
3240
|
Setup,
|
|
2454
3241
|
{
|
|
2455
3242
|
config,
|
|
@@ -2466,7 +3253,7 @@ function App({ config: initialConfig, initialScreen = "home" }) {
|
|
|
2466
3253
|
}
|
|
2467
3254
|
);
|
|
2468
3255
|
case "integrations":
|
|
2469
|
-
return /* @__PURE__ */
|
|
3256
|
+
return /* @__PURE__ */ jsx15(
|
|
2470
3257
|
Integrations,
|
|
2471
3258
|
{
|
|
2472
3259
|
config,
|
|
@@ -2479,21 +3266,21 @@ function App({ config: initialConfig, initialScreen = "home" }) {
|
|
|
2479
3266
|
}
|
|
2480
3267
|
);
|
|
2481
3268
|
case "connect":
|
|
2482
|
-
return /* @__PURE__ */
|
|
3269
|
+
return /* @__PURE__ */ jsx15(ConnectShopify, { config, onDone: toSetup });
|
|
2483
3270
|
case "webhook":
|
|
2484
|
-
return /* @__PURE__ */
|
|
3271
|
+
return /* @__PURE__ */ jsx15(Webhook, { config, onDone: toSetup });
|
|
2485
3272
|
case "configure":
|
|
2486
|
-
return /* @__PURE__ */
|
|
3273
|
+
return /* @__PURE__ */ jsx15(Configure, { config, section, onDone: toSetup });
|
|
2487
3274
|
case "build":
|
|
2488
|
-
return /* @__PURE__ */
|
|
3275
|
+
return /* @__PURE__ */ jsx15(Build, { config, providerId: provider, onDone: toIntegrations });
|
|
2489
3276
|
case "history":
|
|
2490
|
-
return /* @__PURE__ */
|
|
3277
|
+
return /* @__PURE__ */ jsx15(History, { config, onDone: toHome });
|
|
2491
3278
|
case "doctor":
|
|
2492
|
-
return /* @__PURE__ */
|
|
3279
|
+
return /* @__PURE__ */ jsx15(Doctor, { config, onDone: toHome });
|
|
2493
3280
|
case "clear":
|
|
2494
|
-
return /* @__PURE__ */
|
|
3281
|
+
return /* @__PURE__ */ jsx15(Clear, { config, onDone: toHome });
|
|
2495
3282
|
default:
|
|
2496
|
-
return /* @__PURE__ */
|
|
3283
|
+
return /* @__PURE__ */ jsx15(Home, { config, onPick: setScreen, onQuit: exit });
|
|
2497
3284
|
}
|
|
2498
3285
|
}
|
|
2499
3286
|
|
|
@@ -2582,15 +3369,16 @@ async function headless(command, args, config) {
|
|
|
2582
3369
|
return 1;
|
|
2583
3370
|
}
|
|
2584
3371
|
}
|
|
2585
|
-
function requireConfig(config) {
|
|
2586
|
-
if (isConfigured(config)) return null;
|
|
2587
|
-
const missing = configStatus(config).filter((r) => !r.ok).map((r) => r.key);
|
|
3372
|
+
function requireConfig(config, providerId = "cashfree-occ") {
|
|
3373
|
+
if (isConfigured(config, providerId)) return null;
|
|
3374
|
+
const missing = configStatus(config, providerId).filter((r) => !r.ok).map((r) => r.key);
|
|
2588
3375
|
log.fail(`configuration incomplete: ${missing.join(", ")}`);
|
|
2589
3376
|
log.dim(`run \`hookwright configure\` or edit ${config._path}`);
|
|
2590
3377
|
return 1;
|
|
2591
3378
|
}
|
|
2592
3379
|
async function cmdBuild(args, config) {
|
|
2593
|
-
const
|
|
3380
|
+
const providerId = typeof args.flags.provider === "string" ? args.flags.provider : "cashfree-occ";
|
|
3381
|
+
const bad = requireConfig(config, providerId);
|
|
2594
3382
|
if (bad) return bad;
|
|
2595
3383
|
const count = Number.parseInt(args.flags.items ?? "1", 10) || 1;
|
|
2596
3384
|
const discount = Number.parseFloat(args.flags.discount ?? "0") || 0;
|
|
@@ -2604,8 +3392,10 @@ async function cmdBuild(args, config) {
|
|
|
2604
3392
|
}
|
|
2605
3393
|
const items = autoSelect(products, count);
|
|
2606
3394
|
log.ok(`${items.length} product(s): ${items.map((i) => i.product.title).join(", ")}`);
|
|
2607
|
-
const built = await
|
|
3395
|
+
const built = await buildPayload4({
|
|
2608
3396
|
cfg: config,
|
|
3397
|
+
providerId,
|
|
3398
|
+
eventName: typeof args.flags.event === "string" ? args.flags.event : void 0,
|
|
2609
3399
|
items,
|
|
2610
3400
|
discount,
|
|
2611
3401
|
phone: typeof args.flags.phone === "string" ? args.flags.phone : void 0,
|
|
@@ -2622,8 +3412,18 @@ async function cmdBuild(args, config) {
|
|
|
2622
3412
|
}
|
|
2623
3413
|
}
|
|
2624
3414
|
if (args.flags.copy !== false && !args.flags.json) {
|
|
2625
|
-
|
|
2626
|
-
|
|
3415
|
+
let clip = built.body;
|
|
3416
|
+
let what = "payload JSON";
|
|
3417
|
+
try {
|
|
3418
|
+
const request = buildRequest({ provider: built.provider, config, body: built.body });
|
|
3419
|
+
clip = toCurl({ url: request.url, headers: request.headers, body: built.body });
|
|
3420
|
+
what = "curl";
|
|
3421
|
+
} catch (err) {
|
|
3422
|
+
log.warn(`${err.message} \u2014 copying the payload JSON instead`);
|
|
3423
|
+
if (err.hint) log.dim(err.hint);
|
|
3424
|
+
}
|
|
3425
|
+
const result = await copy(clip);
|
|
3426
|
+
if (result.ok) log.ok(`${what} copied to clipboard via ${result.via}`);
|
|
2627
3427
|
else log.dim(`clipboard unavailable \u2014 install xclip, xsel or wl-copy to enable copying`);
|
|
2628
3428
|
}
|
|
2629
3429
|
if (!built.report.ok && !args.flags.force) {
|
|
@@ -2651,7 +3451,8 @@ async function cmdSend(args, config, built) {
|
|
|
2651
3451
|
cfg: config,
|
|
2652
3452
|
record,
|
|
2653
3453
|
force: Boolean(args.flags.force),
|
|
2654
|
-
dryRun: Boolean(args.flags["dry-run"])
|
|
3454
|
+
dryRun: Boolean(args.flags["dry-run"]),
|
|
3455
|
+
checkEndpoint: args.flags["endpoint-check"] !== false
|
|
2655
3456
|
});
|
|
2656
3457
|
if (result.dryRun) {
|
|
2657
3458
|
log.blank();
|
|
@@ -2749,14 +3550,15 @@ function cmdClear(args, config) {
|
|
|
2749
3550
|
return 0;
|
|
2750
3551
|
}
|
|
2751
3552
|
function printReport(built) {
|
|
2752
|
-
const
|
|
3553
|
+
const s = built.summary ?? {};
|
|
2753
3554
|
log.blank();
|
|
2754
3555
|
box("Payload", [
|
|
2755
|
-
`
|
|
2756
|
-
`
|
|
2757
|
-
`
|
|
2758
|
-
`
|
|
2759
|
-
`
|
|
3556
|
+
`provider ${built.provider.label}`,
|
|
3557
|
+
`event ${s.event ?? "\u2014"}`,
|
|
3558
|
+
`store ${s.store ?? "\u2014"}`,
|
|
3559
|
+
`customer ${s.customer ?? "\u2014"}`,
|
|
3560
|
+
`items ${s.items ?? "\u2014"}`,
|
|
3561
|
+
`total ${s.total ?? "\u2014"} ${s.currency ?? ""}${s.extra ? " (" + s.extra + ")" : ""}`,
|
|
2760
3562
|
`saved ${built.file}`
|
|
2761
3563
|
], c.green);
|
|
2762
3564
|
log.blank();
|
|
@@ -2778,7 +3580,7 @@ function printReport(built) {
|
|
|
2778
3580
|
}
|
|
2779
3581
|
|
|
2780
3582
|
// src/cli.jsx
|
|
2781
|
-
import { jsx as
|
|
3583
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
2782
3584
|
var HELP = `
|
|
2783
3585
|
${c.bold("hookwright")} \u2014 generate real, signed provider webhooks from a live Shopify catalogue
|
|
2784
3586
|
|
|
@@ -2796,6 +3598,9 @@ ${c.bold("COMMANDS")}
|
|
|
2796
3598
|
help show this message
|
|
2797
3599
|
|
|
2798
3600
|
${c.bold("BUILD OPTIONS")}
|
|
3601
|
+
--provider <id> cashfree-occ | razorpay-magic | nitro (default cashfree-occ)
|
|
3602
|
+
--event <name> nitro only: view \xB7 category_view \xB7 product_view \xB7 addtocart
|
|
3603
|
+
removefromcart \xB7 checkout \xB7 orders/create \xB7 orders/updated
|
|
2799
3604
|
--items <n> how many products to put in the cart (default 1)
|
|
2800
3605
|
--search <text> only consider products matching a title
|
|
2801
3606
|
--discount <n> cart discount in major currency units (default 0)
|
|
@@ -2811,6 +3616,7 @@ ${c.bold("SEND OPTIONS")}
|
|
|
2811
3616
|
--file <path> send a specific saved payload
|
|
2812
3617
|
--dry-run print the request without sending
|
|
2813
3618
|
--force send even if pre-flight fails or target looks like prod
|
|
3619
|
+
--no-endpoint-check skip verifying the destination exists before sending
|
|
2814
3620
|
|
|
2815
3621
|
${c.bold("CLEAR OPTIONS")}
|
|
2816
3622
|
--payloads delete saved payloads
|
|
@@ -2864,7 +3670,7 @@ async function main() {
|
|
|
2864
3670
|
const config = loadConfig(args.flags.config);
|
|
2865
3671
|
if (command && command !== "ui") {
|
|
2866
3672
|
if (command === "configure" && process.stdin.isTTY) {
|
|
2867
|
-
render(/* @__PURE__ */
|
|
3673
|
+
render(/* @__PURE__ */ jsx16(App, { config, initialScreen: "connect" }));
|
|
2868
3674
|
return;
|
|
2869
3675
|
}
|
|
2870
3676
|
const code = await headless(command, args, config);
|
|
@@ -2877,11 +3683,14 @@ async function main() {
|
|
|
2877
3683
|
process.exitCode = 1;
|
|
2878
3684
|
return;
|
|
2879
3685
|
}
|
|
2880
|
-
render(/* @__PURE__ */
|
|
3686
|
+
render(/* @__PURE__ */ jsx16(App, { config, initialScreen: "home" }));
|
|
2881
3687
|
}
|
|
2882
3688
|
main().catch((err) => {
|
|
2883
3689
|
console.error(c.red(`
|
|
2884
3690
|
${err.message}`));
|
|
3691
|
+
for (const failure of err.failures ?? []) {
|
|
3692
|
+
console.error(` ${c.red(sym.cross)} ${c.bold(failure.name)}: ${failure.detail}`);
|
|
3693
|
+
}
|
|
2885
3694
|
if (err.hint) console.error(c.yellow(` ${err.hint}`));
|
|
2886
3695
|
process.exitCode = 1;
|
|
2887
3696
|
});
|