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.
Files changed (41) hide show
  1. package/README.md +65 -0
  2. package/dist/ai/enrich.d.ts +93 -0
  3. package/dist/ai/enrich.js +25 -0
  4. package/dist/checkout.js +6 -0
  5. package/dist/chunk-2MF53V33.js +196 -0
  6. package/dist/chunk-CN7L3BHG.js +147 -0
  7. package/dist/chunk-CXUCPK6X.js +460 -0
  8. package/dist/chunk-MOBWPEY4.js +420 -0
  9. package/dist/chunk-ROH545KI.js +274 -0
  10. package/dist/chunk-RR6YTQWP.js +90 -0
  11. package/dist/chunk-V52MFQZE.js +233 -0
  12. package/dist/chunk-VPPCOJC3.js +865 -0
  13. package/dist/collections.d.ts +2 -1
  14. package/dist/collections.js +8 -0
  15. package/dist/index.d.ts +7 -84
  16. package/dist/index.js +753 -0
  17. package/dist/products.d.ts +2 -1
  18. package/dist/products.js +8 -0
  19. package/dist/store.d.ts +53 -1
  20. package/dist/store.js +9 -0
  21. package/dist/{store-iQARl6J3.d.ts → types-luPg5O08.d.ts} +1 -208
  22. package/dist/utils/detect-country.d.ts +32 -0
  23. package/dist/utils/detect-country.js +6 -0
  24. package/dist/utils/func.d.ts +61 -0
  25. package/dist/utils/func.js +24 -0
  26. package/dist/utils/rate-limit.js +10 -0
  27. package/package.json +16 -3
  28. package/dist/checkout.mjs +0 -1
  29. package/dist/chunk-6GPWNCDO.mjs +0 -130
  30. package/dist/chunk-EJO5U4BT.mjs +0 -2
  31. package/dist/chunk-FFKWCNLU.mjs +0 -1
  32. package/dist/chunk-KYLPIEU3.mjs +0 -2
  33. package/dist/chunk-MB2INNNP.mjs +0 -1
  34. package/dist/chunk-MI7754VX.mjs +0 -2
  35. package/dist/chunk-SZQPMLZG.mjs +0 -1
  36. package/dist/collections.mjs +0 -1
  37. package/dist/enrich-OZHBXKK6.mjs +0 -1
  38. package/dist/index.mjs +0 -2
  39. package/dist/products.mjs +0 -1
  40. package/dist/store.mjs +0 -1
  41. package/dist/utils/rate-limit.mjs +0 -1
@@ -0,0 +1,420 @@
1
+ import {
2
+ rateLimitedFetch
3
+ } from "./chunk-2MF53V33.js";
4
+ import {
5
+ formatPrice
6
+ } from "./chunk-CN7L3BHG.js";
7
+
8
+ // src/collections.ts
9
+ import { filter, isNonNullish } from "remeda";
10
+ function createCollectionOperations(baseUrl, storeDomain, fetchCollections, collectionsDto, fetchPaginatedProductsFromCollection, getStoreInfo, findCollection) {
11
+ const cacheExpiryMs = 5 * 60 * 1e3;
12
+ const findCache = /* @__PURE__ */ new Map();
13
+ const getCached = (key) => {
14
+ const entry = findCache.get(key);
15
+ if (!entry) return void 0;
16
+ if (Date.now() - entry.ts < cacheExpiryMs) return entry.value;
17
+ findCache.delete(key);
18
+ return void 0;
19
+ };
20
+ const setCached = (key, value) => {
21
+ findCache.set(key, { ts: Date.now(), value });
22
+ };
23
+ function applyCurrencyOverride(product, currency) {
24
+ var _a, _b, _c, _d, _e, _f;
25
+ const priceMin = (_b = (_a = product.priceMin) != null ? _a : product.price) != null ? _b : 0;
26
+ const priceMax = (_d = (_c = product.priceMax) != null ? _c : product.price) != null ? _d : 0;
27
+ const compareAtMin = (_f = (_e = product.compareAtPriceMin) != null ? _e : product.compareAtPrice) != null ? _f : 0;
28
+ return {
29
+ ...product,
30
+ currency,
31
+ localizedPricing: {
32
+ currency,
33
+ priceFormatted: formatPrice(priceMin, currency),
34
+ priceMinFormatted: formatPrice(priceMin, currency),
35
+ priceMaxFormatted: formatPrice(priceMax, currency),
36
+ compareAtPriceFormatted: formatPrice(compareAtMin, currency)
37
+ }
38
+ };
39
+ }
40
+ function maybeOverrideProductsCurrency(products, currency) {
41
+ if (!products || !currency) return products;
42
+ return products.map((p) => applyCurrencyOverride(p, currency));
43
+ }
44
+ return {
45
+ /**
46
+ * Fetches collections with pagination support.
47
+ *
48
+ * @param options - Pagination options
49
+ * @param options.page - Page number (default: 1)
50
+ * @param options.limit - Number of collections per page (default: 10, max: 250)
51
+ *
52
+ * @returns {Promise<Collection[] | null>} Collections for the requested page, or null on error
53
+ */
54
+ paginated: async (options) => {
55
+ var _a, _b;
56
+ const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
57
+ const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 10;
58
+ if (page < 1 || limit < 1 || limit > 250) {
59
+ throw new Error(
60
+ "Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
61
+ );
62
+ }
63
+ try {
64
+ const collections = await fetchCollections(page, limit);
65
+ return collections != null ? collections : null;
66
+ } catch (error) {
67
+ console.error(
68
+ "Failed to fetch paginated collections:",
69
+ storeDomain,
70
+ error
71
+ );
72
+ return null;
73
+ }
74
+ },
75
+ /**
76
+ * Fetches all collections from the store across all pages.
77
+ *
78
+ * @returns {Promise<Collection[]>} Array of all collections
79
+ *
80
+ * @throws {Error} When there's a network error or API failure
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const shop = new ShopClient('https://exampleshop.com');
85
+ * const allCollections = await shop.collections.all();
86
+ *
87
+ * console.log(`Found ${allCollections.length} collections`);
88
+ * allCollections.forEach(collection => {
89
+ * console.log(collection.title, collection.handle);
90
+ * });
91
+ * ```
92
+ */
93
+ all: async () => {
94
+ const limit = 250;
95
+ const allCollections = [];
96
+ async function fetchAll() {
97
+ let currentPage = 1;
98
+ while (true) {
99
+ const collections = await fetchCollections(currentPage, limit);
100
+ if (!collections || collections.length === 0 || collections.length < limit) {
101
+ if (!collections) {
102
+ console.warn(
103
+ "fetchCollections returned null, treating as empty array."
104
+ );
105
+ break;
106
+ }
107
+ if (collections && collections.length > 0) {
108
+ allCollections.push(...collections);
109
+ }
110
+ break;
111
+ }
112
+ allCollections.push(...collections);
113
+ currentPage++;
114
+ }
115
+ return allCollections;
116
+ }
117
+ try {
118
+ const collections = await fetchAll();
119
+ return collections || [];
120
+ } catch (error) {
121
+ console.error("Failed to fetch all collections:", storeDomain, error);
122
+ throw error;
123
+ }
124
+ },
125
+ /**
126
+ * Finds a specific collection by its handle.
127
+ *
128
+ * @param collectionHandle - The collection handle (URL slug) to search for
129
+ *
130
+ * @returns {Promise<Collection | null>} The collection if found, null if not found
131
+ *
132
+ * @throws {Error} When the handle is invalid or there's a network error
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const shop = new ShopClient('https://example.myshopify.com');
137
+ * const collection = await shop.collections.find('summer-collection');
138
+ * if (collection) {
139
+ * console.log(collection.title); // "Summer Collection"
140
+ * }
141
+ * ```
142
+ */
143
+ find: async (collectionHandle) => {
144
+ var _a, _b;
145
+ if (!collectionHandle || typeof collectionHandle !== "string") {
146
+ throw new Error("Collection handle is required and must be a string");
147
+ }
148
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
149
+ if (!sanitizedHandle) {
150
+ throw new Error("Invalid collection handle format");
151
+ }
152
+ if (sanitizedHandle.length > 255) {
153
+ throw new Error("Collection handle is too long");
154
+ }
155
+ const cached = getCached(sanitizedHandle);
156
+ if (typeof cached !== "undefined") {
157
+ return cached;
158
+ }
159
+ try {
160
+ const url = `${baseUrl}collections/${encodeURIComponent(sanitizedHandle)}.json`;
161
+ const response = await rateLimitedFetch(url, {
162
+ rateLimitClass: "collections:single"
163
+ });
164
+ if (!response.ok) {
165
+ if (response.status === 404) {
166
+ return null;
167
+ }
168
+ throw new Error(`HTTP error! status: ${response.status}`);
169
+ }
170
+ const result = await response.json();
171
+ let collectionImage = result.collection.image;
172
+ if (!collectionImage) {
173
+ const collectionProduct = (_a = await fetchPaginatedProductsFromCollection(
174
+ result.collection.handle,
175
+ {
176
+ limit: 1,
177
+ page: 1
178
+ }
179
+ )) == null ? void 0 : _a.at(0);
180
+ const collectionProductImage = (_b = collectionProduct == null ? void 0 : collectionProduct.images) == null ? void 0 : _b[0];
181
+ if (collectionProduct && collectionProductImage) {
182
+ collectionImage = {
183
+ id: collectionProductImage.id,
184
+ src: collectionProductImage.src,
185
+ alt: collectionProductImage.alt || collectionProduct.title,
186
+ created_at: collectionProductImage.createdAt || (/* @__PURE__ */ new Date()).toISOString()
187
+ };
188
+ }
189
+ }
190
+ const collectionData = collectionsDto([
191
+ {
192
+ ...result.collection,
193
+ image: collectionImage
194
+ }
195
+ ]);
196
+ const coll = collectionData[0] || null;
197
+ setCached(sanitizedHandle, coll);
198
+ if ((coll == null ? void 0 : coll.handle) && coll.handle !== sanitizedHandle) {
199
+ setCached(coll.handle, coll);
200
+ }
201
+ return coll;
202
+ } catch (error) {
203
+ if (error instanceof Error) {
204
+ console.error(
205
+ `Error fetching collection ${sanitizedHandle}:`,
206
+ baseUrl,
207
+ error.message
208
+ );
209
+ }
210
+ throw error;
211
+ }
212
+ },
213
+ /**
214
+ * Fetches collections that are showcased/featured on the store's homepage.
215
+ *
216
+ * @returns {Promise<Collection[]>} Array of showcased collections found on the homepage
217
+ *
218
+ * @throws {Error} When there's a network error or API failure
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const shop = new ShopClient('https://exampleshop.com');
223
+ * const showcasedCollections = await shop.collections.showcased();
224
+ *
225
+ * console.log(`Found ${showcasedCollections.length} showcased collections`);
226
+ * showcasedCollections.forEach(collection => {
227
+ * console.log(`Featured: ${collection.title} - ${collection.productsCount} products`);
228
+ * });
229
+ * ```
230
+ */
231
+ showcased: async () => {
232
+ const storeInfo = await getStoreInfo();
233
+ const collections = await Promise.all(
234
+ storeInfo.showcase.collections.map(
235
+ (collectionHandle) => findCollection(collectionHandle)
236
+ )
237
+ );
238
+ return filter(collections, isNonNullish);
239
+ },
240
+ products: {
241
+ /**
242
+ * Fetches products from a specific collection with pagination support.
243
+ *
244
+ * @param collectionHandle - The collection handle to fetch products from
245
+ * @param options - Pagination options
246
+ * @param options.page - Page number (default: 1)
247
+ * @param options.limit - Number of products per page (default: 250, max: 250)
248
+ *
249
+ * @returns {Promise<Product[] | null>} Array of products from the collection or null if error occurs
250
+ *
251
+ * @throws {Error} When the collection handle is invalid or there's a network error
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * const shop = new ShopClient('https://example.myshopify.com');
256
+ *
257
+ * // Get first page of products from a collection
258
+ * const products = await shop.collections.products.paginated('summer-collection');
259
+ *
260
+ * // Get second page with custom limit
261
+ * const moreProducts = await shop.collections.products.paginated(
262
+ * 'summer-collection',
263
+ * { page: 2, limit: 50 }
264
+ * );
265
+ * ```
266
+ */
267
+ paginated: async (collectionHandle, options) => {
268
+ var _a, _b;
269
+ if (!collectionHandle || typeof collectionHandle !== "string") {
270
+ throw new Error("Collection handle is required and must be a string");
271
+ }
272
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
273
+ if (!sanitizedHandle) {
274
+ throw new Error("Invalid collection handle format");
275
+ }
276
+ if (sanitizedHandle.length > 255) {
277
+ throw new Error("Collection handle is too long");
278
+ }
279
+ const page = (_a = options == null ? void 0 : options.page) != null ? _a : 1;
280
+ const limit = (_b = options == null ? void 0 : options.limit) != null ? _b : 250;
281
+ if (page < 1 || limit < 1 || limit > 250) {
282
+ throw new Error(
283
+ "Invalid pagination parameters: page must be >= 1, limit must be between 1 and 250"
284
+ );
285
+ }
286
+ const products = await fetchPaginatedProductsFromCollection(
287
+ sanitizedHandle,
288
+ { page, limit }
289
+ );
290
+ return maybeOverrideProductsCurrency(products, options == null ? void 0 : options.currency);
291
+ },
292
+ /**
293
+ * Fetches all products from a specific collection.
294
+ *
295
+ * @param collectionHandle - The collection handle to fetch products from
296
+ *
297
+ * @returns {Promise<Product[] | null>} Array of all products from the collection or null if error occurs
298
+ *
299
+ * @throws {Error} When the collection handle is invalid or there's a network error
300
+ *
301
+ * @example
302
+ * ```typescript
303
+ * const shop = new ShopClient('https://exampleshop.com');
304
+ * const allProducts = await shop.collections.products.all('summer-collection');
305
+ *
306
+ * if (allProducts) {
307
+ * console.log(`Found ${allProducts.length} products in the collection`);
308
+ * allProducts.forEach(product => {
309
+ * console.log(`${product.title} - $${product.price}`);
310
+ * });
311
+ * }
312
+ * ```
313
+ */
314
+ all: async (collectionHandle, options) => {
315
+ if (!collectionHandle || typeof collectionHandle !== "string") {
316
+ throw new Error("Collection handle is required and must be a string");
317
+ }
318
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
319
+ if (!sanitizedHandle) {
320
+ throw new Error("Invalid collection handle format");
321
+ }
322
+ if (sanitizedHandle.length > 255) {
323
+ throw new Error("Collection handle is too long");
324
+ }
325
+ try {
326
+ const limit = 250;
327
+ const allProducts = [];
328
+ let currentPage = 1;
329
+ while (true) {
330
+ const products = await fetchPaginatedProductsFromCollection(
331
+ sanitizedHandle,
332
+ {
333
+ page: currentPage,
334
+ limit
335
+ }
336
+ );
337
+ if (!products || products.length === 0 || products.length < limit) {
338
+ if (products && products.length > 0) {
339
+ allProducts.push(...products);
340
+ }
341
+ break;
342
+ }
343
+ allProducts.push(...products);
344
+ currentPage++;
345
+ }
346
+ return maybeOverrideProductsCurrency(allProducts, options == null ? void 0 : options.currency);
347
+ } catch (error) {
348
+ console.error(
349
+ `Error fetching all products for collection ${sanitizedHandle}:`,
350
+ baseUrl,
351
+ error
352
+ );
353
+ return null;
354
+ }
355
+ },
356
+ /**
357
+ * Fetches all product slugs from a specific collection.
358
+ *
359
+ * @param collectionHandle - The collection handle to fetch product slugs from
360
+ *
361
+ * @returns {Promise<string[] | null>} Array of product slugs from the collection or null if error occurs
362
+ *
363
+ * @throws {Error} When the collection handle is invalid or there's a network error
364
+ *
365
+ * @example
366
+ * ```typescript
367
+ * const shop = new ShopClient('https://exampleshop.com');
368
+ * const productSlugs = await shop.collections.products.slugs('summer-collection');
369
+ * console.log(productSlugs);
370
+ * ```
371
+ */
372
+ slugs: async (collectionHandle) => {
373
+ if (!collectionHandle || typeof collectionHandle !== "string") {
374
+ throw new Error("Collection handle is required and must be a string");
375
+ }
376
+ const sanitizedHandle = collectionHandle.trim().replace(/[^a-zA-Z0-9\-_]/g, "");
377
+ if (!sanitizedHandle) {
378
+ throw new Error("Invalid collection handle format");
379
+ }
380
+ if (sanitizedHandle.length > 255) {
381
+ throw new Error("Collection handle is too long");
382
+ }
383
+ try {
384
+ const limit = 250;
385
+ const slugs = [];
386
+ let currentPage = 1;
387
+ while (true) {
388
+ const products = await fetchPaginatedProductsFromCollection(
389
+ sanitizedHandle,
390
+ {
391
+ page: currentPage,
392
+ limit
393
+ }
394
+ );
395
+ if (!products || products.length === 0 || products.length < limit) {
396
+ if (products && products.length > 0) {
397
+ slugs.push(...products.map((p) => p.slug));
398
+ }
399
+ break;
400
+ }
401
+ slugs.push(...products.map((p) => p.slug));
402
+ currentPage++;
403
+ }
404
+ return slugs;
405
+ } catch (error) {
406
+ console.error(
407
+ `Error fetching product slugs for collection ${sanitizedHandle}:`,
408
+ baseUrl,
409
+ error
410
+ );
411
+ return null;
412
+ }
413
+ }
414
+ }
415
+ };
416
+ }
417
+
418
+ export {
419
+ createCollectionOperations
420
+ };
@@ -0,0 +1,274 @@
1
+ // src/utils/detect-country.ts
2
+ var COUNTRY_CODES = {
3
+ "+1": "US",
4
+ // United States (primary) / Canada also uses +1
5
+ "+44": "GB",
6
+ // United Kingdom
7
+ "+61": "AU",
8
+ // Australia
9
+ "+65": "SG",
10
+ // Singapore
11
+ "+91": "IN",
12
+ // India
13
+ "+81": "JP",
14
+ // Japan
15
+ "+49": "DE",
16
+ // Germany
17
+ "+33": "FR",
18
+ // France
19
+ "+971": "AE",
20
+ // United Arab Emirates
21
+ "+39": "IT",
22
+ // Italy
23
+ "+34": "ES",
24
+ // Spain
25
+ "+82": "KR",
26
+ // South Korea
27
+ "+55": "BR",
28
+ // Brazil
29
+ "+62": "ID",
30
+ // Indonesia
31
+ "+92": "PK",
32
+ // Pakistan
33
+ "+7": "RU"
34
+ // Russia
35
+ };
36
+ var CURRENCY_SYMBOLS = {
37
+ Rs: "IN",
38
+ // India
39
+ "\u20B9": "IN",
40
+ // India
41
+ $: "US",
42
+ // United States (primary, though many countries use $)
43
+ CA$: "CA",
44
+ // Canada
45
+ A$: "AU",
46
+ // Australia
47
+ "\xA3": "GB",
48
+ // United Kingdom
49
+ "\u20AC": "EU",
50
+ // European Union (not a country code, but commonly used)
51
+ AED: "AE",
52
+ // United Arab Emirates
53
+ "\u20A9": "KR",
54
+ // South Korea
55
+ "\xA5": "JP"
56
+ // Japan (primary, though China also uses ¥)
57
+ };
58
+ var CURRENCY_SYMBOL_TO_CODE = {
59
+ Rs: "INR",
60
+ "\u20B9": "INR",
61
+ $: "USD",
62
+ CA$: "CAD",
63
+ A$: "AUD",
64
+ "\xA3": "GBP",
65
+ "\u20AC": "EUR",
66
+ AED: "AED",
67
+ "\u20A9": "KRW",
68
+ "\xA5": "JPY"
69
+ };
70
+ var CURRENCY_CODE_TO_COUNTRY = {
71
+ INR: "IN",
72
+ USD: "US",
73
+ CAD: "CA",
74
+ AUD: "AU",
75
+ GBP: "GB",
76
+ EUR: "EU",
77
+ AED: "AE",
78
+ KRW: "KR",
79
+ JPY: "JP"
80
+ };
81
+ function scoreCountry(countryScores, country, weight, reason) {
82
+ if (!country) return;
83
+ if (!countryScores[country])
84
+ countryScores[country] = { score: 0, reasons: [] };
85
+ countryScores[country].score += weight;
86
+ countryScores[country].reasons.push(reason);
87
+ }
88
+ async function detectShopCountry(html) {
89
+ var _a, _b;
90
+ const countryScores = {};
91
+ let detectedCurrencyCode;
92
+ const shopifyFeaturesMatch = html.match(
93
+ /<script[^>]+id=["']shopify-features["'][^>]*>([\s\S]*?)<\/script>/
94
+ );
95
+ if (shopifyFeaturesMatch) {
96
+ try {
97
+ const json = shopifyFeaturesMatch[1];
98
+ if (!json) {
99
+ } else {
100
+ const data = JSON.parse(json);
101
+ if (data.country)
102
+ scoreCountry(
103
+ countryScores,
104
+ data.country,
105
+ 1,
106
+ "shopify-features.country"
107
+ );
108
+ if ((_a = data.locale) == null ? void 0 : _a.includes("-")) {
109
+ const [, localeCountry] = data.locale.split("-");
110
+ if (localeCountry) {
111
+ scoreCountry(
112
+ countryScores,
113
+ localeCountry.toUpperCase(),
114
+ 0.7,
115
+ "shopify-features.locale"
116
+ );
117
+ }
118
+ }
119
+ if (data.moneyFormat) {
120
+ for (const symbol in CURRENCY_SYMBOLS) {
121
+ if (data.moneyFormat.includes(symbol)) {
122
+ const iso = CURRENCY_SYMBOLS[symbol];
123
+ if (typeof iso === "string") {
124
+ scoreCountry(countryScores, iso, 0.6, "moneyFormat symbol");
125
+ }
126
+ const code = CURRENCY_SYMBOL_TO_CODE[symbol];
127
+ if (!detectedCurrencyCode && typeof code === "string") {
128
+ detectedCurrencyCode = code;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ } catch (_error) {
135
+ }
136
+ }
137
+ const currencyJsonMatch = html.match(/Shopify\.currency\s*=\s*(\{[^}]*\})/);
138
+ if (currencyJsonMatch) {
139
+ try {
140
+ const raw = currencyJsonMatch[1];
141
+ const obj = JSON.parse(raw || "{}");
142
+ const activeCode = typeof (obj == null ? void 0 : obj.active) === "string" ? obj.active.toUpperCase() : void 0;
143
+ const iso = activeCode ? CURRENCY_CODE_TO_COUNTRY[activeCode] : void 0;
144
+ if (activeCode) {
145
+ detectedCurrencyCode = activeCode;
146
+ }
147
+ if (typeof iso === "string") {
148
+ scoreCountry(countryScores, iso, 0.8, "Shopify.currency.active");
149
+ }
150
+ } catch (_error) {
151
+ }
152
+ } else {
153
+ const currencyActiveAssignMatch = html.match(
154
+ /Shopify\.currency\.active\s*=\s*['"]([A-Za-z]{3})['"]/i
155
+ );
156
+ if (currencyActiveAssignMatch) {
157
+ const captured = currencyActiveAssignMatch[1];
158
+ const code = typeof captured === "string" ? captured.toUpperCase() : void 0;
159
+ const iso = code ? CURRENCY_CODE_TO_COUNTRY[code] : void 0;
160
+ if (code) {
161
+ detectedCurrencyCode = code;
162
+ }
163
+ if (typeof iso === "string") {
164
+ scoreCountry(countryScores, iso, 0.8, "Shopify.currency.active");
165
+ }
166
+ }
167
+ }
168
+ const shopifyCountryMatch = html.match(
169
+ /Shopify\.country\s*=\s*['"]([A-Za-z]{2})['"]/i
170
+ );
171
+ if (shopifyCountryMatch) {
172
+ const captured = shopifyCountryMatch[1];
173
+ const iso = typeof captured === "string" ? captured.toUpperCase() : void 0;
174
+ if (typeof iso === "string") {
175
+ scoreCountry(countryScores, iso, 1, "Shopify.country");
176
+ }
177
+ }
178
+ const phones = html.match(/\+\d{1,3}[\s\-()0-9]{5,}/g);
179
+ if (phones) {
180
+ for (const phone of phones) {
181
+ const prefix = (_b = phone.match(/^\+\d{1,3}/)) == null ? void 0 : _b[0];
182
+ if (prefix && COUNTRY_CODES[prefix])
183
+ scoreCountry(
184
+ countryScores,
185
+ COUNTRY_CODES[prefix],
186
+ 0.8,
187
+ `phone prefix ${prefix}`
188
+ );
189
+ }
190
+ }
191
+ const jsonLdRegex = /<script[^>]+application\/ld\+json[^>]*>(.*?)<\/script>/g;
192
+ let jsonLdMatch = jsonLdRegex.exec(html);
193
+ while (jsonLdMatch !== null) {
194
+ try {
195
+ const json = jsonLdMatch[1];
196
+ if (!json) {
197
+ } else {
198
+ const raw = JSON.parse(json);
199
+ const collectAddressCountries = (node, results = []) => {
200
+ if (Array.isArray(node)) {
201
+ for (const item of node) collectAddressCountries(item, results);
202
+ return results;
203
+ }
204
+ if (node && typeof node === "object") {
205
+ const obj = node;
206
+ const address = obj.address;
207
+ if (address && typeof address === "object") {
208
+ const country = address.addressCountry;
209
+ if (typeof country === "string") results.push(country);
210
+ }
211
+ const graph = obj["@graph"];
212
+ if (graph) collectAddressCountries(graph, results);
213
+ }
214
+ return results;
215
+ };
216
+ const countries = collectAddressCountries(raw);
217
+ for (const country of countries) {
218
+ scoreCountry(countryScores, country, 1, "JSON-LD addressCountry");
219
+ }
220
+ }
221
+ } catch (_error) {
222
+ }
223
+ jsonLdMatch = jsonLdRegex.exec(html);
224
+ }
225
+ const footerMatch = html.match(/<footer[^>]*>(.*?)<\/footer>/i);
226
+ if (footerMatch) {
227
+ const footerTextGroup = footerMatch[1];
228
+ const footerText = footerTextGroup ? footerTextGroup.toLowerCase() : "";
229
+ const countryNameToISO = {
230
+ india: "IN",
231
+ "united states": "US",
232
+ canada: "CA",
233
+ australia: "AU",
234
+ "united kingdom": "GB",
235
+ britain: "GB",
236
+ uk: "GB",
237
+ japan: "JP",
238
+ "south korea": "KR",
239
+ korea: "KR",
240
+ germany: "DE",
241
+ france: "FR",
242
+ italy: "IT",
243
+ spain: "ES",
244
+ brazil: "BR",
245
+ russia: "RU",
246
+ singapore: "SG",
247
+ indonesia: "ID",
248
+ pakistan: "PK"
249
+ };
250
+ for (const [countryName, isoCode] of Object.entries(countryNameToISO)) {
251
+ if (footerText.includes(countryName))
252
+ scoreCountry(countryScores, isoCode, 0.4, "footer mention");
253
+ }
254
+ }
255
+ const sorted = Object.entries(countryScores).sort(
256
+ (a, b) => b[1].score - a[1].score
257
+ );
258
+ const best = sorted[0];
259
+ return best ? {
260
+ country: best[0],
261
+ confidence: Math.min(1, best[1].score / 2),
262
+ signals: best[1].reasons,
263
+ currencyCode: detectedCurrencyCode
264
+ } : {
265
+ country: "Unknown",
266
+ confidence: 0,
267
+ signals: [],
268
+ currencyCode: detectedCurrencyCode
269
+ };
270
+ }
271
+
272
+ export {
273
+ detectShopCountry
274
+ };