@ceon-oy/monitor-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,575 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/MonitorClient.ts
9
+ var MonitorClient = class {
10
+ constructor(config) {
11
+ this.queue = [];
12
+ this.flushTimer = null;
13
+ this.isClosed = false;
14
+ this.retryCount = /* @__PURE__ */ new Map();
15
+ this.isFlushInProgress = false;
16
+ if (!config.apiKey || config.apiKey.trim().length === 0) {
17
+ throw new Error("[MonitorClient] API key is required");
18
+ }
19
+ if (!/^cm_[a-zA-Z0-9_-]+$/.test(config.apiKey)) {
20
+ throw new Error("[MonitorClient] Invalid API key format. Expected format: cm_xxx");
21
+ }
22
+ if (!config.endpoint || config.endpoint.trim().length === 0) {
23
+ throw new Error("[MonitorClient] Endpoint URL is required");
24
+ }
25
+ try {
26
+ const url = new URL(config.endpoint);
27
+ if (!["https:", "http:"].includes(url.protocol)) {
28
+ throw new Error("[MonitorClient] Endpoint must use HTTP or HTTPS protocol");
29
+ }
30
+ if (url.protocol === "http:" && config.environment !== "development" && config.environment !== "test") {
31
+ console.warn("[MonitorClient] Warning: Using HTTP in non-development environment is not recommended");
32
+ }
33
+ } catch (err) {
34
+ if (err instanceof Error && err.message.includes("[MonitorClient]")) {
35
+ throw err;
36
+ }
37
+ throw new Error("[MonitorClient] Invalid endpoint URL");
38
+ }
39
+ this.apiKey = config.apiKey;
40
+ this.endpoint = config.endpoint.replace(/\/$/, "");
41
+ this.environment = config.environment || "production";
42
+ this.batchSize = Math.max(1, config.batchSize || 10);
43
+ this.flushIntervalMs = Math.max(1e3, config.flushIntervalMs || 5e3);
44
+ this.trackDependencies = config.trackDependencies || false;
45
+ this.packageJsonPath = config.packageJsonPath;
46
+ this.dependencySources = config.dependencySources;
47
+ this.maxQueueSize = config.maxQueueSize || 1e3;
48
+ this.maxRetries = config.maxRetries || 3;
49
+ this.requestTimeoutMs = config.requestTimeoutMs || 1e4;
50
+ const defaultExcludePatterns = [
51
+ "@types/*",
52
+ "eslint*",
53
+ "prettier*",
54
+ "*-loader",
55
+ "*-plugin",
56
+ "@eslint/*",
57
+ "@typescript-eslint/*"
58
+ ];
59
+ this.excludePatterns = config.excludePatterns || defaultExcludePatterns;
60
+ this.startFlushTimer();
61
+ if (this.trackDependencies) {
62
+ this.syncDependencies().catch((err) => {
63
+ console.error("[MonitorClient] Failed to sync dependencies:", err instanceof Error ? err.message : String(err));
64
+ });
65
+ }
66
+ }
67
+ async captureError(error, context) {
68
+ if (this.isClosed) return;
69
+ const payload = {
70
+ severity: context?.severity || "ERROR",
71
+ message: error.message,
72
+ stack: error.stack,
73
+ environment: this.environment,
74
+ route: context?.route,
75
+ method: context?.method,
76
+ statusCode: context?.statusCode,
77
+ userAgent: context?.userAgent,
78
+ ip: context?.ip,
79
+ requestId: context?.requestId,
80
+ metadata: context?.metadata
81
+ };
82
+ this.enqueue(payload);
83
+ }
84
+ async captureMessage(message, severity = "INFO", context) {
85
+ if (this.isClosed) return;
86
+ const payload = {
87
+ severity,
88
+ message,
89
+ environment: this.environment,
90
+ route: context?.route,
91
+ method: context?.method,
92
+ statusCode: context?.statusCode,
93
+ userAgent: context?.userAgent,
94
+ ip: context?.ip,
95
+ requestId: context?.requestId,
96
+ metadata: context?.metadata
97
+ };
98
+ this.enqueue(payload);
99
+ }
100
+ async flush() {
101
+ if (this.isFlushInProgress || this.queue.length === 0) return;
102
+ this.isFlushInProgress = true;
103
+ const errors = [...this.queue];
104
+ this.queue = [];
105
+ try {
106
+ if (errors.length === 1) {
107
+ await this.sendSingle(errors[0]);
108
+ } else {
109
+ await this.sendBatch(errors);
110
+ }
111
+ for (const error of errors) {
112
+ this.retryCount.delete(this.getErrorKey(error));
113
+ }
114
+ } catch (err) {
115
+ console.error("[MonitorClient] Failed to send errors:", err instanceof Error ? err.message : String(err));
116
+ for (const error of errors) {
117
+ const key = this.getErrorKey(error);
118
+ const retries = this.retryCount.get(key) || 0;
119
+ if (retries < this.maxRetries) {
120
+ this.retryCount.set(key, retries + 1);
121
+ if (this.queue.length < this.maxQueueSize) {
122
+ this.queue.push(error);
123
+ } else {
124
+ console.warn("[MonitorClient] Queue full, dropping error");
125
+ this.retryCount.delete(key);
126
+ }
127
+ } else {
128
+ console.warn("[MonitorClient] Max retries exceeded, dropping error");
129
+ this.retryCount.delete(key);
130
+ }
131
+ }
132
+ } finally {
133
+ this.isFlushInProgress = false;
134
+ }
135
+ }
136
+ getErrorKey(error) {
137
+ return `${error.message}-${error.stack?.substring(0, 100) || ""}-${error.route || ""}`;
138
+ }
139
+ async close() {
140
+ this.isClosed = true;
141
+ this.stopFlushTimer();
142
+ await this.flush();
143
+ }
144
+ enqueue(payload) {
145
+ if (this.queue.length >= this.maxQueueSize) {
146
+ console.warn("[MonitorClient] Queue full, dropping oldest error");
147
+ this.queue.shift();
148
+ }
149
+ this.queue.push(payload);
150
+ if (this.queue.length >= this.batchSize) {
151
+ this.flush();
152
+ }
153
+ }
154
+ /**
155
+ * Fetch with timeout to prevent hanging requests
156
+ */
157
+ async fetchWithTimeout(url, options, timeoutMs = this.requestTimeoutMs) {
158
+ const controller = new AbortController();
159
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
160
+ try {
161
+ const response = await fetch(url, {
162
+ ...options,
163
+ signal: controller.signal
164
+ });
165
+ return response;
166
+ } catch (err) {
167
+ if (err instanceof Error && err.name === "AbortError") {
168
+ throw new Error(`Request timeout after ${timeoutMs}ms`);
169
+ }
170
+ throw err;
171
+ } finally {
172
+ clearTimeout(timeoutId);
173
+ }
174
+ }
175
+ async sendSingle(error) {
176
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/errors`, {
177
+ method: "POST",
178
+ headers: {
179
+ "Content-Type": "application/json",
180
+ Authorization: `Bearer ${this.apiKey}`
181
+ },
182
+ body: JSON.stringify(error)
183
+ });
184
+ if (!response.ok) {
185
+ const errorText = await response.text().catch(() => "");
186
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
187
+ }
188
+ }
189
+ async sendBatch(errors) {
190
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/errors/batch`, {
191
+ method: "POST",
192
+ headers: {
193
+ "Content-Type": "application/json",
194
+ Authorization: `Bearer ${this.apiKey}`
195
+ },
196
+ body: JSON.stringify({ errors })
197
+ });
198
+ if (!response.ok) {
199
+ const errorText = await response.text().catch(() => "");
200
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
201
+ }
202
+ }
203
+ startFlushTimer() {
204
+ this.flushTimer = setInterval(() => {
205
+ this.flush();
206
+ }, this.flushIntervalMs);
207
+ }
208
+ stopFlushTimer() {
209
+ if (this.flushTimer) {
210
+ clearInterval(this.flushTimer);
211
+ this.flushTimer = null;
212
+ }
213
+ }
214
+ async syncDependencies() {
215
+ try {
216
+ if (this.dependencySources && this.dependencySources.length > 0) {
217
+ for (const source of this.dependencySources) {
218
+ const technologies = await this.readPackageJsonFromPath(source.path);
219
+ if (technologies.length === 0) continue;
220
+ await this.sendTechnologiesWithEnvironment(technologies, source.environment);
221
+ }
222
+ } else {
223
+ const technologies = await this.readPackageJson();
224
+ if (technologies.length === 0) return;
225
+ await this.sendTechnologies(technologies);
226
+ }
227
+ } catch (err) {
228
+ console.error("[MonitorClient] Failed to sync dependencies:", err);
229
+ }
230
+ }
231
+ async syncTechnologies(technologies) {
232
+ await this.sendTechnologies(technologies);
233
+ }
234
+ async readPackageJson() {
235
+ const packagePath = this.packageJsonPath || "package.json";
236
+ return this.readPackageJsonFromPath(packagePath);
237
+ }
238
+ async readPackageJsonFromPath(packagePath) {
239
+ if (typeof process === "undefined" || typeof process.cwd !== "function") {
240
+ return [];
241
+ }
242
+ try {
243
+ const fsModule = await import("fs");
244
+ const pathModule = await import("path");
245
+ const fs = fsModule.default || fsModule;
246
+ const path = pathModule.default || pathModule;
247
+ const baseDir = process.cwd();
248
+ const resolvedPath = path.isAbsolute(packagePath) ? packagePath : path.join(baseDir, packagePath);
249
+ const normalizedPath = path.normalize(resolvedPath);
250
+ const normalizedBase = path.normalize(baseDir);
251
+ if (!path.isAbsolute(packagePath)) {
252
+ if (!normalizedPath.startsWith(normalizedBase)) {
253
+ console.warn("[MonitorClient] Path traversal attempt blocked:", packagePath);
254
+ return [];
255
+ }
256
+ }
257
+ if (packagePath.includes("\0") || /\.\.[\\/]/.test(packagePath)) {
258
+ console.warn("[MonitorClient] Suspicious path blocked:", packagePath);
259
+ return [];
260
+ }
261
+ if (!normalizedPath.endsWith("package.json")) {
262
+ console.warn("[MonitorClient] Path must point to package.json");
263
+ return [];
264
+ }
265
+ if (!fs.existsSync(normalizedPath)) {
266
+ return [];
267
+ }
268
+ const packageJson = JSON.parse(fs.readFileSync(normalizedPath, "utf-8"));
269
+ const technologies = [];
270
+ const deps = {
271
+ ...packageJson.dependencies,
272
+ ...packageJson.devDependencies
273
+ };
274
+ for (const [name, version] of Object.entries(deps)) {
275
+ if (typeof version === "string") {
276
+ if (this.shouldExclude(name)) {
277
+ continue;
278
+ }
279
+ technologies.push({
280
+ name,
281
+ version: version.replace(/^[\^~]/, "")
282
+ });
283
+ }
284
+ }
285
+ return technologies;
286
+ } catch {
287
+ return [];
288
+ }
289
+ }
290
+ shouldExclude(packageName) {
291
+ for (const pattern of this.excludePatterns) {
292
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
293
+ if (new RegExp(`^${regexPattern}$`).test(packageName)) {
294
+ return true;
295
+ }
296
+ }
297
+ return false;
298
+ }
299
+ async sendTechnologies(technologies) {
300
+ await this.sendTechnologiesWithEnvironment(technologies, this.environment);
301
+ }
302
+ async sendTechnologiesWithEnvironment(technologies, environment) {
303
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/technologies/sync`, {
304
+ method: "POST",
305
+ headers: {
306
+ "Content-Type": "application/json",
307
+ Authorization: `Bearer ${this.apiKey}`
308
+ },
309
+ body: JSON.stringify({
310
+ environment,
311
+ technologies
312
+ })
313
+ });
314
+ if (!response.ok) {
315
+ const errorText = await response.text().catch(() => "");
316
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
317
+ }
318
+ }
319
+ /**
320
+ * Capture a security event (auth failures, rate limits, suspicious activity, etc.)
321
+ * Returns brute force warning if pattern is detected
322
+ */
323
+ async captureSecurityEvent(input) {
324
+ if (this.isClosed) return {};
325
+ const payload = {
326
+ ...input,
327
+ environment: this.environment
328
+ };
329
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security`, {
330
+ method: "POST",
331
+ headers: {
332
+ "Content-Type": "application/json",
333
+ Authorization: `Bearer ${this.apiKey}`
334
+ },
335
+ body: JSON.stringify(payload)
336
+ });
337
+ if (!response.ok) {
338
+ const errorText = await response.text().catch(() => "");
339
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
340
+ }
341
+ const result = await response.json();
342
+ return { warning: result.warning };
343
+ }
344
+ /**
345
+ * Capture a login failure event (convenience method)
346
+ */
347
+ async captureLoginFailure(options) {
348
+ return this.captureSecurityEvent({
349
+ eventType: `login_failed_${options.authMethod || "other"}`,
350
+ category: "AUTHENTICATION",
351
+ severity: "MEDIUM",
352
+ ip: options.ip,
353
+ identifier: options.identifier,
354
+ endpoint: options.endpoint,
355
+ userAgent: options.userAgent,
356
+ metadata: { reason: options.reason, authMethod: options.authMethod }
357
+ });
358
+ }
359
+ /**
360
+ * Capture a successful login event
361
+ */
362
+ async captureLoginSuccess(options) {
363
+ await this.captureSecurityEvent({
364
+ eventType: `login_success_${options.authMethod || "other"}`,
365
+ category: "AUTHENTICATION",
366
+ severity: "LOW",
367
+ ip: options.ip,
368
+ identifier: options.identifier,
369
+ endpoint: options.endpoint,
370
+ userAgent: options.userAgent,
371
+ metadata: { authMethod: options.authMethod }
372
+ });
373
+ }
374
+ /**
375
+ * Capture a rate limit event
376
+ */
377
+ async captureRateLimit(options) {
378
+ await this.captureSecurityEvent({
379
+ eventType: "rate_limit_exceeded",
380
+ category: "RATE_LIMIT",
381
+ severity: "MEDIUM",
382
+ ip: options.ip,
383
+ identifier: options.identifier,
384
+ endpoint: options.endpoint,
385
+ userAgent: options.userAgent,
386
+ metadata: { limit: options.limit, window: options.window }
387
+ });
388
+ }
389
+ /**
390
+ * Capture an authorization failure (user tried to access unauthorized resource)
391
+ */
392
+ async captureAuthorizationFailure(options) {
393
+ await this.captureSecurityEvent({
394
+ eventType: "authorization_denied",
395
+ category: "AUTHORIZATION",
396
+ severity: "MEDIUM",
397
+ ip: options.ip,
398
+ identifier: options.identifier,
399
+ endpoint: options.endpoint,
400
+ userAgent: options.userAgent,
401
+ metadata: { resource: options.resource, action: options.action }
402
+ });
403
+ }
404
+ /**
405
+ * Check if an IP or identifier has triggered brute force detection
406
+ */
407
+ async checkBruteForce(options) {
408
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/security/detect/brute-force`, {
409
+ method: "POST",
410
+ headers: {
411
+ "Content-Type": "application/json",
412
+ Authorization: `Bearer ${this.apiKey}`
413
+ },
414
+ body: JSON.stringify(options)
415
+ });
416
+ if (!response.ok) {
417
+ const errorText = await response.text().catch(() => "");
418
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
419
+ }
420
+ const result = await response.json();
421
+ return result.data;
422
+ }
423
+ /**
424
+ * Run npm audit and send results to the monitoring server.
425
+ * This scans the project for known vulnerabilities in dependencies.
426
+ *
427
+ * @param options.projectPath - Path to the project directory (defaults to cwd)
428
+ * @param options.environment - Environment label (defaults to client environment)
429
+ * @returns Audit summary with vulnerability counts
430
+ */
431
+ async auditDependencies(options = {}) {
432
+ if (typeof __require === "undefined") {
433
+ console.warn("[MonitorClient] auditDependencies only works in Node.js environment");
434
+ return null;
435
+ }
436
+ const startTime = Date.now();
437
+ const environment = options.environment || this.environment;
438
+ try {
439
+ const { execSync } = __require("child_process");
440
+ const path = __require("path");
441
+ const fs = __require("fs");
442
+ let projectPath = options.projectPath || process.cwd();
443
+ if (projectPath.includes("\0") || /[;&|`$(){}[\]<>]/.test(projectPath)) {
444
+ console.error("[MonitorClient] Invalid projectPath: contains forbidden characters");
445
+ return null;
446
+ }
447
+ projectPath = path.resolve(projectPath);
448
+ try {
449
+ const stats = fs.statSync(projectPath);
450
+ if (!stats.isDirectory()) {
451
+ console.error("[MonitorClient] projectPath is not a directory");
452
+ return null;
453
+ }
454
+ } catch {
455
+ console.error("[MonitorClient] projectPath does not exist");
456
+ return null;
457
+ }
458
+ const packageJsonPath = path.join(projectPath, "package.json");
459
+ if (!fs.existsSync(packageJsonPath)) {
460
+ console.error("[MonitorClient] No package.json found in projectPath");
461
+ return null;
462
+ }
463
+ let auditOutput;
464
+ try {
465
+ auditOutput = execSync("npm audit --json", {
466
+ cwd: projectPath,
467
+ encoding: "utf-8",
468
+ stdio: ["pipe", "pipe", "pipe"],
469
+ maxBuffer: 10 * 1024 * 1024,
470
+ // 10MB buffer for large outputs
471
+ timeout: 6e4
472
+ // 60 second timeout
473
+ });
474
+ } catch (err) {
475
+ const execError = err;
476
+ if (execError.killed) {
477
+ console.error("[MonitorClient] npm audit timed out");
478
+ return null;
479
+ }
480
+ if (execError.stdout) {
481
+ auditOutput = execError.stdout;
482
+ } else {
483
+ throw err;
484
+ }
485
+ }
486
+ const auditData = JSON.parse(auditOutput);
487
+ const vulnerabilities = this.parseNpmAuditOutput(auditData);
488
+ const totalDeps = auditData.metadata?.dependencies?.total || 0;
489
+ const scanDurationMs = Date.now() - startTime;
490
+ const response = await this.fetchWithTimeout(`${this.endpoint}/api/v1/vulnerabilities/audit`, {
491
+ method: "POST",
492
+ headers: {
493
+ "Content-Type": "application/json",
494
+ Authorization: `Bearer ${this.apiKey}`
495
+ },
496
+ body: JSON.stringify({
497
+ environment,
498
+ totalDeps,
499
+ vulnerabilities,
500
+ scanDurationMs
501
+ })
502
+ });
503
+ if (!response.ok) {
504
+ const errorText = await response.text().catch(() => "");
505
+ throw new Error(`HTTP ${response.status}${errorText ? `: ${errorText}` : ""}`);
506
+ }
507
+ const result = await response.json();
508
+ return result.data;
509
+ } catch (err) {
510
+ console.error("[MonitorClient] Failed to audit dependencies:", err instanceof Error ? err.message : String(err));
511
+ return null;
512
+ }
513
+ }
514
+ /**
515
+ * Parse npm audit JSON output into vulnerability items
516
+ */
517
+ parseNpmAuditOutput(auditData) {
518
+ const vulnerabilities = [];
519
+ if (!auditData.vulnerabilities) {
520
+ return vulnerabilities;
521
+ }
522
+ for (const [packageName, vuln] of Object.entries(auditData.vulnerabilities)) {
523
+ const viaDetails = vuln.via.find(
524
+ (v) => typeof v === "object" && "title" in v
525
+ );
526
+ if (viaDetails) {
527
+ vulnerabilities.push({
528
+ packageName,
529
+ severity: vuln.severity.toLowerCase(),
530
+ title: viaDetails.title,
531
+ url: viaDetails.url,
532
+ vulnerableRange: viaDetails.range,
533
+ installedVersion: vuln.nodes?.[0]?.split("@").pop(),
534
+ patchedVersions: this.getFixVersion(vuln.fixAvailable),
535
+ path: vuln.nodes?.join(" > "),
536
+ recommendation: this.getRecommendation(vuln.fixAvailable),
537
+ cwe: viaDetails.cwe,
538
+ cvss: viaDetails.cvss?.score,
539
+ isFixable: Boolean(vuln.fixAvailable),
540
+ isDirect: vuln.isDirect
541
+ });
542
+ } else {
543
+ const viaNames = vuln.via.filter((v) => typeof v === "string");
544
+ vulnerabilities.push({
545
+ packageName,
546
+ severity: vuln.severity.toLowerCase(),
547
+ title: `Depends on vulnerable ${viaNames.join(", ")}`,
548
+ vulnerableRange: vuln.range,
549
+ path: vuln.nodes?.join(" > "),
550
+ isFixable: Boolean(vuln.fixAvailable),
551
+ isDirect: vuln.isDirect
552
+ });
553
+ }
554
+ }
555
+ return vulnerabilities;
556
+ }
557
+ getFixVersion(fixAvailable) {
558
+ if (typeof fixAvailable === "object" && fixAvailable !== null) {
559
+ return fixAvailable.version;
560
+ }
561
+ return void 0;
562
+ }
563
+ getRecommendation(fixAvailable) {
564
+ if (typeof fixAvailable === "object" && fixAvailable !== null) {
565
+ const majorWarning = fixAvailable.isSemVerMajor ? " (breaking change)" : "";
566
+ return `Update ${fixAvailable.name} to ${fixAvailable.version}${majorWarning}`;
567
+ } else if (fixAvailable === true) {
568
+ return "Run npm audit fix";
569
+ }
570
+ return "No fix available";
571
+ }
572
+ };
573
+ export {
574
+ MonitorClient
575
+ };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@ceon-oy/monitor-sdk",
3
+ "version": "1.0.1",
4
+ "description": "Client SDK for Ceon Monitor - Error tracking, health monitoring, security events, and vulnerability scanning",
5
+ "author": "Ceon",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/ceon-oy/ceon-monitor-sdk.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/ceon-oy/ceon-monitor-sdk/issues"
13
+ },
14
+ "homepage": "https://github.com/ceon-oy/ceon-monitor-sdk#readme",
15
+ "keywords": [
16
+ "monitoring",
17
+ "error-tracking",
18
+ "health-check",
19
+ "security",
20
+ "vulnerability",
21
+ "logging",
22
+ "observability"
23
+ ],
24
+ "main": "dist/index.js",
25
+ "module": "dist/index.mjs",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.mjs",
31
+ "require": "./dist/index.js"
32
+ }
33
+ },
34
+ "scripts": {
35
+ "build": "tsup src/index.ts --format cjs,esm --dts",
36
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^24.10.2",
41
+ "tsup": "^8.3.5",
42
+ "typescript": "^5.7.2"
43
+ },
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "engines": {
48
+ "node": ">=18.0.0"
49
+ }
50
+ }