shop-client 3.14.1 → 3.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -22
- package/dist/ai/enrich.d.ts +27 -3
- package/dist/ai/enrich.mjs +9 -1
- package/dist/{chunk-7IMI76JZ.mjs → chunk-OA76XD32.mjs} +15 -2
- package/dist/{chunk-554O5ED6.mjs → chunk-QCB3U4AO.mjs} +12 -1
- package/dist/{chunk-ZF4M6GMB.mjs → chunk-THCO3JT4.mjs} +94 -29
- package/dist/{chunk-GNIBTUEK.mjs → chunk-ZX4IG4TY.mjs} +390 -206
- package/dist/collections.d.ts +1 -1
- package/dist/collections.mjs +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.mjs +10 -6
- package/dist/products.d.ts +30 -3
- package/dist/products.mjs +1 -1
- package/dist/store.d.ts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/{types-luPg5O08.d.ts → types-BRXamZMS.d.ts} +14 -1
- package/dist/utils/detect-country.d.ts +1 -1
- package/dist/utils/func.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -409,6 +409,24 @@ const showcasedProducts = await shop.products.showcased();
|
|
|
409
409
|
|
|
410
410
|
**Returns:** `Product[]` - Array of featured products
|
|
411
411
|
|
|
412
|
+
#### `products.infoHtml(productHandle, content?)`
|
|
413
|
+
|
|
414
|
+
Fetches the extracted HTML content from the product page. This is useful for getting the main product description and content directly from the page HTML.
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
// Fetch from store
|
|
418
|
+
const html = await shop.products.infoHtml("product-handle");
|
|
419
|
+
|
|
420
|
+
// Use provided HTML
|
|
421
|
+
const htmlFromContent = await shop.products.infoHtml("product-handle", "<html>...</html>");
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Parameters:**
|
|
425
|
+
- `productHandle` (string): The product handle
|
|
426
|
+
- `content` (string, optional): HTML content to extract from. If provided, skips fetching the product page.
|
|
427
|
+
|
|
428
|
+
**Returns:** `Promise<string | null>` - Extracted HTML content or null if not found
|
|
429
|
+
|
|
412
430
|
#### `products.filter()`
|
|
413
431
|
|
|
414
432
|
Creates a map of variant options and their distinct values from all products in the store. This is useful for building filter interfaces, search facets, and product option selectors.
|
|
@@ -699,14 +717,11 @@ Determine the store’s primary verticals and target audiences using showcased p
|
|
|
699
717
|
```typescript
|
|
700
718
|
import { ShopClient } from 'shop-client';
|
|
701
719
|
|
|
702
|
-
const shop = new ShopClient('your-store-domain.com'
|
|
720
|
+
const shop = new ShopClient('your-store-domain.com', {
|
|
721
|
+
openRouter: { apiKey: 'YOUR_OPENROUTER_API_KEY', model: 'openai/gpt-4o-mini' },
|
|
722
|
+
});
|
|
703
723
|
|
|
704
724
|
const breakdown = await shop.determineStoreType({
|
|
705
|
-
// Optional: provide an OpenRouter API key for online classification
|
|
706
|
-
// Offline mode falls back to regex heuristics if no key is set
|
|
707
|
-
apiKey: process.env.OPENROUTER_API_KEY,
|
|
708
|
-
// Optional: model name when using online classification
|
|
709
|
-
model: 'openai/gpt-4o-mini',
|
|
710
725
|
// Optional: limit the number of showcased products sampled (default 10, max 50)
|
|
711
726
|
maxShowcaseProducts: 12,
|
|
712
727
|
// Note: showcased collections are not used for classification
|
|
@@ -724,34 +739,45 @@ Details:
|
|
|
724
739
|
- Uses only `product.bodyHtml` for classification (no images or external text).
|
|
725
740
|
- Samples up to `maxShowcaseProducts` from `getInfo().showcase.products`.
|
|
726
741
|
- Aggregates per-product audience/vertical into a multi-audience breakdown.
|
|
727
|
-
- If `
|
|
742
|
+
- If `openRouter.offline` is `true`, uses offline regex heuristics. Otherwise, an OpenRouter API key is required (via `ShopClient` options or `determineStoreType({ apiKey })`).
|
|
728
743
|
- Applies store-level pruning based on title/description to improve consistency.
|
|
729
744
|
|
|
730
745
|
### AI Enrichment
|
|
731
746
|
|
|
732
747
|
```typescript
|
|
733
|
-
import {
|
|
748
|
+
import { ShopClient } from 'shop-client';
|
|
749
|
+
|
|
750
|
+
const shop = new ShopClient('your-store-domain.com', {
|
|
751
|
+
openRouter: { apiKey: 'YOUR_OPENROUTER_API_KEY', model: 'openai/gpt-4o-mini' },
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Merge API (Ajax) + product page content into a clean description
|
|
755
|
+
const enrichedMarkdown = await shop.products.enriched('some-product-handle', {
|
|
756
|
+
outputFormat: 'markdown',
|
|
757
|
+
});
|
|
758
|
+
// enrichedMarkdown?.enriched_content → markdown
|
|
759
|
+
|
|
760
|
+
// Use provided content instead of fetching product page
|
|
761
|
+
const enrichedFromContent = await shop.products.enriched('some-product-handle', {
|
|
762
|
+
outputFormat: 'markdown',
|
|
763
|
+
content: '<html>...</html>',
|
|
764
|
+
});
|
|
734
765
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
title: 'Organic Cotton T-Shirt',
|
|
738
|
-
bodyHtml: '<p>Soft, breathable cotton tee</p>',
|
|
739
|
-
tags: ['organic', 'cotton', 'unisex'],
|
|
766
|
+
const enrichedJson = await shop.products.enriched('some-product-handle', {
|
|
767
|
+
outputFormat: 'json',
|
|
740
768
|
});
|
|
741
|
-
//
|
|
769
|
+
// enrichedJson?.enriched_content → JSON string (validated)
|
|
742
770
|
|
|
743
|
-
//
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
tags: ['organic', 'cotton', 'unisex'],
|
|
771
|
+
// Build prompts without calling the LLM (useful for debugging)
|
|
772
|
+
const { system, user } = await shop.products.enrichedPrompts('some-product-handle', {
|
|
773
|
+
outputFormat: 'markdown',
|
|
774
|
+
content: '<html>...</html>', // Optional content
|
|
748
775
|
});
|
|
749
|
-
// seo.metaTitle, seo.metaDescription, seo.keywords
|
|
750
776
|
```
|
|
751
777
|
|
|
752
778
|
Notes:
|
|
753
|
-
- `
|
|
754
|
-
- `
|
|
779
|
+
- `enriched()` returns `enriched_content` as either markdown or a validated JSON string (based on `outputFormat`).
|
|
780
|
+
- `enrichedPrompts()` and `classifyPrompts()` return the prompt pair without making network calls.
|
|
755
781
|
|
|
756
782
|
## 🏗️ Type Definitions
|
|
757
783
|
|
package/dist/ai/enrich.d.ts
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
import { k as ProductClassification, l as SEOContent, a as ShopifySingleProduct } from '../types-
|
|
1
|
+
import { O as OpenRouterConfig, k as ProductClassification, l as SEOContent, m as SystemUserPrompt, a as ShopifySingleProduct } from '../types-BRXamZMS.js';
|
|
2
2
|
|
|
3
|
+
declare function buildEnrichPrompt(args: {
|
|
4
|
+
bodyInput: string;
|
|
5
|
+
pageInput: string;
|
|
6
|
+
inputType: "markdown" | "html";
|
|
7
|
+
outputFormat: "markdown" | "json";
|
|
8
|
+
}): SystemUserPrompt;
|
|
9
|
+
declare function buildClassifyPrompt(productContent: string): SystemUserPrompt;
|
|
10
|
+
declare function buildEnrichPromptForProduct(domain: string, handle: string, options?: {
|
|
11
|
+
useGfm?: boolean;
|
|
12
|
+
inputType?: "markdown" | "html";
|
|
13
|
+
outputFormat?: "markdown" | "json";
|
|
14
|
+
htmlContent?: string;
|
|
15
|
+
}): Promise<SystemUserPrompt>;
|
|
16
|
+
declare function buildClassifyPromptForProduct(domain: string, handle: string, options?: {
|
|
17
|
+
useGfm?: boolean;
|
|
18
|
+
inputType?: "markdown" | "html";
|
|
19
|
+
htmlContent?: string;
|
|
20
|
+
}): Promise<SystemUserPrompt>;
|
|
3
21
|
interface EnrichedProductResult {
|
|
4
22
|
bodyHtml: string;
|
|
5
23
|
pageHtml: string;
|
|
@@ -26,7 +44,7 @@ declare function extractMainSection(html: string): string | null;
|
|
|
26
44
|
*/
|
|
27
45
|
declare function htmlToMarkdown(html: string | null, options?: {
|
|
28
46
|
useGfm?: boolean;
|
|
29
|
-
}): string
|
|
47
|
+
}): Promise<string>;
|
|
30
48
|
/**
|
|
31
49
|
* Merge the two markdown sources using OpenAI GPT
|
|
32
50
|
*/
|
|
@@ -35,6 +53,7 @@ declare function mergeWithLLM(bodyInput: string, pageInput: string, options?: {
|
|
|
35
53
|
inputType?: "markdown" | "html";
|
|
36
54
|
model?: string;
|
|
37
55
|
outputFormat?: "markdown" | "json";
|
|
56
|
+
openRouter?: OpenRouterConfig;
|
|
38
57
|
}): Promise<string>;
|
|
39
58
|
/**
|
|
40
59
|
* MAIN WORKFLOW
|
|
@@ -45,6 +64,8 @@ declare function enrichProduct(domain: string, handle: string, options?: {
|
|
|
45
64
|
inputType?: "markdown" | "html";
|
|
46
65
|
model?: string;
|
|
47
66
|
outputFormat?: "markdown" | "json";
|
|
67
|
+
openRouter?: OpenRouterConfig;
|
|
68
|
+
htmlContent?: string;
|
|
48
69
|
}): Promise<EnrichedProductResult>;
|
|
49
70
|
/**
|
|
50
71
|
* Classify product content into a three-tier hierarchy using LLM.
|
|
@@ -53,6 +74,7 @@ declare function enrichProduct(domain: string, handle: string, options?: {
|
|
|
53
74
|
declare function classifyProduct(productContent: string, options?: {
|
|
54
75
|
apiKey?: string;
|
|
55
76
|
model?: string;
|
|
77
|
+
openRouter?: OpenRouterConfig;
|
|
56
78
|
}): Promise<ProductClassification>;
|
|
57
79
|
/**
|
|
58
80
|
* Generate SEO and marketing content for a product. Returns strictly validated JSON.
|
|
@@ -66,6 +88,7 @@ declare function generateSEOContent(product: {
|
|
|
66
88
|
}, options?: {
|
|
67
89
|
apiKey?: string;
|
|
68
90
|
model?: string;
|
|
91
|
+
openRouter?: OpenRouterConfig;
|
|
69
92
|
}): Promise<SEOContent>;
|
|
70
93
|
/**
|
|
71
94
|
* Determine store type (primary vertical and audience) from store information.
|
|
@@ -87,7 +110,8 @@ declare function determineStoreType(storeInfo: {
|
|
|
87
110
|
}, options?: {
|
|
88
111
|
apiKey?: string;
|
|
89
112
|
model?: string;
|
|
113
|
+
openRouter?: OpenRouterConfig;
|
|
90
114
|
}): Promise<Partial<Record<ProductClassification["audience"], Partial<Record<ProductClassification["vertical"], string[]>>>>>;
|
|
91
115
|
declare function pruneBreakdownForSignals(breakdown: Partial<Record<ProductClassification["audience"], Partial<Record<ProductClassification["vertical"], string[]>>>>, text: string): Partial<Record<ProductClassification["audience"], Partial<Record<ProductClassification["vertical"], string[]>>>>;
|
|
92
116
|
|
|
93
|
-
export { type EnrichedProductResult, classifyProduct, determineStoreType, enrichProduct, extractMainSection, fetchAjaxProduct, fetchProductPage, generateSEOContent, htmlToMarkdown, mergeWithLLM, pruneBreakdownForSignals };
|
|
117
|
+
export { type EnrichedProductResult, buildClassifyPrompt, buildClassifyPromptForProduct, buildEnrichPrompt, buildEnrichPromptForProduct, classifyProduct, determineStoreType, enrichProduct, extractMainSection, fetchAjaxProduct, fetchProductPage, generateSEOContent, htmlToMarkdown, mergeWithLLM, pruneBreakdownForSignals };
|
package/dist/ai/enrich.mjs
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
buildClassifyPrompt,
|
|
3
|
+
buildClassifyPromptForProduct,
|
|
4
|
+
buildEnrichPrompt,
|
|
5
|
+
buildEnrichPromptForProduct,
|
|
2
6
|
classifyProduct,
|
|
3
7
|
determineStoreType,
|
|
4
8
|
enrichProduct,
|
|
@@ -9,9 +13,13 @@ import {
|
|
|
9
13
|
htmlToMarkdown,
|
|
10
14
|
mergeWithLLM,
|
|
11
15
|
pruneBreakdownForSignals
|
|
12
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-ZX4IG4TY.mjs";
|
|
13
17
|
import "../chunk-D5MTUWFO.mjs";
|
|
14
18
|
export {
|
|
19
|
+
buildClassifyPrompt,
|
|
20
|
+
buildClassifyPromptForProduct,
|
|
21
|
+
buildEnrichPrompt,
|
|
22
|
+
buildEnrichPromptForProduct,
|
|
15
23
|
classifyProduct,
|
|
16
24
|
determineStoreType,
|
|
17
25
|
enrichProduct,
|
|
@@ -171,6 +171,19 @@ async function getInfoForShop(args, options) {
|
|
|
171
171
|
(handle) => Boolean(handle)
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
|
+
const dedupeByNormalized = (arr) => {
|
|
175
|
+
var _a2;
|
|
176
|
+
const out = [];
|
|
177
|
+
const seen = /* @__PURE__ */ new Set();
|
|
178
|
+
for (const h of arr) {
|
|
179
|
+
const base = (_a2 = h == null ? void 0 : h.split("?")[0]) == null ? void 0 : _a2.replace(/^\/|\/$/g, "");
|
|
180
|
+
if (!base) continue;
|
|
181
|
+
if (seen.has(base)) continue;
|
|
182
|
+
seen.add(base);
|
|
183
|
+
out.push(base);
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
};
|
|
174
187
|
const info = {
|
|
175
188
|
name: name || slug,
|
|
176
189
|
domain: sanitizeDomain(baseUrl),
|
|
@@ -182,8 +195,8 @@ async function getInfoForShop(args, options) {
|
|
|
182
195
|
contactLinks,
|
|
183
196
|
headerLinks,
|
|
184
197
|
showcase: {
|
|
185
|
-
products:
|
|
186
|
-
collections:
|
|
198
|
+
products: dedupeByNormalized(homePageProductLinks != null ? homePageProductLinks : []),
|
|
199
|
+
collections: dedupeByNormalized(homePageCollectionLinks != null ? homePageCollectionLinks : [])
|
|
187
200
|
},
|
|
188
201
|
jsonLdData: ((_p = (_o = html.match(
|
|
189
202
|
/<script[^>]*type="application\/ld\+json"[^>]*>([^<]+)<\/script>/g
|
|
@@ -230,8 +230,19 @@ function createCollectionOperations(baseUrl, storeDomain, fetchCollections, coll
|
|
|
230
230
|
*/
|
|
231
231
|
showcased: async () => {
|
|
232
232
|
const storeInfo = await getStoreInfo();
|
|
233
|
+
const normalizedHandles = storeInfo.showcase.collections.map((h) => {
|
|
234
|
+
var _a;
|
|
235
|
+
return (_a = h.split("?")[0]) == null ? void 0 : _a.replace(/^\/|\/$/g, "");
|
|
236
|
+
}).filter((base) => Boolean(base));
|
|
237
|
+
const seen = /* @__PURE__ */ new Set();
|
|
238
|
+
const uniqueHandles = [];
|
|
239
|
+
for (const base of normalizedHandles) {
|
|
240
|
+
if (seen.has(base)) continue;
|
|
241
|
+
seen.add(base);
|
|
242
|
+
uniqueHandles.push(base);
|
|
243
|
+
}
|
|
233
244
|
const collections = await Promise.all(
|
|
234
|
-
|
|
245
|
+
uniqueHandles.map(
|
|
235
246
|
(collectionHandle) => findCollection(collectionHandle)
|
|
236
247
|
)
|
|
237
248
|
);
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
// src/products.ts
|
|
9
9
|
import { filter, isNonNullish } from "remeda";
|
|
10
|
-
function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDto, productDto, getStoreInfo, findProduct) {
|
|
10
|
+
function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDto, productDto, getStoreInfo, findProduct, ai) {
|
|
11
11
|
const cacheExpiryMs = 5 * 60 * 1e3;
|
|
12
12
|
const findCache = /* @__PURE__ */ new Map();
|
|
13
13
|
const getCached = (key) => {
|
|
@@ -242,12 +242,6 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
|
|
|
242
242
|
if (!productHandle || typeof productHandle !== "string") {
|
|
243
243
|
throw new Error("Product handle is required and must be a string");
|
|
244
244
|
}
|
|
245
|
-
const apiKey = (options == null ? void 0 : options.apiKey) || process.env.OPENROUTER_API_KEY;
|
|
246
|
-
if (!apiKey) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
"Missing OpenRouter API key. Pass options.apiKey or set OPENROUTER_API_KEY."
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
245
|
const baseProduct = await operations.find(productHandle);
|
|
252
246
|
if (!baseProduct) {
|
|
253
247
|
return null;
|
|
@@ -255,32 +249,46 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
|
|
|
255
249
|
const handle = baseProduct.handle;
|
|
256
250
|
const { enrichProduct } = await import("./ai/enrich.mjs");
|
|
257
251
|
const enriched = await enrichProduct(storeDomain, handle, {
|
|
258
|
-
apiKey,
|
|
252
|
+
apiKey: options == null ? void 0 : options.apiKey,
|
|
253
|
+
openRouter: ai == null ? void 0 : ai.openRouter,
|
|
259
254
|
useGfm: options == null ? void 0 : options.useGfm,
|
|
260
255
|
inputType: options == null ? void 0 : options.inputType,
|
|
261
256
|
model: options == null ? void 0 : options.model,
|
|
262
|
-
outputFormat: options == null ? void 0 : options.outputFormat
|
|
257
|
+
outputFormat: options == null ? void 0 : options.outputFormat,
|
|
258
|
+
htmlContent: options == null ? void 0 : options.content
|
|
263
259
|
});
|
|
264
260
|
return {
|
|
265
261
|
...baseProduct,
|
|
266
262
|
enriched_content: enriched.mergedMarkdown
|
|
267
263
|
};
|
|
268
264
|
},
|
|
269
|
-
|
|
265
|
+
enrichedPrompts: async (productHandle, options) => {
|
|
270
266
|
if (!productHandle || typeof productHandle !== "string") {
|
|
271
267
|
throw new Error("Product handle is required and must be a string");
|
|
272
268
|
}
|
|
273
|
-
const
|
|
274
|
-
if (!
|
|
275
|
-
throw new Error(
|
|
276
|
-
|
|
277
|
-
|
|
269
|
+
const baseProduct = await operations.find(productHandle);
|
|
270
|
+
if (!baseProduct) {
|
|
271
|
+
throw new Error("Product not found");
|
|
272
|
+
}
|
|
273
|
+
const handle = baseProduct.handle;
|
|
274
|
+
const { buildEnrichPromptForProduct } = await import("./ai/enrich.mjs");
|
|
275
|
+
return buildEnrichPromptForProduct(storeDomain, handle, {
|
|
276
|
+
useGfm: options == null ? void 0 : options.useGfm,
|
|
277
|
+
inputType: options == null ? void 0 : options.inputType,
|
|
278
|
+
outputFormat: options == null ? void 0 : options.outputFormat,
|
|
279
|
+
htmlContent: options == null ? void 0 : options.content
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
classify: async (productHandle, options) => {
|
|
283
|
+
if (!productHandle || typeof productHandle !== "string") {
|
|
284
|
+
throw new Error("Product handle is required and must be a string");
|
|
278
285
|
}
|
|
279
286
|
const enrichedProduct = await operations.enriched(productHandle, {
|
|
280
|
-
apiKey,
|
|
287
|
+
apiKey: options == null ? void 0 : options.apiKey,
|
|
281
288
|
inputType: "html",
|
|
282
289
|
model: options == null ? void 0 : options.model,
|
|
283
|
-
outputFormat: "json"
|
|
290
|
+
outputFormat: "json",
|
|
291
|
+
content: options == null ? void 0 : options.content
|
|
284
292
|
});
|
|
285
293
|
if (!enrichedProduct || !enrichedProduct.enriched_content) return null;
|
|
286
294
|
let productContent = enrichedProduct.enriched_content;
|
|
@@ -304,20 +312,31 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
|
|
|
304
312
|
}
|
|
305
313
|
const { classifyProduct } = await import("./ai/enrich.mjs");
|
|
306
314
|
const classification = await classifyProduct(productContent, {
|
|
307
|
-
apiKey,
|
|
315
|
+
apiKey: options == null ? void 0 : options.apiKey,
|
|
316
|
+
openRouter: ai == null ? void 0 : ai.openRouter,
|
|
308
317
|
model: options == null ? void 0 : options.model
|
|
309
318
|
});
|
|
310
319
|
return classification;
|
|
311
320
|
},
|
|
312
|
-
|
|
321
|
+
classifyPrompts: async (productHandle, options) => {
|
|
313
322
|
if (!productHandle || typeof productHandle !== "string") {
|
|
314
323
|
throw new Error("Product handle is required and must be a string");
|
|
315
324
|
}
|
|
316
|
-
const
|
|
317
|
-
if (!
|
|
318
|
-
throw new Error(
|
|
319
|
-
|
|
320
|
-
|
|
325
|
+
const baseProduct = await operations.find(productHandle);
|
|
326
|
+
if (!baseProduct) {
|
|
327
|
+
throw new Error("Product not found");
|
|
328
|
+
}
|
|
329
|
+
const handle = baseProduct.handle;
|
|
330
|
+
const { buildClassifyPromptForProduct } = await import("./ai/enrich.mjs");
|
|
331
|
+
return buildClassifyPromptForProduct(storeDomain, handle, {
|
|
332
|
+
useGfm: options == null ? void 0 : options.useGfm,
|
|
333
|
+
inputType: options == null ? void 0 : options.inputType,
|
|
334
|
+
htmlContent: options == null ? void 0 : options.content
|
|
335
|
+
});
|
|
336
|
+
},
|
|
337
|
+
generateSEOContent: async (productHandle, options) => {
|
|
338
|
+
if (!productHandle || typeof productHandle !== "string") {
|
|
339
|
+
throw new Error("Product handle is required and must be a string");
|
|
321
340
|
}
|
|
322
341
|
const baseProduct = await operations.find(productHandle);
|
|
323
342
|
if (!baseProduct) return null;
|
|
@@ -328,13 +347,50 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
|
|
|
328
347
|
price: baseProduct.price,
|
|
329
348
|
tags: baseProduct.tags
|
|
330
349
|
};
|
|
331
|
-
const {
|
|
350
|
+
const {
|
|
351
|
+
extractMainSection,
|
|
352
|
+
fetchAjaxProduct,
|
|
353
|
+
fetchProductPage,
|
|
354
|
+
generateSEOContent: generateSEOContentLLM,
|
|
355
|
+
mergeWithLLM
|
|
356
|
+
} = await import("./ai/enrich.mjs");
|
|
332
357
|
const seo = await generateSEOContentLLM(payload, {
|
|
333
|
-
apiKey,
|
|
358
|
+
apiKey: options == null ? void 0 : options.apiKey,
|
|
359
|
+
openRouter: ai == null ? void 0 : ai.openRouter,
|
|
334
360
|
model: options == null ? void 0 : options.model
|
|
335
361
|
});
|
|
336
362
|
return seo;
|
|
337
363
|
},
|
|
364
|
+
/**
|
|
365
|
+
* Fetches the extracted HTML content from the product page.
|
|
366
|
+
* This is useful for getting the main product description and content directly from the page HTML.
|
|
367
|
+
*
|
|
368
|
+
* @param productHandle - The handle of the product
|
|
369
|
+
* @param content - Optional HTML content to extract from. If provided, skips fetching the product page.
|
|
370
|
+
* @returns {Promise<string | null>} The extracted HTML content or null if not found
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* // Fetch from store
|
|
375
|
+
* const html = await shop.products.infoHtml("product-handle");
|
|
376
|
+
*
|
|
377
|
+
* // Use provided HTML
|
|
378
|
+
* const htmlFromContent = await shop.products.infoHtml("product-handle", "<html>...</html>");
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
infoHtml: async (productHandle, content) => {
|
|
382
|
+
if (!productHandle || typeof productHandle !== "string") {
|
|
383
|
+
throw new Error("Product handle is required and must be a string");
|
|
384
|
+
}
|
|
385
|
+
const { extractMainSection, fetchProductPage } = await import("./ai/enrich.mjs");
|
|
386
|
+
if (content) {
|
|
387
|
+
return extractMainSection(content);
|
|
388
|
+
}
|
|
389
|
+
const baseProduct = await operations.find(productHandle);
|
|
390
|
+
if (!baseProduct) return null;
|
|
391
|
+
const pageHtml = await fetchProductPage(storeDomain, baseProduct.handle);
|
|
392
|
+
return extractMainSection(pageHtml);
|
|
393
|
+
},
|
|
338
394
|
/**
|
|
339
395
|
* Fetches products that are showcased/featured on the store's homepage.
|
|
340
396
|
*
|
|
@@ -355,10 +411,19 @@ function createProductOperations(baseUrl, storeDomain, fetchProducts, productsDt
|
|
|
355
411
|
*/
|
|
356
412
|
showcased: async () => {
|
|
357
413
|
const storeInfo = await getStoreInfo();
|
|
414
|
+
const normalizedHandles = storeInfo.showcase.products.map((h) => {
|
|
415
|
+
var _a;
|
|
416
|
+
return (_a = h.split("?")[0]) == null ? void 0 : _a.replace(/^\/|\/$/g, "");
|
|
417
|
+
}).filter((base) => Boolean(base));
|
|
418
|
+
const seen = /* @__PURE__ */ new Set();
|
|
419
|
+
const uniqueHandles = [];
|
|
420
|
+
for (const base of normalizedHandles) {
|
|
421
|
+
if (seen.has(base)) continue;
|
|
422
|
+
seen.add(base);
|
|
423
|
+
uniqueHandles.push(base);
|
|
424
|
+
}
|
|
358
425
|
const products = await Promise.all(
|
|
359
|
-
|
|
360
|
-
(productHandle) => findProduct(productHandle)
|
|
361
|
-
)
|
|
426
|
+
uniqueHandles.map((productHandle) => findProduct(productHandle))
|
|
362
427
|
);
|
|
363
428
|
return filter(products, isNonNullish);
|
|
364
429
|
},
|