@anton.andrusenko/shopify-mcp-admin 2.3.0 → 2.4.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/dist/{chunk-JU5IFCVJ.js → chunk-CJXPHNYT.js} +36 -1
- package/dist/chunk-CZJ7LSEO.js +251 -0
- package/dist/{chunk-EQUN4XCH.js → chunk-H36XQ6QK.js} +1 -1
- package/dist/{chunk-RBXQOPVF.js → chunk-UMNIRP6T.js} +16 -135
- package/dist/dashboard/assets/index-DVjSu1HI.js +130 -0
- package/dist/dashboard/assets/index-DVjSu1HI.js.map +1 -0
- package/dist/dashboard/assets/index-DlTP0Kre.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +842 -185
- package/dist/{mcp-auth-CWOWKID3.js → mcp-auth-54BVOYFJ.js} +2 -2
- package/dist/{security-44M6F2QU.js → security-6CNKRY2G.js} +4 -1
- package/dist/{store-JK2ZU6DR.js → store-5NJBYK45.js} +2 -2
- package/dist/{tools-BCI3Z2AW.js → tools-SVKPHJYW.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-5QMYOO4B.js +0 -146
- package/dist/dashboard/assets/index-ClITn1me.css +0 -1
- package/dist/dashboard/assets/index-Cvo1L2xM.js +0 -126
- package/dist/dashboard/assets/index-Cvo1L2xM.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
log
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CZJ7LSEO.js";
|
|
4
4
|
|
|
5
5
|
// src/db/client.ts
|
|
6
6
|
import { PrismaClient } from "@prisma/client";
|
|
@@ -11,10 +11,44 @@ function getPrismaClient() {
|
|
|
11
11
|
log.debug("Creating new Prisma client instance");
|
|
12
12
|
prismaInstance = new PrismaClient({
|
|
13
13
|
log: process.env.DEBUG === "true" ? ["query", "info", "warn", "error"] : ["error"]
|
|
14
|
+
// Note: Connection pool settings are configured via DATABASE_URL query params:
|
|
15
|
+
// - connection_limit: Max connections in pool (default: num_cpus * 2 + 1)
|
|
16
|
+
// - pool_timeout: Seconds to wait for available connection (default: 10)
|
|
17
|
+
// - connect_timeout: Seconds to wait for initial connection (default: 5)
|
|
18
|
+
// Example: postgresql://...?connection_limit=10&pool_timeout=30&connect_timeout=10
|
|
19
|
+
//
|
|
20
|
+
// For serverless databases (Neon, Supabase), also consider:
|
|
21
|
+
// - pgbouncer=true: Required if using PgBouncer connection pooler
|
|
14
22
|
});
|
|
15
23
|
}
|
|
16
24
|
return prismaInstance;
|
|
17
25
|
}
|
|
26
|
+
async function warmupDatabase() {
|
|
27
|
+
const prisma2 = getPrismaClient();
|
|
28
|
+
const maxRetries = 3;
|
|
29
|
+
const retryDelayMs = 1e3;
|
|
30
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
31
|
+
try {
|
|
32
|
+
const startTime = Date.now();
|
|
33
|
+
await prisma2.$connect();
|
|
34
|
+
await prisma2.$queryRaw`SELECT 1`;
|
|
35
|
+
const duration = Date.now() - startTime;
|
|
36
|
+
log.info(`Database warmup completed in ${duration}ms`);
|
|
37
|
+
return;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
40
|
+
if (attempt < maxRetries) {
|
|
41
|
+
log.warn(
|
|
42
|
+
`Database warmup attempt ${attempt}/${maxRetries} failed: ${message}, retrying in ${retryDelayMs}ms...`
|
|
43
|
+
);
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs * attempt));
|
|
45
|
+
} else {
|
|
46
|
+
log.error(`Database warmup failed after ${maxRetries} attempts: ${message}`);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
18
52
|
async function disconnectPrisma() {
|
|
19
53
|
if (prismaInstance) {
|
|
20
54
|
log.debug("Disconnecting Prisma client");
|
|
@@ -201,6 +235,7 @@ var sessionStore = new SessionStore();
|
|
|
201
235
|
|
|
202
236
|
export {
|
|
203
237
|
getPrismaClient,
|
|
238
|
+
warmupDatabase,
|
|
204
239
|
disconnectPrisma,
|
|
205
240
|
prisma,
|
|
206
241
|
SessionStore,
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configSchema,
|
|
3
|
+
isDebugEnabled
|
|
4
|
+
} from "./chunk-EGGOXEIC.js";
|
|
5
|
+
|
|
6
|
+
// src/config/index.ts
|
|
7
|
+
var _config = null;
|
|
8
|
+
function getConfig() {
|
|
9
|
+
if (_config !== null) {
|
|
10
|
+
return _config;
|
|
11
|
+
}
|
|
12
|
+
const result = configSchema.safeParse(process.env);
|
|
13
|
+
if (!result.success) {
|
|
14
|
+
const errors = result.error.errors.map((err) => {
|
|
15
|
+
const path = err.path.join(".");
|
|
16
|
+
return ` - ${path}: ${err.message}`;
|
|
17
|
+
});
|
|
18
|
+
console.error("Configuration error:");
|
|
19
|
+
console.error(errors.join("\n"));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
_config = result.data;
|
|
23
|
+
return _config;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/logging/sanitize.ts
|
|
27
|
+
var MAX_STRING_LENGTH = 1e3;
|
|
28
|
+
var MAX_ARRAY_ITEMS = 10;
|
|
29
|
+
var MAX_RECURSION_DEPTH = 5;
|
|
30
|
+
var MAX_OUTPUT_BYTES = 5e3;
|
|
31
|
+
var TOKEN_PATTERNS = [
|
|
32
|
+
// Shopify access tokens (shpat_xxx, shpua_xxx) - include underscores in the pattern
|
|
33
|
+
{ pattern: /shpat_[a-zA-Z0-9_]+/g, replacement: "[REDACTED]" },
|
|
34
|
+
{ pattern: /shpua_[a-zA-Z0-9_]+/g, replacement: "[REDACTED]" },
|
|
35
|
+
// Bearer tokens
|
|
36
|
+
{ pattern: /Bearer\s+[a-zA-Z0-9_.-]+/g, replacement: "Bearer [REDACTED]" },
|
|
37
|
+
// OAuth access tokens and secrets (mcp_access_xxx, mcp_secret_xxx, mcp_client_xxx)
|
|
38
|
+
{ pattern: /mcp_access_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
39
|
+
{ pattern: /mcp_secret_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
40
|
+
{ pattern: /mcp_client_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
41
|
+
// Live/test API keys (sk_live_xxx, sk_test_xxx)
|
|
42
|
+
{ pattern: /sk_live_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
43
|
+
{ pattern: /sk_test_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
44
|
+
// Generic access_token and client_secret patterns
|
|
45
|
+
{ pattern: /access_token[=:]\s*[a-zA-Z0-9_.-]+/gi, replacement: "access_token=[REDACTED]" },
|
|
46
|
+
{ pattern: /client_secret[=:]\s*[a-zA-Z0-9_.-]+/gi, replacement: "client_secret=[REDACTED]" }
|
|
47
|
+
];
|
|
48
|
+
function redactTokens(text) {
|
|
49
|
+
let result = text;
|
|
50
|
+
for (const { pattern, replacement } of TOKEN_PATTERNS) {
|
|
51
|
+
result = result.replace(pattern, replacement);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
function truncateString(text, maxLength = MAX_STRING_LENGTH) {
|
|
56
|
+
if (text.length <= maxLength) {
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
return `${text.substring(0, maxLength)}...[TRUNCATED]`;
|
|
60
|
+
}
|
|
61
|
+
function sanitizeParams(params, depth = 0) {
|
|
62
|
+
if (depth > MAX_RECURSION_DEPTH) {
|
|
63
|
+
return "[TRUNCATED]";
|
|
64
|
+
}
|
|
65
|
+
if (params === null || params === void 0) {
|
|
66
|
+
return params;
|
|
67
|
+
}
|
|
68
|
+
if (typeof params === "string") {
|
|
69
|
+
const redacted = redactTokens(params);
|
|
70
|
+
return truncateString(redacted);
|
|
71
|
+
}
|
|
72
|
+
if (typeof params !== "object") {
|
|
73
|
+
return params;
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(params)) {
|
|
76
|
+
const limited = params.slice(0, MAX_ARRAY_ITEMS);
|
|
77
|
+
return limited.map((item) => sanitizeParams(item, depth + 1));
|
|
78
|
+
}
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const [key, value] of Object.entries(params)) {
|
|
81
|
+
result[key] = sanitizeParams(value, depth + 1);
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
function getByteSize(value) {
|
|
86
|
+
try {
|
|
87
|
+
return JSON.stringify(value).length;
|
|
88
|
+
} catch {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function truncateOutput(output, maxBytes = MAX_OUTPUT_BYTES) {
|
|
93
|
+
const sanitized = sanitizeParams(output);
|
|
94
|
+
const size = getByteSize(sanitized);
|
|
95
|
+
if (size <= maxBytes) {
|
|
96
|
+
return sanitized;
|
|
97
|
+
}
|
|
98
|
+
if (typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
|
|
99
|
+
const obj = sanitized;
|
|
100
|
+
if ("data" in obj || "errors" in obj) {
|
|
101
|
+
const result = {};
|
|
102
|
+
if (obj.errors) {
|
|
103
|
+
result.errors = obj.errors;
|
|
104
|
+
}
|
|
105
|
+
if (obj.data) {
|
|
106
|
+
result.data = "[TRUNCATED - output exceeded 5KB]";
|
|
107
|
+
}
|
|
108
|
+
if (obj.extensions && getByteSize(obj.extensions) < 200) {
|
|
109
|
+
result.extensions = obj.extensions;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
_truncated: true,
|
|
115
|
+
_message: `Output truncated: ${size} bytes exceeded ${maxBytes} byte limit`,
|
|
116
|
+
_keys: Object.keys(obj).slice(0, 10)
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(sanitized)) {
|
|
120
|
+
return {
|
|
121
|
+
_truncated: true,
|
|
122
|
+
_message: `Array truncated: ${sanitized.length} items, ${size} bytes exceeded ${maxBytes} byte limit`,
|
|
123
|
+
_sample: sanitized.slice(0, 3)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return "[TRUNCATED - output exceeded 5KB]";
|
|
127
|
+
}
|
|
128
|
+
function sanitizeErrorMessage(error) {
|
|
129
|
+
if (error instanceof Error) {
|
|
130
|
+
return truncateString(redactTokens(error.message), 500);
|
|
131
|
+
}
|
|
132
|
+
if (typeof error === "string") {
|
|
133
|
+
return truncateString(redactTokens(error), 500);
|
|
134
|
+
}
|
|
135
|
+
return "Unknown error";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/utils/logger.ts
|
|
139
|
+
function sanitizeLogMessage(message) {
|
|
140
|
+
return redactTokens(message);
|
|
141
|
+
}
|
|
142
|
+
function sanitizeObject(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
143
|
+
if (typeof obj === "string") {
|
|
144
|
+
return sanitizeLogMessage(obj);
|
|
145
|
+
}
|
|
146
|
+
if (obj === null || typeof obj !== "object") {
|
|
147
|
+
return obj;
|
|
148
|
+
}
|
|
149
|
+
if (seen.has(obj)) {
|
|
150
|
+
return "[Circular]";
|
|
151
|
+
}
|
|
152
|
+
seen.add(obj);
|
|
153
|
+
if (Array.isArray(obj)) {
|
|
154
|
+
return obj.map((item) => sanitizeObject(item, seen));
|
|
155
|
+
}
|
|
156
|
+
const result = {};
|
|
157
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
158
|
+
result[key] = sanitizeObject(value, seen);
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
function safeStringify(data) {
|
|
163
|
+
try {
|
|
164
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
165
|
+
return JSON.stringify(data, (_key, value) => {
|
|
166
|
+
if (typeof value === "object" && value !== null) {
|
|
167
|
+
if (seen.has(value)) {
|
|
168
|
+
return "[Circular]";
|
|
169
|
+
}
|
|
170
|
+
seen.add(value);
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
});
|
|
174
|
+
} catch {
|
|
175
|
+
return "[Unable to stringify]";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
var log = {
|
|
179
|
+
/**
|
|
180
|
+
* Debug level logging - only outputs when DEBUG=1 or DEBUG=true
|
|
181
|
+
*
|
|
182
|
+
* @param msg - Debug message
|
|
183
|
+
* @param data - Optional data object to include (JSON-stringified)
|
|
184
|
+
*/
|
|
185
|
+
debug: (msg, data) => {
|
|
186
|
+
if (isDebugEnabled(process.env.DEBUG)) {
|
|
187
|
+
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
188
|
+
if (data) {
|
|
189
|
+
const sanitizedData = sanitizeObject(data);
|
|
190
|
+
const dataStr = safeStringify(sanitizedData);
|
|
191
|
+
console.error(`[DEBUG] ${sanitizedMsg} ${dataStr}`);
|
|
192
|
+
} else {
|
|
193
|
+
console.error(`[DEBUG] ${sanitizedMsg}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
/**
|
|
198
|
+
* Info level logging
|
|
199
|
+
*
|
|
200
|
+
* @param msg - Info message
|
|
201
|
+
* @param data - Optional data object to include (JSON-stringified)
|
|
202
|
+
*/
|
|
203
|
+
info: (msg, data) => {
|
|
204
|
+
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
205
|
+
if (data) {
|
|
206
|
+
const sanitizedData = sanitizeObject(data);
|
|
207
|
+
const dataStr = safeStringify(sanitizedData);
|
|
208
|
+
console.error(`[INFO] ${sanitizedMsg} ${dataStr}`);
|
|
209
|
+
} else {
|
|
210
|
+
console.error(`[INFO] ${sanitizedMsg}`);
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
/**
|
|
214
|
+
* Warning level logging
|
|
215
|
+
*
|
|
216
|
+
* @param msg - Warning message
|
|
217
|
+
* @param data - Optional data object to include (JSON-stringified)
|
|
218
|
+
*/
|
|
219
|
+
warn: (msg, data) => {
|
|
220
|
+
const sanitizedMsg = sanitizeLogMessage(msg);
|
|
221
|
+
if (data) {
|
|
222
|
+
const sanitizedData = sanitizeObject(data);
|
|
223
|
+
const dataStr = safeStringify(sanitizedData);
|
|
224
|
+
console.error(`[WARN] ${sanitizedMsg} ${dataStr}`);
|
|
225
|
+
} else {
|
|
226
|
+
console.error(`[WARN] ${sanitizedMsg}`);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
/**
|
|
230
|
+
* Error level logging
|
|
231
|
+
*
|
|
232
|
+
* @param msg - Error message
|
|
233
|
+
* @param err - Optional Error object (stack trace shown when DEBUG enabled)
|
|
234
|
+
*/
|
|
235
|
+
error: (msg, err) => {
|
|
236
|
+
console.error(`[ERROR] ${sanitizeLogMessage(msg)}`);
|
|
237
|
+
if (err && isDebugEnabled(process.env.DEBUG)) {
|
|
238
|
+
console.error(sanitizeLogMessage(err.stack || err.message));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
export {
|
|
244
|
+
getConfig,
|
|
245
|
+
redactTokens,
|
|
246
|
+
sanitizeParams,
|
|
247
|
+
truncateOutput,
|
|
248
|
+
sanitizeErrorMessage,
|
|
249
|
+
sanitizeLogMessage,
|
|
250
|
+
log
|
|
251
|
+
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getConfig,
|
|
3
3
|
log,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
redactTokens,
|
|
5
|
+
sanitizeErrorMessage,
|
|
6
|
+
sanitizeLogMessage,
|
|
7
|
+
sanitizeParams,
|
|
8
|
+
truncateOutput
|
|
9
|
+
} from "./chunk-CZJ7LSEO.js";
|
|
6
10
|
import {
|
|
7
11
|
getAuthMode,
|
|
8
12
|
getConfiguredRole,
|
|
@@ -1434,12 +1438,12 @@ function initSentry(options) {
|
|
|
1434
1438
|
}
|
|
1435
1439
|
}
|
|
1436
1440
|
if (event.message) {
|
|
1437
|
-
event.message =
|
|
1441
|
+
event.message = sanitizeErrorMessage2(event.message);
|
|
1438
1442
|
}
|
|
1439
1443
|
if (event.exception?.values) {
|
|
1440
1444
|
for (const exception of event.exception.values) {
|
|
1441
1445
|
if (exception.value) {
|
|
1442
|
-
exception.value =
|
|
1446
|
+
exception.value = sanitizeErrorMessage2(exception.value);
|
|
1443
1447
|
}
|
|
1444
1448
|
}
|
|
1445
1449
|
}
|
|
@@ -1457,7 +1461,7 @@ function initSentry(options) {
|
|
|
1457
1461
|
]
|
|
1458
1462
|
});
|
|
1459
1463
|
}
|
|
1460
|
-
function
|
|
1464
|
+
function sanitizeErrorMessage2(message) {
|
|
1461
1465
|
const patterns = [
|
|
1462
1466
|
/shpat_[a-zA-Z0-9]+/g,
|
|
1463
1467
|
/shpua_[a-zA-Z0-9]+/g,
|
|
@@ -1563,20 +1567,8 @@ function captureError(error, context) {
|
|
|
1563
1567
|
}
|
|
1564
1568
|
|
|
1565
1569
|
// src/logging/structured-logger.ts
|
|
1566
|
-
var SANITIZATION_PATTERNS = [
|
|
1567
|
-
{ pattern: /shpat_[a-zA-Z0-9]+/g, replacement: "[REDACTED]" },
|
|
1568
|
-
{ pattern: /shpua_[a-zA-Z0-9]+/g, replacement: "[REDACTED]" },
|
|
1569
|
-
{ pattern: /Bearer\s+[a-zA-Z0-9_-]+/g, replacement: "Bearer [REDACTED]" },
|
|
1570
|
-
{ pattern: /access_token[=:]\s*[a-zA-Z0-9_-]+/gi, replacement: "access_token=[REDACTED]" },
|
|
1571
|
-
{ pattern: /client_secret[=:]\s*[a-zA-Z0-9_-]+/gi, replacement: "client_secret=[REDACTED]" },
|
|
1572
|
-
{ pattern: /sk_live_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" }
|
|
1573
|
-
];
|
|
1574
1570
|
function sanitizeText(text) {
|
|
1575
|
-
|
|
1576
|
-
for (const { pattern, replacement } of SANITIZATION_PATTERNS) {
|
|
1577
|
-
result = result.replace(pattern, replacement);
|
|
1578
|
-
}
|
|
1579
|
-
return result;
|
|
1571
|
+
return redactTokens(text);
|
|
1580
1572
|
}
|
|
1581
1573
|
function sanitizeObject(obj, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1582
1574
|
if (typeof obj === "string") {
|
|
@@ -1745,118 +1737,6 @@ function flushLogs() {
|
|
|
1745
1737
|
|
|
1746
1738
|
// src/logging/tool-execution-logger.ts
|
|
1747
1739
|
import { Prisma } from "@prisma/client";
|
|
1748
|
-
|
|
1749
|
-
// src/logging/sanitize.ts
|
|
1750
|
-
var MAX_STRING_LENGTH = 1e3;
|
|
1751
|
-
var MAX_ARRAY_ITEMS = 10;
|
|
1752
|
-
var MAX_RECURSION_DEPTH = 5;
|
|
1753
|
-
var MAX_OUTPUT_BYTES = 5e3;
|
|
1754
|
-
var TOKEN_PATTERNS = [
|
|
1755
|
-
// Shopify access tokens (shpat_xxx, shpua_xxx) - include underscores in the pattern
|
|
1756
|
-
{ pattern: /shpat_[a-zA-Z0-9_]+/g, replacement: "[REDACTED]" },
|
|
1757
|
-
{ pattern: /shpua_[a-zA-Z0-9_]+/g, replacement: "[REDACTED]" },
|
|
1758
|
-
// Bearer tokens
|
|
1759
|
-
{ pattern: /Bearer\s+[a-zA-Z0-9_.-]+/g, replacement: "Bearer [REDACTED]" },
|
|
1760
|
-
// OAuth access tokens (mcp_access_xxx)
|
|
1761
|
-
{ pattern: /mcp_access_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
1762
|
-
// Live/test API keys (sk_live_xxx, sk_test_xxx)
|
|
1763
|
-
{ pattern: /sk_live_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
1764
|
-
{ pattern: /sk_test_[a-zA-Z0-9_-]+/g, replacement: "[REDACTED]" },
|
|
1765
|
-
// Generic access_token and client_secret patterns
|
|
1766
|
-
{ pattern: /access_token[=:]\s*[a-zA-Z0-9_.-]+/gi, replacement: "access_token=[REDACTED]" },
|
|
1767
|
-
{ pattern: /client_secret[=:]\s*[a-zA-Z0-9_.-]+/gi, replacement: "client_secret=[REDACTED]" }
|
|
1768
|
-
];
|
|
1769
|
-
function redactTokens(text) {
|
|
1770
|
-
let result = text;
|
|
1771
|
-
for (const { pattern, replacement } of TOKEN_PATTERNS) {
|
|
1772
|
-
result = result.replace(pattern, replacement);
|
|
1773
|
-
}
|
|
1774
|
-
return result;
|
|
1775
|
-
}
|
|
1776
|
-
function truncateString(text, maxLength = MAX_STRING_LENGTH) {
|
|
1777
|
-
if (text.length <= maxLength) {
|
|
1778
|
-
return text;
|
|
1779
|
-
}
|
|
1780
|
-
return `${text.substring(0, maxLength)}...[TRUNCATED]`;
|
|
1781
|
-
}
|
|
1782
|
-
function sanitizeParams(params, depth = 0) {
|
|
1783
|
-
if (depth > MAX_RECURSION_DEPTH) {
|
|
1784
|
-
return "[TRUNCATED]";
|
|
1785
|
-
}
|
|
1786
|
-
if (params === null || params === void 0) {
|
|
1787
|
-
return params;
|
|
1788
|
-
}
|
|
1789
|
-
if (typeof params === "string") {
|
|
1790
|
-
const redacted = redactTokens(params);
|
|
1791
|
-
return truncateString(redacted);
|
|
1792
|
-
}
|
|
1793
|
-
if (typeof params !== "object") {
|
|
1794
|
-
return params;
|
|
1795
|
-
}
|
|
1796
|
-
if (Array.isArray(params)) {
|
|
1797
|
-
const limited = params.slice(0, MAX_ARRAY_ITEMS);
|
|
1798
|
-
return limited.map((item) => sanitizeParams(item, depth + 1));
|
|
1799
|
-
}
|
|
1800
|
-
const result = {};
|
|
1801
|
-
for (const [key, value] of Object.entries(params)) {
|
|
1802
|
-
result[key] = sanitizeParams(value, depth + 1);
|
|
1803
|
-
}
|
|
1804
|
-
return result;
|
|
1805
|
-
}
|
|
1806
|
-
function getByteSize(value) {
|
|
1807
|
-
try {
|
|
1808
|
-
return JSON.stringify(value).length;
|
|
1809
|
-
} catch {
|
|
1810
|
-
return 0;
|
|
1811
|
-
}
|
|
1812
|
-
}
|
|
1813
|
-
function truncateOutput(output, maxBytes = MAX_OUTPUT_BYTES) {
|
|
1814
|
-
const sanitized = sanitizeParams(output);
|
|
1815
|
-
const size = getByteSize(sanitized);
|
|
1816
|
-
if (size <= maxBytes) {
|
|
1817
|
-
return sanitized;
|
|
1818
|
-
}
|
|
1819
|
-
if (typeof sanitized === "object" && sanitized !== null && !Array.isArray(sanitized)) {
|
|
1820
|
-
const obj = sanitized;
|
|
1821
|
-
if ("data" in obj || "errors" in obj) {
|
|
1822
|
-
const result = {};
|
|
1823
|
-
if (obj.errors) {
|
|
1824
|
-
result.errors = obj.errors;
|
|
1825
|
-
}
|
|
1826
|
-
if (obj.data) {
|
|
1827
|
-
result.data = "[TRUNCATED - output exceeded 5KB]";
|
|
1828
|
-
}
|
|
1829
|
-
if (obj.extensions && getByteSize(obj.extensions) < 200) {
|
|
1830
|
-
result.extensions = obj.extensions;
|
|
1831
|
-
}
|
|
1832
|
-
return result;
|
|
1833
|
-
}
|
|
1834
|
-
return {
|
|
1835
|
-
_truncated: true,
|
|
1836
|
-
_message: `Output truncated: ${size} bytes exceeded ${maxBytes} byte limit`,
|
|
1837
|
-
_keys: Object.keys(obj).slice(0, 10)
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
if (Array.isArray(sanitized)) {
|
|
1841
|
-
return {
|
|
1842
|
-
_truncated: true,
|
|
1843
|
-
_message: `Array truncated: ${sanitized.length} items, ${size} bytes exceeded ${maxBytes} byte limit`,
|
|
1844
|
-
_sample: sanitized.slice(0, 3)
|
|
1845
|
-
};
|
|
1846
|
-
}
|
|
1847
|
-
return "[TRUNCATED - output exceeded 5KB]";
|
|
1848
|
-
}
|
|
1849
|
-
function sanitizeErrorMessage2(error) {
|
|
1850
|
-
if (error instanceof Error) {
|
|
1851
|
-
return truncateString(redactTokens(error.message), 500);
|
|
1852
|
-
}
|
|
1853
|
-
if (typeof error === "string") {
|
|
1854
|
-
return truncateString(redactTokens(error), 500);
|
|
1855
|
-
}
|
|
1856
|
-
return "Unknown error";
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
// src/logging/tool-execution-logger.ts
|
|
1860
1740
|
var logger = createLogger("logging/tool-execution-logger");
|
|
1861
1741
|
var VALIDATION_ERROR_KEYWORDS = [
|
|
1862
1742
|
"validation failed",
|
|
@@ -1960,7 +1840,7 @@ var ToolExecutionLogger = class {
|
|
|
1960
1840
|
}
|
|
1961
1841
|
const sanitizedInput = entry.inputParams ? sanitizeParams(entry.inputParams) : void 0;
|
|
1962
1842
|
const sanitizedOutput = entry.output ? truncateOutput(entry.output) : void 0;
|
|
1963
|
-
const sanitizedErrorMessage = entry.errorMessage ?
|
|
1843
|
+
const sanitizedErrorMessage = entry.errorMessage ? sanitizeErrorMessage(entry.errorMessage) : void 0;
|
|
1964
1844
|
const logData = {
|
|
1965
1845
|
tenant: { connect: { id: entry.tenantId } },
|
|
1966
1846
|
toolName: entry.toolName,
|
|
@@ -2077,8 +1957,7 @@ function createToolSuccess(data) {
|
|
|
2077
1957
|
type: "text",
|
|
2078
1958
|
text
|
|
2079
1959
|
}
|
|
2080
|
-
]
|
|
2081
|
-
structuredContent: data
|
|
1960
|
+
]
|
|
2082
1961
|
};
|
|
2083
1962
|
}
|
|
2084
1963
|
|
|
@@ -2753,10 +2632,10 @@ function createHandlerWithContext(contextAwareHandler) {
|
|
|
2753
2632
|
}
|
|
2754
2633
|
if (isRemoteMode(config)) {
|
|
2755
2634
|
log.error(
|
|
2756
|
-
`[tool] Remote mode but no valid async context available. Context: ${asyncContext ? JSON.stringify(Object.keys(asyncContext)) : "undefined"}. This
|
|
2635
|
+
`[tool] Remote mode but no valid async context available. Context: ${asyncContext ? JSON.stringify(Object.keys(asyncContext)) : "undefined"}. This likely means no shop is connected or shop tokens need re-authentication.`
|
|
2757
2636
|
);
|
|
2758
2637
|
throw new Error(
|
|
2759
|
-
"
|
|
2638
|
+
"No connected Shopify store found. Please connect a store through the dashboard, or reconnect if your store was previously connected."
|
|
2760
2639
|
);
|
|
2761
2640
|
}
|
|
2762
2641
|
const client = await getShopifyClient();
|
|
@@ -14685,6 +14564,7 @@ export {
|
|
|
14685
14564
|
initSentry,
|
|
14686
14565
|
sentryRequestMiddleware,
|
|
14687
14566
|
sentryErrorHandler,
|
|
14567
|
+
captureError,
|
|
14688
14568
|
sanitizeErrorMessage3 as sanitizeErrorMessage,
|
|
14689
14569
|
createShopifyClient,
|
|
14690
14570
|
getShopifyClient,
|
|
@@ -14702,6 +14582,7 @@ export {
|
|
|
14702
14582
|
correlationMiddleware,
|
|
14703
14583
|
createLogger,
|
|
14704
14584
|
flushLogs,
|
|
14585
|
+
getToolExecutionLogger,
|
|
14705
14586
|
deriveDefaultAnnotations,
|
|
14706
14587
|
validateToolName,
|
|
14707
14588
|
isValidToolName,
|