shop-client 3.9.0 → 3.9.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 +65 -0
- package/dist/ai/enrich.d.ts +93 -0
- package/dist/ai/enrich.js +25 -0
- package/dist/checkout.js +6 -0
- package/dist/chunk-2MF53V33.js +196 -0
- package/dist/chunk-CN7L3BHG.js +147 -0
- package/dist/chunk-CXUCPK6X.js +460 -0
- package/dist/chunk-MOBWPEY4.js +420 -0
- package/dist/chunk-ROH545KI.js +274 -0
- package/dist/chunk-RR6YTQWP.js +90 -0
- package/dist/chunk-V52MFQZE.js +233 -0
- package/dist/chunk-VPPCOJC3.js +865 -0
- package/dist/collections.d.ts +2 -1
- package/dist/collections.js +8 -0
- package/dist/index.d.ts +7 -84
- package/dist/index.js +753 -0
- package/dist/products.d.ts +2 -1
- package/dist/products.js +8 -0
- package/dist/store.d.ts +53 -1
- package/dist/store.js +9 -0
- package/dist/{store-iQARl6J3.d.ts → types-luPg5O08.d.ts} +1 -208
- package/dist/utils/detect-country.d.ts +32 -0
- package/dist/utils/detect-country.js +6 -0
- package/dist/utils/func.d.ts +61 -0
- package/dist/utils/func.js +24 -0
- package/dist/utils/rate-limit.js +10 -0
- package/package.json +16 -3
- package/dist/checkout.mjs +0 -1
- package/dist/chunk-6GPWNCDO.mjs +0 -130
- package/dist/chunk-EJO5U4BT.mjs +0 -2
- package/dist/chunk-FFKWCNLU.mjs +0 -1
- package/dist/chunk-KYLPIEU3.mjs +0 -2
- package/dist/chunk-MB2INNNP.mjs +0 -1
- package/dist/chunk-MI7754VX.mjs +0 -2
- package/dist/chunk-SZQPMLZG.mjs +0 -1
- package/dist/collections.mjs +0 -1
- package/dist/enrich-OZHBXKK6.mjs +0 -1
- package/dist/index.mjs +0 -2
- package/dist/products.mjs +0 -1
- package/dist/store.mjs +0 -1
- package/dist/utils/rate-limit.mjs +0 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/checkout.ts
|
|
2
|
+
function createCheckoutOperations(baseUrl) {
|
|
3
|
+
return {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Shopify checkout URL with pre-filled customer information and cart items.
|
|
6
|
+
*
|
|
7
|
+
* @param params - Checkout parameters
|
|
8
|
+
* @param params.email - Customer's email address (must be valid email format)
|
|
9
|
+
* @param params.items - Array of products to add to cart
|
|
10
|
+
* @param params.items[].productVariantId - Shopify product variant ID
|
|
11
|
+
* @param params.items[].quantity - Quantity as string (must be positive number)
|
|
12
|
+
* @param params.address - Customer's shipping address
|
|
13
|
+
* @param params.address.firstName - Customer's first name
|
|
14
|
+
* @param params.address.lastName - Customer's last name
|
|
15
|
+
* @param params.address.address1 - Street address
|
|
16
|
+
* @param params.address.city - City name
|
|
17
|
+
* @param params.address.zip - Postal/ZIP code
|
|
18
|
+
* @param params.address.country - Country name
|
|
19
|
+
* @param params.address.province - State/Province name
|
|
20
|
+
* @param params.address.phone - Phone number
|
|
21
|
+
*
|
|
22
|
+
* @returns {string} Complete Shopify checkout URL with pre-filled information
|
|
23
|
+
*
|
|
24
|
+
* @throws {Error} When email is invalid, items array is empty, or required address fields are missing
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
29
|
+
* const checkoutUrl = await shop.checkout.create([
|
|
30
|
+
* { variantId: '123', quantity: 2 },
|
|
31
|
+
* { variantId: '456', quantity: 1 }
|
|
32
|
+
* ]);
|
|
33
|
+
* console.log(checkoutUrl);
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
createUrl: ({
|
|
37
|
+
email,
|
|
38
|
+
items,
|
|
39
|
+
address
|
|
40
|
+
}) => {
|
|
41
|
+
if (!email || !email.includes("@")) {
|
|
42
|
+
throw new Error("Invalid email address");
|
|
43
|
+
}
|
|
44
|
+
if (!items || items.length === 0) {
|
|
45
|
+
throw new Error("Items array cannot be empty");
|
|
46
|
+
}
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (!item.productVariantId || !item.quantity) {
|
|
49
|
+
throw new Error("Each item must have productVariantId and quantity");
|
|
50
|
+
}
|
|
51
|
+
const qty = Number.parseInt(item.quantity, 10);
|
|
52
|
+
if (Number.isNaN(qty) || qty <= 0) {
|
|
53
|
+
throw new Error("Quantity must be a positive number");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const requiredFields = [
|
|
57
|
+
"firstName",
|
|
58
|
+
"lastName",
|
|
59
|
+
"address1",
|
|
60
|
+
"city",
|
|
61
|
+
"zip",
|
|
62
|
+
"country"
|
|
63
|
+
];
|
|
64
|
+
for (const field of requiredFields) {
|
|
65
|
+
if (!address[field]) {
|
|
66
|
+
throw new Error(`Address field '${field}' is required`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const cartPath = items.map(
|
|
70
|
+
(item) => `${encodeURIComponent(item.productVariantId)}:${encodeURIComponent(item.quantity)}`
|
|
71
|
+
).join(",");
|
|
72
|
+
const params = new URLSearchParams({
|
|
73
|
+
"checkout[email]": email,
|
|
74
|
+
"checkout[shipping_address][first_name]": address.firstName,
|
|
75
|
+
"checkout[shipping_address][last_name]": address.lastName,
|
|
76
|
+
"checkout[shipping_address][address1]": address.address1,
|
|
77
|
+
"checkout[shipping_address][city]": address.city,
|
|
78
|
+
"checkout[shipping_address][zip]": address.zip,
|
|
79
|
+
"checkout[shipping_address][country]": address.country,
|
|
80
|
+
"checkout[shipping_address][province]": address.province,
|
|
81
|
+
"checkout[shipping_address][phone]": address.phone
|
|
82
|
+
});
|
|
83
|
+
return `${baseUrl}cart/${cartPath}?${params.toString()}`;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
createCheckoutOperations
|
|
90
|
+
};
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {
|
|
2
|
+
rateLimitedFetch
|
|
3
|
+
} from "./chunk-2MF53V33.js";
|
|
4
|
+
import {
|
|
5
|
+
detectShopCountry
|
|
6
|
+
} from "./chunk-ROH545KI.js";
|
|
7
|
+
import {
|
|
8
|
+
extractDomainWithoutSuffix,
|
|
9
|
+
generateStoreSlug,
|
|
10
|
+
sanitizeDomain
|
|
11
|
+
} from "./chunk-CN7L3BHG.js";
|
|
12
|
+
|
|
13
|
+
// src/client/get-info.ts
|
|
14
|
+
import { unique } from "remeda";
|
|
15
|
+
async function getInfoForStore(args) {
|
|
16
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
17
|
+
const {
|
|
18
|
+
baseUrl,
|
|
19
|
+
storeDomain,
|
|
20
|
+
validateProductExists,
|
|
21
|
+
validateCollectionExists,
|
|
22
|
+
validateLinksInBatches
|
|
23
|
+
} = args;
|
|
24
|
+
const response = await rateLimitedFetch(baseUrl, {
|
|
25
|
+
rateLimitClass: "store:info"
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
const html = await response.text();
|
|
31
|
+
const getMetaTag = (name2) => {
|
|
32
|
+
const regex = new RegExp(
|
|
33
|
+
`<meta[^>]*name=["']${name2}["'][^>]*content=["'](.*?)["']`
|
|
34
|
+
);
|
|
35
|
+
const match = html.match(regex);
|
|
36
|
+
return match ? match[1] : null;
|
|
37
|
+
};
|
|
38
|
+
const getPropertyMetaTag = (property) => {
|
|
39
|
+
const regex = new RegExp(
|
|
40
|
+
`<meta[^>]*property=["']${property}["'][^>]*content=["'](.*?)["']`
|
|
41
|
+
);
|
|
42
|
+
const match = html.match(regex);
|
|
43
|
+
return match ? match[1] : null;
|
|
44
|
+
};
|
|
45
|
+
const name = (_a = getMetaTag("og:site_name")) != null ? _a : extractDomainWithoutSuffix(baseUrl);
|
|
46
|
+
const title = (_b = getMetaTag("og:title")) != null ? _b : getMetaTag("twitter:title");
|
|
47
|
+
const description = getMetaTag("description") || getPropertyMetaTag("og:description");
|
|
48
|
+
const shopifyWalletId = (_c = getMetaTag("shopify-digital-wallet")) == null ? void 0 : _c.split("/")[1];
|
|
49
|
+
const myShopifySubdomainMatch = html.match(/['"](.*?\.myshopify\.com)['"]/);
|
|
50
|
+
const myShopifySubdomain = myShopifySubdomainMatch ? myShopifySubdomainMatch[1] : null;
|
|
51
|
+
let logoUrl = getPropertyMetaTag("og:image") || getPropertyMetaTag("og:image:secure_url");
|
|
52
|
+
if (!logoUrl) {
|
|
53
|
+
const logoMatch = html.match(
|
|
54
|
+
/<img[^>]+src=["']([^"']+\/cdn\/shop\/[^"']+)["']/
|
|
55
|
+
);
|
|
56
|
+
const matchedUrl = logoMatch == null ? void 0 : logoMatch[1];
|
|
57
|
+
logoUrl = matchedUrl ? matchedUrl.replace("http://", "https://") : null;
|
|
58
|
+
} else {
|
|
59
|
+
logoUrl = logoUrl.replace("http://", "https://");
|
|
60
|
+
}
|
|
61
|
+
const socialLinks = {};
|
|
62
|
+
const socialRegex = /<a[^>]+href=["']([^"']*(?:facebook|twitter|instagram|pinterest|youtube|linkedin|tiktok|vimeo)\.com[^"']*)["']/g;
|
|
63
|
+
for (const match of html.matchAll(socialRegex)) {
|
|
64
|
+
const str = match[1];
|
|
65
|
+
if (!str) continue;
|
|
66
|
+
let href = str;
|
|
67
|
+
try {
|
|
68
|
+
if (href.startsWith("//")) {
|
|
69
|
+
href = `https:${href}`;
|
|
70
|
+
} else if (href.startsWith("/")) {
|
|
71
|
+
href = new URL(href, baseUrl).toString();
|
|
72
|
+
}
|
|
73
|
+
const parsed = new URL(href);
|
|
74
|
+
const domain = parsed.hostname.replace("www.", "").split(".")[0];
|
|
75
|
+
if (domain) {
|
|
76
|
+
socialLinks[domain] = parsed.toString();
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const contactLinks = {
|
|
82
|
+
tel: null,
|
|
83
|
+
email: null,
|
|
84
|
+
contactPage: null
|
|
85
|
+
};
|
|
86
|
+
for (const match of html.matchAll(/href=["']tel:([^"']+)["']/g)) {
|
|
87
|
+
contactLinks.tel = ((_d = match == null ? void 0 : match[1]) == null ? void 0 : _d.trim()) || null;
|
|
88
|
+
}
|
|
89
|
+
for (const match of html.matchAll(/href=["']mailto:([^"']+)["']/g)) {
|
|
90
|
+
contactLinks.email = ((_e = match == null ? void 0 : match[1]) == null ? void 0 : _e.trim()) || null;
|
|
91
|
+
}
|
|
92
|
+
for (const match of html.matchAll(
|
|
93
|
+
/href=["']([^"']*(?:\/contact|\/pages\/contact)[^"']*)["']/g
|
|
94
|
+
)) {
|
|
95
|
+
contactLinks.contactPage = (match == null ? void 0 : match[1]) || null;
|
|
96
|
+
}
|
|
97
|
+
const extractedProductLinks = ((_g = (_f = html.match(/href=["']([^"']*\/products\/[^"']+)["']/g)) == null ? void 0 : _f.map(
|
|
98
|
+
(match) => {
|
|
99
|
+
var _a2, _b2;
|
|
100
|
+
return (_b2 = (_a2 = match == null ? void 0 : match.split("href=")[1]) == null ? void 0 : _a2.replace(/["']/g, "")) == null ? void 0 : _b2.split("/").at(-1);
|
|
101
|
+
}
|
|
102
|
+
)) == null ? void 0 : _g.filter(Boolean)) || [];
|
|
103
|
+
const extractedCollectionLinks = ((_i = (_h = html.match(/href=["']([^"']*\/collections\/[^"']+)["']/g)) == null ? void 0 : _h.map(
|
|
104
|
+
(match) => {
|
|
105
|
+
var _a2, _b2;
|
|
106
|
+
return (_b2 = (_a2 = match == null ? void 0 : match.split("href=")[1]) == null ? void 0 : _a2.replace(/["']/g, "")) == null ? void 0 : _b2.split("/").at(-1);
|
|
107
|
+
}
|
|
108
|
+
)) == null ? void 0 : _i.filter(Boolean)) || [];
|
|
109
|
+
const headerLinks = (_k = (_j = html.match(
|
|
110
|
+
/<(header|nav|div|section)\b[^>]*\b(?:id|class)=["'][^"']*(?=.*shopify-section)(?=.*\b(header|navigation|nav|menu)\b)[^"']*["'][^>]*>[\s\S]*?<\/\1>/gi
|
|
111
|
+
)) == null ? void 0 : _j.flatMap((header) => {
|
|
112
|
+
var _a2, _b2;
|
|
113
|
+
const links = (_a2 = header.match(/href=["']([^"']+)["']/g)) == null ? void 0 : _a2.filter(
|
|
114
|
+
(link) => link.includes("/products/") || link.includes("/collections/") || link.includes("/pages/")
|
|
115
|
+
);
|
|
116
|
+
return (_b2 = links == null ? void 0 : links.map((link) => {
|
|
117
|
+
var _a3;
|
|
118
|
+
const href = (_a3 = link.match(/href=["']([^"']+)["']/)) == null ? void 0 : _a3[1];
|
|
119
|
+
if (href && !href.startsWith("#") && !href.startsWith("javascript:")) {
|
|
120
|
+
try {
|
|
121
|
+
const url = new URL(href, storeDomain);
|
|
122
|
+
return url.pathname.replace(/^\/|\/$/g, "");
|
|
123
|
+
} catch {
|
|
124
|
+
return href.replace(/^\/|\/$/g, "");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}).filter((item) => Boolean(item))) != null ? _b2 : [];
|
|
129
|
+
})) != null ? _k : [];
|
|
130
|
+
const slug = generateStoreSlug(baseUrl);
|
|
131
|
+
const countryDetection = await detectShopCountry(html);
|
|
132
|
+
const [homePageProductLinks, homePageCollectionLinks] = await Promise.all([
|
|
133
|
+
validateLinksInBatches(
|
|
134
|
+
extractedProductLinks.filter(
|
|
135
|
+
(handle) => Boolean(handle)
|
|
136
|
+
),
|
|
137
|
+
(handle) => validateProductExists(handle)
|
|
138
|
+
),
|
|
139
|
+
validateLinksInBatches(
|
|
140
|
+
extractedCollectionLinks.filter(
|
|
141
|
+
(handle) => Boolean(handle)
|
|
142
|
+
),
|
|
143
|
+
(handle) => validateCollectionExists(handle)
|
|
144
|
+
)
|
|
145
|
+
]);
|
|
146
|
+
const info = {
|
|
147
|
+
name: name || slug,
|
|
148
|
+
domain: sanitizeDomain(baseUrl),
|
|
149
|
+
slug,
|
|
150
|
+
title: title || null,
|
|
151
|
+
description: description || null,
|
|
152
|
+
logoUrl,
|
|
153
|
+
socialLinks,
|
|
154
|
+
contactLinks,
|
|
155
|
+
headerLinks,
|
|
156
|
+
showcase: {
|
|
157
|
+
products: unique(homePageProductLinks != null ? homePageProductLinks : []),
|
|
158
|
+
collections: unique(homePageCollectionLinks != null ? homePageCollectionLinks : [])
|
|
159
|
+
},
|
|
160
|
+
jsonLdData: ((_m = (_l = html.match(
|
|
161
|
+
/<script[^>]*type="application\/ld\+json"[^>]*>([^<]+)<\/script>/g
|
|
162
|
+
)) == null ? void 0 : _l.map(
|
|
163
|
+
(match) => {
|
|
164
|
+
var _a2;
|
|
165
|
+
return ((_a2 = match == null ? void 0 : match.split(">")[1]) == null ? void 0 : _a2.replace(/<\/script/g, "")) || null;
|
|
166
|
+
}
|
|
167
|
+
)) == null ? void 0 : _m.map((json) => json ? JSON.parse(json) : null)) || [],
|
|
168
|
+
techProvider: {
|
|
169
|
+
name: "shopify",
|
|
170
|
+
walletId: shopifyWalletId,
|
|
171
|
+
subDomain: myShopifySubdomain != null ? myShopifySubdomain : null
|
|
172
|
+
},
|
|
173
|
+
country: countryDetection.country
|
|
174
|
+
};
|
|
175
|
+
const currencyCode = countryDetection == null ? void 0 : countryDetection.currencyCode;
|
|
176
|
+
return { info, currencyCode };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/store.ts
|
|
180
|
+
function createStoreOperations(context) {
|
|
181
|
+
return {
|
|
182
|
+
/**
|
|
183
|
+
* Fetches comprehensive store information including metadata, social links, and showcase content.
|
|
184
|
+
*
|
|
185
|
+
* @returns {Promise<StoreInfo>} Store information object containing:
|
|
186
|
+
* - `name` - Store name from meta tags or domain
|
|
187
|
+
* - `domain` - Store domain URL
|
|
188
|
+
* - `slug` - Generated store slug
|
|
189
|
+
* - `title` - Store title from meta tags
|
|
190
|
+
* - `description` - Store description from meta tags
|
|
191
|
+
* - `logoUrl` - Store logo URL from Open Graph or CDN
|
|
192
|
+
* - `socialLinks` - Object with social media links (facebook, twitter, instagram, etc.)
|
|
193
|
+
* - `contactLinks` - Object with contact information (tel, email, contactPage)
|
|
194
|
+
* - `headerLinks` - Array of navigation links from header
|
|
195
|
+
* - `showcase` - Object with featured products and collections from homepage
|
|
196
|
+
* - `jsonLdData` - Structured data from JSON-LD scripts
|
|
197
|
+
* - `techProvider` - Shopify-specific information (walletId, subDomain)
|
|
198
|
+
* - `country` - Country detection results with ISO 3166-1 alpha-2 codes (e.g., "US", "GB")
|
|
199
|
+
*
|
|
200
|
+
* @throws {Error} When the store URL is unreachable or returns an error
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* const shop = new ShopClient('https://exampleshop.com');
|
|
205
|
+
* const storeInfo = await shop.getInfo();
|
|
206
|
+
*
|
|
207
|
+
* console.log(storeInfo.name); // "Example Store"
|
|
208
|
+
* console.log(storeInfo.socialLinks.instagram); // "https://instagram.com/example"
|
|
209
|
+
* console.log(storeInfo.showcase.products); // ["product-handle-1", "product-handle-2"]
|
|
210
|
+
* console.log(storeInfo.country); // "US"
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
info: async () => {
|
|
214
|
+
try {
|
|
215
|
+
const { info } = await getInfoForStore({
|
|
216
|
+
baseUrl: context.baseUrl,
|
|
217
|
+
storeDomain: context.storeDomain,
|
|
218
|
+
validateProductExists: context.validateProductExists,
|
|
219
|
+
validateCollectionExists: context.validateCollectionExists,
|
|
220
|
+
validateLinksInBatches: context.validateLinksInBatches
|
|
221
|
+
});
|
|
222
|
+
return info;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
context.handleFetchError(error, "fetching store info", context.baseUrl);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export {
|
|
231
|
+
getInfoForStore,
|
|
232
|
+
createStoreOperations
|
|
233
|
+
};
|