@dropins/mcp 0.1.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/LICENSE.md +127 -0
- package/README.md +314 -0
- package/dist/common/project-reader.d.ts +55 -0
- package/dist/common/project-reader.js +173 -0
- package/dist/common/registry-loader.d.ts +101 -0
- package/dist/common/registry-loader.js +386 -0
- package/dist/common/response-handling.d.ts +12 -0
- package/dist/common/response-handling.js +21 -0
- package/dist/common/sanitize.d.ts +8 -0
- package/dist/common/sanitize.js +45 -0
- package/dist/common/synonyms.d.ts +9 -0
- package/dist/common/synonyms.js +127 -0
- package/dist/common/telemetry.d.ts +14 -0
- package/dist/common/telemetry.js +54 -0
- package/dist/common/types.d.ts +308 -0
- package/dist/common/types.js +1 -0
- package/dist/common/version.d.ts +2 -0
- package/dist/common/version.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +136 -0
- package/dist/operations/analyze-project.d.ts +13 -0
- package/dist/operations/analyze-project.js +125 -0
- package/dist/operations/check-block-health.d.ts +19 -0
- package/dist/operations/check-block-health.js +1149 -0
- package/dist/operations/check-config.d.ts +13 -0
- package/dist/operations/check-config.js +228 -0
- package/dist/operations/explain-event-flow.d.ts +16 -0
- package/dist/operations/explain-event-flow.js +218 -0
- package/dist/operations/get-upgrade-diff.d.ts +13 -0
- package/dist/operations/get-upgrade-diff.js +144 -0
- package/dist/operations/list-api-functions.d.ts +13 -0
- package/dist/operations/list-api-functions.js +53 -0
- package/dist/operations/list-containers.d.ts +13 -0
- package/dist/operations/list-containers.js +44 -0
- package/dist/operations/list-design-tokens.d.ts +13 -0
- package/dist/operations/list-design-tokens.js +47 -0
- package/dist/operations/list-events.d.ts +16 -0
- package/dist/operations/list-events.js +39 -0
- package/dist/operations/list-graphql-queries.d.ts +19 -0
- package/dist/operations/list-graphql-queries.js +84 -0
- package/dist/operations/list-i18n-keys.d.ts +19 -0
- package/dist/operations/list-i18n-keys.js +105 -0
- package/dist/operations/list-models.d.ts +16 -0
- package/dist/operations/list-models.js +80 -0
- package/dist/operations/list-slots.d.ts +16 -0
- package/dist/operations/list-slots.js +81 -0
- package/dist/operations/scaffold-block.d.ts +31 -0
- package/dist/operations/scaffold-block.js +331 -0
- package/dist/operations/scaffold-extension.d.ts +28 -0
- package/dist/operations/scaffold-extension.js +346 -0
- package/dist/operations/scaffold-slot.d.ts +22 -0
- package/dist/operations/scaffold-slot.js +189 -0
- package/dist/operations/search-commerce-docs.d.ts +16 -0
- package/dist/operations/search-commerce-docs.js +101 -0
- package/dist/operations/search-docs.d.ts +23 -0
- package/dist/operations/search-docs.js +298 -0
- package/dist/operations/suggest-event-handler.d.ts +16 -0
- package/dist/operations/suggest-event-handler.js +175 -0
- package/dist/operations/suggest-slot-implementation.d.ts +19 -0
- package/dist/operations/suggest-slot-implementation.js +183 -0
- package/dist/registry/api-functions.json +3045 -0
- package/dist/registry/block-patterns.json +78 -0
- package/dist/registry/containers.json +2003 -0
- package/dist/registry/design-tokens.json +577 -0
- package/dist/registry/docs/boilerplate.json +55 -0
- package/dist/registry/docs/dropins-all.json +97 -0
- package/dist/registry/docs/dropins-b2b.json +607 -0
- package/dist/registry/docs/dropins-cart.json +163 -0
- package/dist/registry/docs/dropins-checkout.json +193 -0
- package/dist/registry/docs/dropins-order.json +139 -0
- package/dist/registry/docs/dropins-payment-services.json +73 -0
- package/dist/registry/docs/dropins-personalization.json +67 -0
- package/dist/registry/docs/dropins-product-details.json +139 -0
- package/dist/registry/docs/dropins-product-discovery.json +85 -0
- package/dist/registry/docs/dropins-recommendations.json +67 -0
- package/dist/registry/docs/dropins-user-account.json +121 -0
- package/dist/registry/docs/dropins-user-auth.json +103 -0
- package/dist/registry/docs/dropins-wishlist.json +85 -0
- package/dist/registry/docs/get-started.json +85 -0
- package/dist/registry/docs/how-tos.json +19 -0
- package/dist/registry/docs/index.json +139 -0
- package/dist/registry/docs/licensing.json +19 -0
- package/dist/registry/docs/merchants.json +523 -0
- package/dist/registry/docs/resources.json +13 -0
- package/dist/registry/docs/sdk.json +139 -0
- package/dist/registry/docs/setup.json +145 -0
- package/dist/registry/docs/troubleshooting.json +19 -0
- package/dist/registry/events.json +2200 -0
- package/dist/registry/examples/index.json +19 -0
- package/dist/registry/examples/storefront-checkout.json +377 -0
- package/dist/registry/examples/storefront-quote-management.json +49 -0
- package/dist/registry/extensions.json +272 -0
- package/dist/registry/graphql.json +3469 -0
- package/dist/registry/i18n.json +1873 -0
- package/dist/registry/models.json +1001 -0
- package/dist/registry/sdk.json +2357 -0
- package/dist/registry/slots.json +2270 -0
- package/dist/registry/tools-components.json +595 -0
- package/dist/resources/guides.d.ts +7 -0
- package/dist/resources/guides.js +625 -0
- package/dist/resources/handlers.d.ts +31 -0
- package/dist/resources/handlers.js +322 -0
- package/package.json +47 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const SearchDocsSchema: z.ZodObject<{
|
|
3
|
+
query: z.ZodString;
|
|
4
|
+
section: z.ZodOptional<z.ZodString>;
|
|
5
|
+
maxResults: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
includeContent: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
}, "strip", z.ZodTypeAny, {
|
|
8
|
+
query: string;
|
|
9
|
+
section?: string | undefined;
|
|
10
|
+
maxResults?: number | undefined;
|
|
11
|
+
includeContent?: boolean | undefined;
|
|
12
|
+
}, {
|
|
13
|
+
query: string;
|
|
14
|
+
section?: string | undefined;
|
|
15
|
+
maxResults?: number | undefined;
|
|
16
|
+
includeContent?: boolean | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export declare function findWordAnchor(lowerContent: string, word: string): number;
|
|
19
|
+
export declare function searchDocsOperation(params: z.infer<typeof SearchDocsSchema>): Promise<{
|
|
20
|
+
success: boolean;
|
|
21
|
+
message: string;
|
|
22
|
+
data: unknown;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatSuccessResponse, formatExceptionResponse, } from "../common/response-handling.js";
|
|
3
|
+
import { DOCS_BASE_URL } from "../common/version.js";
|
|
4
|
+
import { searchDocs, getDocsIndex, getDocsSection, searchExamples, getExamplesByDropin, searchSdk, searchGraphql, tokenizeQuery, scoreItem, topResults, } from "../common/registry-loader.js";
|
|
5
|
+
import { expandQuery } from "../common/synonyms.js";
|
|
6
|
+
function toDocsUrl(path) {
|
|
7
|
+
return `${DOCS_BASE_URL}/${path}`;
|
|
8
|
+
}
|
|
9
|
+
function toRelevance(score) {
|
|
10
|
+
if (score >= 15)
|
|
11
|
+
return "high";
|
|
12
|
+
if (score >= 5)
|
|
13
|
+
return "medium";
|
|
14
|
+
return "low";
|
|
15
|
+
}
|
|
16
|
+
export const SearchDocsSchema = z.object({
|
|
17
|
+
query: z
|
|
18
|
+
.string()
|
|
19
|
+
.trim()
|
|
20
|
+
.min(1, { message: "query must not be empty" })
|
|
21
|
+
.describe('Search query (e.g. "CORS setup", "cart slots", "payment method integration")'),
|
|
22
|
+
section: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Limit search to a specific section (e.g. "setup", "dropins/cart", "merchants")'),
|
|
26
|
+
maxResults: z
|
|
27
|
+
.number()
|
|
28
|
+
.int()
|
|
29
|
+
.min(1, { message: "maxResults must be a positive integer" })
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Maximum total results to return across all sources combined (default: 10). Each source is searched independently and the top results are selected from the combined pool."),
|
|
32
|
+
includeContent: z
|
|
33
|
+
.boolean()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Include full page content in results (default: false, returns snippets)"),
|
|
36
|
+
});
|
|
37
|
+
export function findWordAnchor(lowerContent, word) {
|
|
38
|
+
try {
|
|
39
|
+
const escaped = word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40
|
+
const m = new RegExp(`\\b${escaped}\\b`).exec(lowerContent);
|
|
41
|
+
return m ? m.index : -1;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return lowerContent.indexOf(word);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function extractSnippet(content, query, contextChars = 200, extraWords = []) {
|
|
48
|
+
const lowerContent = content.toLowerCase();
|
|
49
|
+
const lowerQuery = query.toLowerCase();
|
|
50
|
+
const idx = lowerContent.indexOf(lowerQuery);
|
|
51
|
+
if (idx === -1) {
|
|
52
|
+
const words = [
|
|
53
|
+
...lowerQuery.split(/\s+/).filter((w) => w.length > 1),
|
|
54
|
+
...extraWords.map((w) => w.toLowerCase()),
|
|
55
|
+
];
|
|
56
|
+
for (const word of words) {
|
|
57
|
+
const wordIdx = findWordAnchor(lowerContent, word);
|
|
58
|
+
if (wordIdx !== -1) {
|
|
59
|
+
const start = Math.max(0, wordIdx - contextChars / 2);
|
|
60
|
+
const end = Math.min(content.length, wordIdx + word.length + contextChars / 2);
|
|
61
|
+
return ((start > 0 ? "..." : "") +
|
|
62
|
+
content.slice(start, end).trim() +
|
|
63
|
+
(end < content.length ? "..." : ""));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return (content.slice(0, contextChars) +
|
|
67
|
+
(content.length > contextChars ? "..." : ""));
|
|
68
|
+
}
|
|
69
|
+
const start = Math.max(0, idx - contextChars / 2);
|
|
70
|
+
const end = Math.min(content.length, idx + query.length + contextChars / 2);
|
|
71
|
+
return ((start > 0 ? "..." : "") +
|
|
72
|
+
content.slice(start, end).trim() +
|
|
73
|
+
(end < content.length ? "..." : ""));
|
|
74
|
+
}
|
|
75
|
+
export async function searchDocsOperation(params) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = SearchDocsSchema.safeParse(params);
|
|
78
|
+
if (!parsed.success) {
|
|
79
|
+
return formatExceptionResponse(new Error(parsed.error.issues.map((i) => i.message).join("; ")), "validating search query");
|
|
80
|
+
}
|
|
81
|
+
params = parsed.data;
|
|
82
|
+
const index = getDocsIndex();
|
|
83
|
+
const docsAvailable = !!(index.sections && index.sections.length > 0);
|
|
84
|
+
const maxResults = params.maxResults ?? 10;
|
|
85
|
+
const includeContent = params.includeContent ?? false;
|
|
86
|
+
const { extraWords, synonymsApplied } = expandQuery(params.query);
|
|
87
|
+
if (params.section) {
|
|
88
|
+
if (!docsAvailable) {
|
|
89
|
+
return formatSuccessResponse("Documentation not available — cannot search by section", {
|
|
90
|
+
query: params.query,
|
|
91
|
+
hint: 'Run "yarn generate:docs" to generate documentation, or omit the section parameter to search SDK, examples, and GraphQL.',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const sectionData = getDocsSection(params.section);
|
|
95
|
+
if (!sectionData) {
|
|
96
|
+
const available = index.sections.map((s) => s.section);
|
|
97
|
+
return formatSuccessResponse(`Section "${params.section}" not found`, {
|
|
98
|
+
availableSections: available,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const sectionEntry = index.sections.find((s) => s.section === params.section);
|
|
102
|
+
const sectionLabel = sectionEntry?.label ?? params.section;
|
|
103
|
+
const { lowerQuery, words } = tokenizeQuery(params.query, extraWords);
|
|
104
|
+
const scored = topResults(sectionData.pages
|
|
105
|
+
.map((page) => ({
|
|
106
|
+
page,
|
|
107
|
+
score: scoreItem(words, lowerQuery, [
|
|
108
|
+
{ value: page.title ?? "", wordWeight: 10, fullMatchWeight: 20 },
|
|
109
|
+
{
|
|
110
|
+
value: page.description ?? "",
|
|
111
|
+
wordWeight: 5,
|
|
112
|
+
fullMatchWeight: 10,
|
|
113
|
+
},
|
|
114
|
+
{ value: page.content ?? "", wordWeight: 1 },
|
|
115
|
+
]),
|
|
116
|
+
}))
|
|
117
|
+
.filter((r) => r.score > 0), maxResults);
|
|
118
|
+
const results = scored.map((r) => ({
|
|
119
|
+
path: r.page.path,
|
|
120
|
+
url: toDocsUrl(r.page.path),
|
|
121
|
+
title: r.page.title,
|
|
122
|
+
description: r.page.description,
|
|
123
|
+
section: params.section,
|
|
124
|
+
sectionLabel,
|
|
125
|
+
relevance: toRelevance(r.score),
|
|
126
|
+
...(includeContent
|
|
127
|
+
? { content: r.page.content }
|
|
128
|
+
: {
|
|
129
|
+
snippet: extractSnippet(r.page.content ?? "", params.query, 200, extraWords),
|
|
130
|
+
}),
|
|
131
|
+
}));
|
|
132
|
+
return formatSuccessResponse(results.length > 0
|
|
133
|
+
? `Found ${results.length} result(s) in "${sectionLabel}" for: "${params.query}"`
|
|
134
|
+
: `No results found in "${sectionLabel}" for: "${params.query}"`, {
|
|
135
|
+
query: params.query,
|
|
136
|
+
section: params.section,
|
|
137
|
+
...(synonymsApplied.length > 0 && { synonymsApplied }),
|
|
138
|
+
results,
|
|
139
|
+
note: "Registry data from dropin source code takes precedence over documentation when there are conflicts.",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const sdkResults = searchSdk(params.query, maxResults, extraWords);
|
|
143
|
+
const exampleResults = searchExamples(params.query, maxResults, extraWords);
|
|
144
|
+
const docResults = docsAvailable
|
|
145
|
+
? searchDocs(params.query, maxResults, extraWords)
|
|
146
|
+
: [];
|
|
147
|
+
const graphqlResults = searchGraphql(params.query, maxResults, extraWords);
|
|
148
|
+
const sdkEntries = sdkResults.map((r) => ({
|
|
149
|
+
source: "sdk",
|
|
150
|
+
type: r.type,
|
|
151
|
+
package: r.packageName,
|
|
152
|
+
...r.item,
|
|
153
|
+
relevance: toRelevance(r.score),
|
|
154
|
+
_score: r.score,
|
|
155
|
+
}));
|
|
156
|
+
const exampleEntries = exampleResults.map((r) => {
|
|
157
|
+
const result = {
|
|
158
|
+
source: "example",
|
|
159
|
+
type: r.type,
|
|
160
|
+
dropin: r.dropin,
|
|
161
|
+
...r.example,
|
|
162
|
+
relevance: toRelevance(r.score),
|
|
163
|
+
_score: r.score,
|
|
164
|
+
};
|
|
165
|
+
if (includeContent && r.type === "extension") {
|
|
166
|
+
const dropinData = getExamplesByDropin(r.dropin);
|
|
167
|
+
const ext = dropinData?.extensions?.find((e) => e.id === r.example.id);
|
|
168
|
+
if (ext) {
|
|
169
|
+
result.files = ext.files;
|
|
170
|
+
if (ext.sharedFiles?.length) {
|
|
171
|
+
const sharedFileContents = {};
|
|
172
|
+
for (const sf of ext.sharedFiles) {
|
|
173
|
+
if (dropinData?.sharedExtensionFiles?.[sf]) {
|
|
174
|
+
sharedFileContents[sf] = dropinData.sharedExtensionFiles[sf];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
result.sharedFileContents = sharedFileContents;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (includeContent && r.type === "block") {
|
|
182
|
+
const dropinData = getExamplesByDropin(r.dropin);
|
|
183
|
+
const block = dropinData?.blocks?.find((b) => b.name === r.example.name);
|
|
184
|
+
if (block) {
|
|
185
|
+
result.files = block.files;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (includeContent && r.type === "guide") {
|
|
189
|
+
const dropinData = getExamplesByDropin(r.dropin);
|
|
190
|
+
if (dropinData?.extensionGuide) {
|
|
191
|
+
result.content = dropinData.extensionGuide;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
});
|
|
196
|
+
const docEntries = docResults.map((r) => {
|
|
197
|
+
const sectionData = getDocsSection(r.section);
|
|
198
|
+
const fullPage = sectionData?.pages?.find((p) => p.path === r.page.path);
|
|
199
|
+
return {
|
|
200
|
+
source: "documentation",
|
|
201
|
+
path: r.page.path,
|
|
202
|
+
url: toDocsUrl(r.page.path),
|
|
203
|
+
title: r.page.title,
|
|
204
|
+
description: r.page.description,
|
|
205
|
+
section: r.section,
|
|
206
|
+
sectionLabel: r.sectionLabel,
|
|
207
|
+
relevance: toRelevance(r.score),
|
|
208
|
+
_score: r.score,
|
|
209
|
+
...(includeContent
|
|
210
|
+
? { content: fullPage?.content ?? "" }
|
|
211
|
+
: {
|
|
212
|
+
snippet: extractSnippet(fullPage?.content ?? "", params.query, 200, extraWords),
|
|
213
|
+
}),
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
const graphqlEntries = graphqlResults.map((r) => {
|
|
217
|
+
if ("fragment" in r) {
|
|
218
|
+
const fr = r;
|
|
219
|
+
return {
|
|
220
|
+
source: "graphql",
|
|
221
|
+
kind: "fragment",
|
|
222
|
+
dropin: fr.dropin,
|
|
223
|
+
name: fr.fragment.name,
|
|
224
|
+
exportName: fr.fragment.exportName,
|
|
225
|
+
file: fr.fragment.file,
|
|
226
|
+
onType: fr.fragment.onType,
|
|
227
|
+
exported: fr.fragment.exported,
|
|
228
|
+
fragmentDependencies: fr.fragment.fragmentDependencies,
|
|
229
|
+
relevance: toRelevance(fr.score),
|
|
230
|
+
_score: fr.score,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
const op = r;
|
|
234
|
+
return {
|
|
235
|
+
source: "graphql",
|
|
236
|
+
kind: "operation",
|
|
237
|
+
dropin: op.dropin,
|
|
238
|
+
name: op.operation.name,
|
|
239
|
+
type: op.operation.type,
|
|
240
|
+
exportName: op.operation.exportName,
|
|
241
|
+
file: op.operation.file,
|
|
242
|
+
variables: op.operation.variables,
|
|
243
|
+
fragmentDependencies: op.operation.fragmentDependencies,
|
|
244
|
+
relevance: toRelevance(op.score),
|
|
245
|
+
_score: op.score,
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
const allResults = [
|
|
249
|
+
...sdkEntries,
|
|
250
|
+
...exampleEntries,
|
|
251
|
+
...graphqlEntries,
|
|
252
|
+
...docEntries,
|
|
253
|
+
]
|
|
254
|
+
.sort((a, b) => b._score - a._score)
|
|
255
|
+
.slice(0, maxResults)
|
|
256
|
+
.map(({ _score: _s, ...rest }) => rest);
|
|
257
|
+
if (allResults.length === 0) {
|
|
258
|
+
return formatSuccessResponse(`No documentation or examples found for: "${params.query}"`, {
|
|
259
|
+
query: params.query,
|
|
260
|
+
...(docsAvailable
|
|
261
|
+
? {
|
|
262
|
+
availableSections: index.sections.map((s) => ({
|
|
263
|
+
section: s.section,
|
|
264
|
+
label: s.label,
|
|
265
|
+
pageCount: s.pageCount,
|
|
266
|
+
})),
|
|
267
|
+
}
|
|
268
|
+
: {
|
|
269
|
+
hint: 'Documentation snapshot not available. Run "yarn generate:docs" to enable doc search. SDK, examples, and GraphQL were searched.',
|
|
270
|
+
}),
|
|
271
|
+
...(!docsAvailable
|
|
272
|
+
? {}
|
|
273
|
+
: {
|
|
274
|
+
hint: "Try broader search terms, or browse a specific section",
|
|
275
|
+
}),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const cappedSdk = allResults.filter((r) => r.source === "sdk");
|
|
279
|
+
const cappedExamples = allResults.filter((r) => r.source === "example");
|
|
280
|
+
const cappedGraphql = allResults.filter((r) => r.source === "graphql");
|
|
281
|
+
const cappedDocs = allResults.filter((r) => r.source === "documentation");
|
|
282
|
+
return formatSuccessResponse(`Found ${allResults.length} result(s) for: "${params.query}" (${cappedSdk.length} SDK, ${cappedExamples.length} examples, ${cappedGraphql.length} GraphQL, ${cappedDocs.length} docs)`, {
|
|
283
|
+
query: params.query,
|
|
284
|
+
...(synonymsApplied.length > 0 && { synonymsApplied }),
|
|
285
|
+
...(!docsAvailable && {
|
|
286
|
+
warning: 'Documentation snapshot not available. Run "yarn generate:docs" to enable doc search.',
|
|
287
|
+
}),
|
|
288
|
+
sdk: cappedSdk,
|
|
289
|
+
examples: cappedExamples,
|
|
290
|
+
graphql: cappedGraphql,
|
|
291
|
+
documentation: cappedDocs,
|
|
292
|
+
note: "SDK results show StorefrontSDK package APIs. Examples are working implementations. GraphQL results show dropin queries/mutations. Registry data takes precedence over documentation when there are conflicts.",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
return formatExceptionResponse(error, "searching documentation");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const SuggestEventHandlerSchema: z.ZodObject<{
|
|
3
|
+
request: z.ZodString;
|
|
4
|
+
projectDir: z.ZodString;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
projectDir: string;
|
|
7
|
+
request: string;
|
|
8
|
+
}, {
|
|
9
|
+
projectDir: string;
|
|
10
|
+
request: string;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function suggestEventHandler(params: z.infer<typeof SuggestEventHandlerSchema>): Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
data: unknown;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatSuccessResponse, formatExceptionResponse, } from "../common/response-handling.js";
|
|
3
|
+
import { getEventCatalog, findModel } from "../common/registry-loader.js";
|
|
4
|
+
import { projectDirGuard, listBlocks, readBlockFile, extractImportsFromBlock, } from "../common/project-reader.js";
|
|
5
|
+
import { sanitizeForComment } from "../common/sanitize.js";
|
|
6
|
+
export const SuggestEventHandlerSchema = z.object({
|
|
7
|
+
request: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe('What the merchant wants (e.g. "Show popup when order is placed", "Refresh mini-cart when product is added")'),
|
|
10
|
+
projectDir: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe("Absolute path to the merchant storefront project root"),
|
|
13
|
+
});
|
|
14
|
+
function scoreEventRelevance(request, event) {
|
|
15
|
+
const lowerReq = request.toLowerCase();
|
|
16
|
+
const lowerName = event.name.toLowerCase();
|
|
17
|
+
const lowerDesc = (event.description ?? "").toLowerCase();
|
|
18
|
+
let score = 0;
|
|
19
|
+
const eventParts = lowerName.split("/");
|
|
20
|
+
for (const part of eventParts) {
|
|
21
|
+
if (lowerReq.includes(part))
|
|
22
|
+
score += 5;
|
|
23
|
+
}
|
|
24
|
+
if (lowerReq.includes("order") && lowerName.includes("order"))
|
|
25
|
+
score += 5;
|
|
26
|
+
if (lowerReq.includes("cart") && lowerName.includes("cart"))
|
|
27
|
+
score += 5;
|
|
28
|
+
if (lowerReq.includes("checkout") && lowerName.includes("checkout"))
|
|
29
|
+
score += 5;
|
|
30
|
+
if (lowerReq.includes("login") && lowerName.includes("authenticated"))
|
|
31
|
+
score += 5;
|
|
32
|
+
if (lowerReq.includes("auth") && lowerName.includes("authenticated"))
|
|
33
|
+
score += 5;
|
|
34
|
+
if (lowerReq.includes("add") && lowerName.includes("added"))
|
|
35
|
+
score += 4;
|
|
36
|
+
if (lowerReq.includes("update") && lowerName.includes("updated"))
|
|
37
|
+
score += 4;
|
|
38
|
+
if (lowerReq.includes("place") && lowerName.includes("placed"))
|
|
39
|
+
score += 5;
|
|
40
|
+
if (lowerReq.includes("search") && lowerName.includes("search"))
|
|
41
|
+
score += 5;
|
|
42
|
+
if (lowerReq.includes("product") && lowerName.includes("product"))
|
|
43
|
+
score += 3;
|
|
44
|
+
if (lowerReq.includes("wishlist") && lowerName.includes("wishlist"))
|
|
45
|
+
score += 5;
|
|
46
|
+
if (lowerReq.includes("quote") && lowerName.includes("quote"))
|
|
47
|
+
score += 5;
|
|
48
|
+
const reqWords = lowerReq.split(/\s+/);
|
|
49
|
+
for (const word of reqWords) {
|
|
50
|
+
if (word.length > 3 && lowerDesc.includes(word))
|
|
51
|
+
score += 2;
|
|
52
|
+
}
|
|
53
|
+
return score;
|
|
54
|
+
}
|
|
55
|
+
function generateEventHandlerCode(event, request) {
|
|
56
|
+
const payloadVar = event.payloadType?.includes("{")
|
|
57
|
+
? "data"
|
|
58
|
+
: event.payloadType?.toLowerCase().replace(/\s+/g, "") || "payload";
|
|
59
|
+
return `import { events } from '@dropins/tools/event-bus.js';
|
|
60
|
+
|
|
61
|
+
// ${sanitizeForComment(request)}
|
|
62
|
+
events.on('${event.name}', (${payloadVar}) => {
|
|
63
|
+
// Payload type: ${event.payloadType}
|
|
64
|
+
// ${sanitizeForComment(event.description ?? "")}
|
|
65
|
+
|
|
66
|
+
// Implement your handler logic here
|
|
67
|
+
console.log('${event.name} received:', ${payloadVar});
|
|
68
|
+
});`;
|
|
69
|
+
}
|
|
70
|
+
function partitionEventRefs(refs) {
|
|
71
|
+
const boilerplateBlocks = [];
|
|
72
|
+
const storefrontDropins = [];
|
|
73
|
+
for (const { dropin } of refs) {
|
|
74
|
+
if (dropin.startsWith("boilerplate/")) {
|
|
75
|
+
boilerplateBlocks.push(dropin.replace("boilerplate/", ""));
|
|
76
|
+
}
|
|
77
|
+
else if (dropin.startsWith("storefront-")) {
|
|
78
|
+
storefrontDropins.push(dropin);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { boilerplateBlocks, storefrontDropins };
|
|
82
|
+
}
|
|
83
|
+
function blocksImportingDropins(dropins, blockImportMap) {
|
|
84
|
+
const result = [];
|
|
85
|
+
for (const [blockName, imports] of blockImportMap) {
|
|
86
|
+
if (imports.some((p) => dropins.some((d) => p.includes(`@dropins/${d}/`)))) {
|
|
87
|
+
result.push(blockName);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
function suggestPlacement(event, blockImportMap) {
|
|
93
|
+
const allRefs = [...(event.emittedBy ?? []), ...(event.consumedBy ?? [])];
|
|
94
|
+
const { boilerplateBlocks, storefrontDropins } = partitionEventRefs(allRefs);
|
|
95
|
+
const presentBoilerplateBlocks = [...new Set(boilerplateBlocks)].filter((name) => blockImportMap.has(name));
|
|
96
|
+
if (presentBoilerplateBlocks.length > 0) {
|
|
97
|
+
return `Place in one of: ${presentBoilerplateBlocks.join(", ")} (known integration block for this event)`;
|
|
98
|
+
}
|
|
99
|
+
const importingBlocks = blocksImportingDropins([...new Set(storefrontDropins)], blockImportMap);
|
|
100
|
+
if (importingBlocks.length > 0) {
|
|
101
|
+
return `Place in one of: ${importingBlocks.join(", ")} (already imports the related dropin)`;
|
|
102
|
+
}
|
|
103
|
+
const uniqueBoilerplateBlocks = [...new Set(boilerplateBlocks)];
|
|
104
|
+
if (uniqueBoilerplateBlocks.length > 0) {
|
|
105
|
+
return `Consider adding this handler in: ${uniqueBoilerplateBlocks.join(", ")}`;
|
|
106
|
+
}
|
|
107
|
+
return "Place in your main commerce block or a shared script file";
|
|
108
|
+
}
|
|
109
|
+
export async function suggestEventHandler(params) {
|
|
110
|
+
try {
|
|
111
|
+
const guard = projectDirGuard(params.projectDir);
|
|
112
|
+
if (guard)
|
|
113
|
+
return guard;
|
|
114
|
+
const catalog = getEventCatalog();
|
|
115
|
+
const allBlocks = listBlocks(params.projectDir);
|
|
116
|
+
const blockImportMap = new Map();
|
|
117
|
+
const existingEventUsage = [];
|
|
118
|
+
for (const block of allBlocks) {
|
|
119
|
+
const content = readBlockFile(params.projectDir, block.name);
|
|
120
|
+
if (!content)
|
|
121
|
+
continue;
|
|
122
|
+
const imports = extractImportsFromBlock(content);
|
|
123
|
+
blockImportMap.set(block.name, imports.dropinImports);
|
|
124
|
+
if (imports.eventImports) {
|
|
125
|
+
existingEventUsage.push({ block: block.name, usesEventBus: true });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const scored = (catalog.events ?? [])
|
|
129
|
+
.map((event) => ({
|
|
130
|
+
event,
|
|
131
|
+
score: scoreEventRelevance(params.request, event),
|
|
132
|
+
}))
|
|
133
|
+
.filter(({ score }) => score > 0)
|
|
134
|
+
.sort((a, b) => b.score - a.score);
|
|
135
|
+
const suggestions = scored
|
|
136
|
+
.slice(0, 5)
|
|
137
|
+
.map(({ event, score }) => ({
|
|
138
|
+
eventName: event.name,
|
|
139
|
+
payloadType: event.payloadType,
|
|
140
|
+
description: event.description,
|
|
141
|
+
relevance: score >= 8 ? "high" : score >= 4 ? "medium" : "low",
|
|
142
|
+
codeSnippet: generateEventHandlerCode(event, params.request),
|
|
143
|
+
placementSuggestion: suggestPlacement(event, blockImportMap),
|
|
144
|
+
}));
|
|
145
|
+
const resolvedTypes = {};
|
|
146
|
+
for (const suggestion of suggestions) {
|
|
147
|
+
const rawType = suggestion.payloadType;
|
|
148
|
+
if (!rawType || rawType.includes("{"))
|
|
149
|
+
continue;
|
|
150
|
+
const namedTypes = rawType.match(/\b[A-Z][A-Za-z0-9]+\b/g) ?? [];
|
|
151
|
+
for (const typeName of namedTypes) {
|
|
152
|
+
if (typeName in resolvedTypes)
|
|
153
|
+
continue;
|
|
154
|
+
const matches = findModel(typeName);
|
|
155
|
+
if (matches.length > 0) {
|
|
156
|
+
resolvedTypes[typeName] = matches[0].definition;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return formatSuccessResponse(suggestions.length > 0
|
|
161
|
+
? `Found ${suggestions.length} event suggestion(s) for: "${params.request}"`
|
|
162
|
+
: `No matching events found for: "${params.request}"`, {
|
|
163
|
+
request: params.request,
|
|
164
|
+
suggestions,
|
|
165
|
+
existingEventUsage,
|
|
166
|
+
...(Object.keys(resolvedTypes).length > 0 && { resolvedTypes }),
|
|
167
|
+
hint: suggestions.length === 0
|
|
168
|
+
? "Try rephrasing your request, or use list_events to browse all available events"
|
|
169
|
+
: undefined,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
return formatExceptionResponse(error, "suggesting event handler");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const SuggestSlotSchema: z.ZodObject<{
|
|
3
|
+
request: z.ZodString;
|
|
4
|
+
blockName: z.ZodString;
|
|
5
|
+
projectDir: z.ZodString;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
projectDir: string;
|
|
8
|
+
blockName: string;
|
|
9
|
+
request: string;
|
|
10
|
+
}, {
|
|
11
|
+
projectDir: string;
|
|
12
|
+
blockName: string;
|
|
13
|
+
request: string;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function suggestSlotImplementation(params: z.infer<typeof SuggestSlotSchema>): Promise<{
|
|
16
|
+
success: boolean;
|
|
17
|
+
message: string;
|
|
18
|
+
data: unknown;
|
|
19
|
+
}>;
|