botversion-sdk 1.0.5 → 1.0.6
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/bin/init.js +3 -3
- package/cli/generator.js +3 -3
- package/cli/writer.js +1 -1
- package/client.js +7 -220
- package/index.js +18 -161
- package/interceptor.js +53 -116
- package/package.json +4 -3
- package/scanner.js +2 -62
package/bin/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
// botversion-sdk/bin/init.js
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
@@ -102,7 +102,7 @@ async function main() {
|
|
|
102
102
|
if (!args.key) {
|
|
103
103
|
error("API key is required.");
|
|
104
104
|
log(`\n Usage: npx botversion-sdk init --key YOUR_WORKSPACE_KEY\n`);
|
|
105
|
-
log(` Get your key from: https://
|
|
105
|
+
log(` Get your key from: https://botversion.com/settings\n`);
|
|
106
106
|
process.exit(1);
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -113,7 +113,7 @@ async function main() {
|
|
|
113
113
|
step("Fetching project info from platform...");
|
|
114
114
|
let projectInfo;
|
|
115
115
|
try {
|
|
116
|
-
projectInfo = await fetchProjectInfo(args.key, "
|
|
116
|
+
projectInfo = await fetchProjectInfo(args.key, "https://botversion.com");
|
|
117
117
|
success(`Project found — ID: ${projectInfo.projectId}`);
|
|
118
118
|
} catch (err) {
|
|
119
119
|
error(err.message);
|
package/cli/generator.js
CHANGED
|
@@ -149,11 +149,11 @@ Fastify support is coming soon. For now, add this manually:
|
|
|
149
149
|
`,
|
|
150
150
|
koa: `
|
|
151
151
|
Koa support is coming soon. For now, add this manually:
|
|
152
|
-
See: https://
|
|
152
|
+
See: https://botversion.com/docs
|
|
153
153
|
`,
|
|
154
154
|
"@nestjs/core": `
|
|
155
155
|
NestJS support is coming soon. For now, add this manually:
|
|
156
|
-
See: https://
|
|
156
|
+
See: https://botversion.com/docs
|
|
157
157
|
`,
|
|
158
158
|
};
|
|
159
159
|
|
|
@@ -161,7 +161,7 @@ NestJS support is coming soon. For now, add this manually:
|
|
|
161
161
|
instructions[framework] ||
|
|
162
162
|
`
|
|
163
163
|
This framework is not yet supported automatically.
|
|
164
|
-
Visit https://
|
|
164
|
+
Visit https://botversion.com/docs for manual setup instructions.
|
|
165
165
|
`
|
|
166
166
|
);
|
|
167
167
|
}
|
package/cli/writer.js
CHANGED
|
@@ -257,7 +257,7 @@ function writeSummary(changes) {
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
lines.push(" Next: Restart your server and test the chat widget.");
|
|
260
|
-
lines.push(" Docs: https://
|
|
260
|
+
lines.push(" Docs: https://botversion.com/docs");
|
|
261
261
|
lines.push("");
|
|
262
262
|
|
|
263
263
|
return lines.join("\n");
|
package/client.js
CHANGED
|
@@ -11,7 +11,7 @@ const url = require("url");
|
|
|
11
11
|
*/
|
|
12
12
|
function BotVersionClient(options) {
|
|
13
13
|
this.apiKey = options.apiKey;
|
|
14
|
-
this.platformUrl = options.platformUrl || "
|
|
14
|
+
this.platformUrl = options.platformUrl || "https://botversion.com";
|
|
15
15
|
this.debug = options.debug || false;
|
|
16
16
|
this.timeout = options.timeout || 30000;
|
|
17
17
|
|
|
@@ -19,25 +19,12 @@ function BotVersionClient(options) {
|
|
|
19
19
|
this._queue = [];
|
|
20
20
|
this._flushTimer = null;
|
|
21
21
|
this._flushDelay = options.flushDelay || 3000;
|
|
22
|
-
this._ws = null;
|
|
23
|
-
this._executor = null;
|
|
24
22
|
var self = this;
|
|
25
23
|
process.on("beforeExit", function () {
|
|
26
24
|
if (self._queue.length > 0) self._flush();
|
|
27
25
|
});
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* Register the executor function — called by interceptor
|
|
32
|
-
* So WebSocket can forward calls to user's backend
|
|
33
|
-
*/
|
|
34
|
-
BotVersionClient.prototype.setExecutor = function (executorFn) {
|
|
35
|
-
this._executor = executorFn;
|
|
36
|
-
if (this.debug) {
|
|
37
|
-
console.log("[BotVersion SDK] ✅ Executor registered");
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
28
|
/**
|
|
42
29
|
* Register multiple endpoints at once (batched)
|
|
43
30
|
*/
|
|
@@ -46,14 +33,6 @@ BotVersionClient.prototype.registerEndpoints = function (endpoints) {
|
|
|
46
33
|
|
|
47
34
|
if (!endpoints || endpoints.length === 0) return Promise.resolve();
|
|
48
35
|
|
|
49
|
-
if (self.debug) {
|
|
50
|
-
console.log(
|
|
51
|
-
"[BotVersion SDK] Queuing",
|
|
52
|
-
endpoints.length,
|
|
53
|
-
"endpoints for registration",
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
36
|
self._queue = self._queue.concat(endpoints);
|
|
58
37
|
|
|
59
38
|
if (!self._flushTimer) {
|
|
@@ -79,20 +58,7 @@ BotVersionClient.prototype.registerEndpointsNow = function (endpoints) {
|
|
|
79
58
|
endpoints: endpoints,
|
|
80
59
|
})
|
|
81
60
|
.then(function (data) {
|
|
82
|
-
if (self.debug) {
|
|
83
|
-
console.log(
|
|
84
|
-
"[BotVersion SDK] ✅ Registered",
|
|
85
|
-
endpoints.length,
|
|
86
|
-
"endpoints immediately",
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
61
|
return data;
|
|
90
|
-
})
|
|
91
|
-
.catch(function (err) {
|
|
92
|
-
console.warn(
|
|
93
|
-
"[BotVersion SDK] ⚠ Failed to register endpoints:",
|
|
94
|
-
err.message,
|
|
95
|
-
);
|
|
96
62
|
});
|
|
97
63
|
};
|
|
98
64
|
|
|
@@ -108,35 +74,14 @@ BotVersionClient.prototype._flush = function () {
|
|
|
108
74
|
var toSend = self._queue.slice();
|
|
109
75
|
self._queue = [];
|
|
110
76
|
|
|
111
|
-
if (self.debug) {
|
|
112
|
-
console.log(
|
|
113
|
-
"[BotVersion SDK] Flushing",
|
|
114
|
-
toSend.length,
|
|
115
|
-
"endpoints to platform",
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
77
|
self
|
|
120
78
|
._post("/api/sdk/register-endpoints", {
|
|
121
79
|
workspaceKey: self.apiKey,
|
|
122
80
|
endpoints: toSend,
|
|
123
81
|
})
|
|
124
|
-
.then(function (data) {
|
|
125
|
-
if (self.debug) {
|
|
126
|
-
console.log(
|
|
127
|
-
"[BotVersion SDK] Registered",
|
|
128
|
-
data.succeeded,
|
|
129
|
-
"endpoints successfully",
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
})
|
|
82
|
+
.then(function (data) {})
|
|
133
83
|
.catch(function (err) {
|
|
134
|
-
|
|
135
|
-
console.warn(
|
|
136
|
-
"[BotVersion SDK] Failed to register endpoints:",
|
|
137
|
-
err.message,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
84
|
+
console.error("[botversion] flush failed:", err.message);
|
|
140
85
|
});
|
|
141
86
|
};
|
|
142
87
|
|
|
@@ -160,27 +105,10 @@ BotVersionClient.prototype.registerRoutePatterns = function (patterns) {
|
|
|
160
105
|
var self = this;
|
|
161
106
|
if (!patterns || patterns.length === 0) return Promise.resolve();
|
|
162
107
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"route patterns to platform",
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return self
|
|
172
|
-
._post("/api/sdk/register-route-patterns", {
|
|
173
|
-
workspaceKey: self.apiKey,
|
|
174
|
-
patterns: patterns,
|
|
175
|
-
})
|
|
176
|
-
.catch(function (err) {
|
|
177
|
-
if (self.debug) {
|
|
178
|
-
console.warn(
|
|
179
|
-
"[BotVersion SDK] Failed to register route patterns:",
|
|
180
|
-
err.message,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
108
|
+
return self._post("/api/sdk/register-route-patterns", {
|
|
109
|
+
workspaceKey: self.apiKey,
|
|
110
|
+
patterns: patterns,
|
|
111
|
+
});
|
|
184
112
|
};
|
|
185
113
|
|
|
186
114
|
/**
|
|
@@ -324,145 +252,4 @@ BotVersionClient.prototype._get = function (path) {
|
|
|
324
252
|
});
|
|
325
253
|
};
|
|
326
254
|
|
|
327
|
-
/**
|
|
328
|
-
* Connect to platform via WebSocket — establishes persistent connection
|
|
329
|
-
* Auto-reconnects every 5 seconds if connection drops
|
|
330
|
-
*/
|
|
331
|
-
BotVersionClient.prototype.connect = function () {
|
|
332
|
-
var self = this;
|
|
333
|
-
// Run in next tick so it doesn't block initialization
|
|
334
|
-
setImmediate(function () {
|
|
335
|
-
self._wsLoop();
|
|
336
|
-
});
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
BotVersionClient.prototype._wsLoop = function () {
|
|
340
|
-
var self = this;
|
|
341
|
-
|
|
342
|
-
var wsUrl = self.platformUrl
|
|
343
|
-
.replace("https://", "wss://")
|
|
344
|
-
.replace("http://", "ws://")
|
|
345
|
-
.replace(":3000", ":3001");
|
|
346
|
-
wsUrl = wsUrl + "?apiKey=" + encodeURIComponent(self.apiKey);
|
|
347
|
-
|
|
348
|
-
var WebSocket;
|
|
349
|
-
try {
|
|
350
|
-
WebSocket = require("ws");
|
|
351
|
-
} catch (e) {
|
|
352
|
-
console.error(
|
|
353
|
-
"[BotVersion SDK] ❌ 'ws' package not installed. Run: npm install ws",
|
|
354
|
-
);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function attempt() {
|
|
359
|
-
var ws = new WebSocket(wsUrl);
|
|
360
|
-
self._ws = ws;
|
|
361
|
-
|
|
362
|
-
ws.on("open", function () {
|
|
363
|
-
if (self.debug) {
|
|
364
|
-
console.log("[BotVersion SDK] ✅ WebSocket connected to platform");
|
|
365
|
-
}
|
|
366
|
-
// Identify this SDK to the platform
|
|
367
|
-
ws.send(
|
|
368
|
-
JSON.stringify({
|
|
369
|
-
type: "IDENTIFY",
|
|
370
|
-
apiKey: self.apiKey,
|
|
371
|
-
}),
|
|
372
|
-
);
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
ws.on("message", function (data) {
|
|
376
|
-
try {
|
|
377
|
-
var msg = JSON.parse(data.toString());
|
|
378
|
-
|
|
379
|
-
if (msg.type === "EXECUTE_CALL") {
|
|
380
|
-
self._handleExecuteCall(msg);
|
|
381
|
-
}
|
|
382
|
-
} catch (e) {
|
|
383
|
-
if (self.debug) {
|
|
384
|
-
console.warn("[BotVersion SDK] ⚠ Error parsing message:", e.message);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
ws.on("error", function (err) {
|
|
390
|
-
if (self.debug) {
|
|
391
|
-
console.warn("[BotVersion SDK] ⚠ WebSocket error:", err.message);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
ws.on("close", function () {
|
|
396
|
-
if (self.debug) {
|
|
397
|
-
console.log(
|
|
398
|
-
"[BotVersion SDK] WebSocket closed — reconnecting in 5s...",
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
self._ws = null;
|
|
402
|
-
// Auto-reconnect after 5 seconds
|
|
403
|
-
setTimeout(attempt, 5000);
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
attempt();
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
BotVersionClient.prototype._handleExecuteCall = function (data) {
|
|
411
|
-
var self = this;
|
|
412
|
-
|
|
413
|
-
var callId = data.callId;
|
|
414
|
-
var method = data.method;
|
|
415
|
-
var path = data.path;
|
|
416
|
-
var body = data.body;
|
|
417
|
-
var cookies = data.cookies || "";
|
|
418
|
-
var headers = data.headers || {};
|
|
419
|
-
var baseUrl = data.baseUrl || "http://127.0.0.1:3000";
|
|
420
|
-
|
|
421
|
-
if (!self._executor) {
|
|
422
|
-
self._sendCallResult(callId, {
|
|
423
|
-
status: 500,
|
|
424
|
-
ok: false,
|
|
425
|
-
data: { error: "No executor registered" },
|
|
426
|
-
});
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
self
|
|
431
|
-
._executor(method, path, body, cookies, headers, baseUrl)
|
|
432
|
-
.then(function (result) {
|
|
433
|
-
self._sendCallResult(callId, result);
|
|
434
|
-
})
|
|
435
|
-
.catch(function (err) {
|
|
436
|
-
self._sendCallResult(callId, {
|
|
437
|
-
status: 500,
|
|
438
|
-
ok: false,
|
|
439
|
-
data: { error: err.message },
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
BotVersionClient.prototype._sendCallResult = function (callId, result) {
|
|
445
|
-
var self = this;
|
|
446
|
-
try {
|
|
447
|
-
if (self._ws && self._ws.readyState === 1) {
|
|
448
|
-
// 1 = OPEN
|
|
449
|
-
self._ws.send(
|
|
450
|
-
JSON.stringify({
|
|
451
|
-
type: "CALL_RESULT",
|
|
452
|
-
callId: callId,
|
|
453
|
-
result: result,
|
|
454
|
-
}),
|
|
455
|
-
);
|
|
456
|
-
} else if (self.debug) {
|
|
457
|
-
console.warn(
|
|
458
|
-
"[BotVersion SDK] ⚠ WebSocket not open — could not send CALL_RESULT",
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
|
-
} catch (e) {
|
|
462
|
-
if (self.debug) {
|
|
463
|
-
console.warn("[BotVersion SDK] ⚠ Failed to send CALL_RESULT:", e.message);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
|
|
468
255
|
module.exports = BotVersionClient;
|
package/index.js
CHANGED
|
@@ -26,22 +26,17 @@ var BotVersion = {
|
|
|
26
26
|
app = null;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (!options.apiKey) {
|
|
30
|
-
console.error("[BotVersion SDK] ❌ apiKey is required.");
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
29
|
// Restore from global if module was re-imported after hot reload
|
|
35
30
|
if (global._botVersionClient) {
|
|
36
31
|
this._client = global._botVersionClient;
|
|
37
32
|
this._options = global._botVersionOptions;
|
|
38
33
|
this._initialized = true;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
// Re-attach interceptor after hot reload
|
|
35
|
+
interceptor.attachNextJsInterceptor(this._client, {
|
|
36
|
+
exclude: (global._botVersionOptions || {}).exclude || [],
|
|
37
|
+
apiPrefix: (global._botVersionOptions || {}).apiPrefix || "/api",
|
|
38
|
+
debug: (global._botVersionOptions || {}).debug || false,
|
|
39
|
+
});
|
|
45
40
|
return;
|
|
46
41
|
}
|
|
47
42
|
|
|
@@ -51,7 +46,7 @@ var BotVersion = {
|
|
|
51
46
|
|
|
52
47
|
this._client = new BotVersionClient({
|
|
53
48
|
apiKey: options.apiKey,
|
|
54
|
-
platformUrl: options.platformUrl || "
|
|
49
|
+
platformUrl: options.platformUrl || "https://botversion.com",
|
|
55
50
|
debug: options.debug || false,
|
|
56
51
|
timeout: options.timeout || 30000,
|
|
57
52
|
});
|
|
@@ -62,80 +57,34 @@ var BotVersion = {
|
|
|
62
57
|
var self = this;
|
|
63
58
|
var debug = options.debug || false;
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
console.log("[BotVersion SDK] Initializing...");
|
|
67
|
-
if (app) {
|
|
68
|
-
console.log("[BotVersion SDK] Mode: Express");
|
|
69
|
-
} else {
|
|
70
|
-
console.log("[BotVersion SDK] Mode: Next.js (file-based routing)");
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ── Framework detection ──────────────────────────────────────────────────
|
|
75
|
-
if (app) {
|
|
76
|
-
var frameworkCheck = detectFramework(app);
|
|
77
|
-
if (!frameworkCheck.supported) {
|
|
78
|
-
console.error(
|
|
79
|
-
"[BotVersion SDK] ❌ Unsupported framework:",
|
|
80
|
-
frameworkCheck.name,
|
|
81
|
-
);
|
|
82
|
-
console.error(
|
|
83
|
-
"[BotVersion SDK] ❌ Currently supports Express and Next.js only.",
|
|
84
|
-
);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (debug) {
|
|
88
|
-
console.log(
|
|
89
|
-
"[BotVersion SDK] ✅ Framework detected:",
|
|
90
|
-
frameworkCheck.name,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Runtime interceptor — Express only ───────────────────────────────────
|
|
60
|
+
// ── Runtime interceptor — Express or Next.js ─────────────────────────────
|
|
96
61
|
if (app && app.use) {
|
|
62
|
+
// Express
|
|
97
63
|
interceptor.attachInterceptor(app, self._client, {
|
|
98
64
|
exclude: options.exclude || [],
|
|
99
65
|
apiPrefix: options.apiPrefix || null,
|
|
100
66
|
debug: debug,
|
|
101
67
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
110
|
-
}
|
|
68
|
+
} else {
|
|
69
|
+
// Next.js — patch the global fetch/http server to intercept API calls
|
|
70
|
+
interceptor.attachNextJsInterceptor(self._client, {
|
|
71
|
+
exclude: options.exclude || [],
|
|
72
|
+
apiPrefix: options.apiPrefix || "/api",
|
|
73
|
+
debug: debug,
|
|
74
|
+
});
|
|
111
75
|
}
|
|
112
76
|
|
|
113
77
|
// ── Static scan ──────────────────────────────────────────────────────────
|
|
114
78
|
setTimeout(function () {
|
|
115
79
|
var endpoints = [];
|
|
116
80
|
|
|
117
|
-
// Express scan
|
|
118
81
|
// Express scan
|
|
119
82
|
if (app) {
|
|
120
|
-
console.log("[BotVersion SDK] Scanning Express routes...");
|
|
121
83
|
var cwd = options.cwd || process.cwd();
|
|
122
84
|
endpoints = scanner.scanExpressRoutes(app, cwd);
|
|
123
|
-
console.log(
|
|
124
|
-
"[BotVersion SDK] Found",
|
|
125
|
-
endpoints.length,
|
|
126
|
-
"Express routes",
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
if (endpoints.length === 0) {
|
|
130
|
-
console.warn("[BotVersion SDK] ⚠ No endpoints found.");
|
|
131
|
-
console.warn(
|
|
132
|
-
"[BotVersion SDK] ⚠ Make sure routes are defined BEFORE BotVersion.init()",
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
85
|
}
|
|
136
86
|
|
|
137
87
|
// Next.js scan
|
|
138
|
-
// Next.js scan — auto detect all possible structures
|
|
139
88
|
if (!app) {
|
|
140
89
|
const fs = require("fs");
|
|
141
90
|
const path = require("path");
|
|
@@ -153,91 +102,32 @@ var BotVersion = {
|
|
|
153
102
|
|
|
154
103
|
for (const dir of possibleAppDirs) {
|
|
155
104
|
if (fs.existsSync(dir)) {
|
|
156
|
-
console.log("[BotVersion SDK] Scanning App Router routes at:", dir);
|
|
157
105
|
const routes = scanner.scanNextJsAppRoutes(dir);
|
|
158
106
|
endpoints = endpoints.concat(routes);
|
|
159
|
-
console.log(
|
|
160
|
-
"[BotVersion SDK] Found",
|
|
161
|
-
routes.length,
|
|
162
|
-
"App Router routes",
|
|
163
|
-
);
|
|
164
107
|
}
|
|
165
108
|
}
|
|
166
109
|
|
|
167
110
|
for (const dir of possiblePagesDirs) {
|
|
168
111
|
if (fs.existsSync(dir)) {
|
|
169
|
-
console.log(
|
|
170
|
-
"[BotVersion SDK] Scanning Pages Router routes at:",
|
|
171
|
-
dir,
|
|
172
|
-
);
|
|
173
112
|
const routes = scanner.scanNextJsRoutes(dir);
|
|
174
113
|
endpoints = endpoints.concat(routes);
|
|
175
|
-
console.log(
|
|
176
|
-
"[BotVersion SDK] Found",
|
|
177
|
-
routes.length,
|
|
178
|
-
"Pages Router routes",
|
|
179
|
-
);
|
|
180
114
|
}
|
|
181
115
|
}
|
|
182
116
|
}
|
|
183
117
|
|
|
184
|
-
// Neither Express nor pagesDir
|
|
185
|
-
// Neither Express nor Next.js routes found
|
|
186
|
-
if (!app && endpoints.length === 0) {
|
|
187
|
-
console.error(
|
|
188
|
-
"[BotVersion SDK] ❌ No routes found. Make sure your app/api or pages/api folder exists.",
|
|
189
|
-
);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
118
|
// Send to platform
|
|
194
119
|
if (endpoints.length > 0) {
|
|
195
|
-
|
|
196
|
-
"[BotVersion SDK] Sending",
|
|
197
|
-
endpoints.length,
|
|
198
|
-
"endpoints to platform...",
|
|
199
|
-
);
|
|
200
|
-
self._client
|
|
201
|
-
.registerEndpoints(endpoints)
|
|
202
|
-
.then(function () {
|
|
203
|
-
console.log(
|
|
204
|
-
"[BotVersion SDK] ✅ Endpoints queued —",
|
|
205
|
-
endpoints.length,
|
|
206
|
-
"endpoints will be sent shortly",
|
|
207
|
-
);
|
|
208
|
-
})
|
|
209
|
-
.catch(function (err) {
|
|
210
|
-
console.error(
|
|
211
|
-
"[BotVersion SDK] ❌ Failed to register endpoints:",
|
|
212
|
-
err.message,
|
|
213
|
-
);
|
|
214
|
-
});
|
|
120
|
+
self._client.registerEndpoints(endpoints);
|
|
215
121
|
}
|
|
216
122
|
|
|
217
123
|
var routePatterns = scanner.scanFrontendRoutes(
|
|
218
124
|
options.cwd || process.cwd(),
|
|
219
125
|
);
|
|
220
126
|
if (routePatterns.length > 0) {
|
|
221
|
-
console.log(
|
|
222
|
-
"[BotVersion SDK] Found",
|
|
223
|
-
routePatterns.length,
|
|
224
|
-
"frontend route patterns",
|
|
225
|
-
);
|
|
226
127
|
self._client
|
|
227
128
|
.registerRoutePatterns(routePatterns)
|
|
228
|
-
.
|
|
229
|
-
console.log("[BotVersion SDK] ✅ Route patterns registered");
|
|
230
|
-
})
|
|
231
|
-
.catch(function (err) {
|
|
232
|
-
console.error(
|
|
233
|
-
"[BotVersion SDK] ❌ Failed to register route patterns:",
|
|
234
|
-
err.message,
|
|
235
|
-
);
|
|
236
|
-
});
|
|
129
|
+
.catch(function (err) {});
|
|
237
130
|
}
|
|
238
|
-
|
|
239
|
-
self._client.connect();
|
|
240
|
-
console.log("[BotVersion SDK] ✅ Initialization complete");
|
|
241
131
|
}, 500);
|
|
242
132
|
},
|
|
243
133
|
|
|
@@ -270,36 +160,3 @@ module.exports.default = BotVersion;
|
|
|
270
160
|
module.exports.init = BotVersion.init;
|
|
271
161
|
module.exports.getEndpoints = BotVersion.getEndpoints;
|
|
272
162
|
module.exports.registerEndpoint = BotVersion.registerEndpoint;
|
|
273
|
-
|
|
274
|
-
// ── Framework detection ──────────────────────────────────────────────────────
|
|
275
|
-
function detectFramework(app) {
|
|
276
|
-
if (!app) return { supported: false, name: "unknown (no app passed)" };
|
|
277
|
-
|
|
278
|
-
var isExpress =
|
|
279
|
-
typeof app.use === "function" &&
|
|
280
|
-
typeof app.get === "function" &&
|
|
281
|
-
typeof app.post === "function" &&
|
|
282
|
-
(app._router !== undefined ||
|
|
283
|
-
app.stack !== undefined ||
|
|
284
|
-
app.lazyrouter !== undefined);
|
|
285
|
-
|
|
286
|
-
if (isExpress) return { supported: true, name: "Express" };
|
|
287
|
-
|
|
288
|
-
if (typeof app.addHook === "function" && typeof app.route === "function") {
|
|
289
|
-
return { supported: false, name: "Fastify" };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (
|
|
293
|
-
typeof app.use === "function" &&
|
|
294
|
-
app.context !== undefined &&
|
|
295
|
-
typeof app.get !== "function"
|
|
296
|
-
) {
|
|
297
|
-
return { supported: false, name: "Koa" };
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (typeof app.route === "function" && typeof app.start === "function") {
|
|
301
|
-
return { supported: false, name: "Hapi" };
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return { supported: false, name: "unknown" };
|
|
305
|
-
}
|
package/interceptor.js
CHANGED
|
@@ -1,109 +1,7 @@
|
|
|
1
1
|
// botversion-sdk/interceptor.js
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
* Makes an internal HTTP request to the user's own backend.
|
|
6
|
-
* Forwards cookies, auth headers, and CSRF tokens.
|
|
7
|
-
*/
|
|
8
|
-
function makeInternalRequest(method, path, body, cookies, headers, baseUrl) {
|
|
9
|
-
return new Promise(function (resolve) {
|
|
10
|
-
var http = require("http");
|
|
11
|
-
var https = require("https");
|
|
12
|
-
var url = require("url");
|
|
13
|
-
|
|
14
|
-
baseUrl = baseUrl || "http://127.0.0.1:8000";
|
|
15
|
-
var fullUrl = baseUrl + path;
|
|
16
|
-
var parsedUrl = url.parse(fullUrl);
|
|
17
|
-
var isHttps = parsedUrl.protocol === "https:";
|
|
18
|
-
var lib = isHttps ? https : http;
|
|
19
|
-
|
|
20
|
-
var bodyStr = body ? JSON.stringify(body) : null;
|
|
21
|
-
|
|
22
|
-
var reqHeaders = {
|
|
23
|
-
"Content-Type": "application/json",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
if (bodyStr) {
|
|
27
|
-
reqHeaders["Content-Length"] = String(Buffer.byteLength(bodyStr));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Forward cookies — this is what identifies the user
|
|
31
|
-
if (cookies) reqHeaders["Cookie"] = cookies;
|
|
32
|
-
|
|
33
|
-
// Forward auth header
|
|
34
|
-
var authHeader =
|
|
35
|
-
headers && (headers["authorization"] || headers["Authorization"]);
|
|
36
|
-
if (authHeader) reqHeaders["Authorization"] = authHeader;
|
|
37
|
-
|
|
38
|
-
// Forward CSRF token
|
|
39
|
-
var csrf =
|
|
40
|
-
headers &&
|
|
41
|
-
(headers["x-csrftoken"] ||
|
|
42
|
-
headers["X-CSRFToken"] ||
|
|
43
|
-
headers["x-xsrf-token"] ||
|
|
44
|
-
headers["X-XSRF-TOKEN"]);
|
|
45
|
-
if (csrf) reqHeaders["X-CSRFToken"] = csrf;
|
|
46
|
-
|
|
47
|
-
var reqOptions = {
|
|
48
|
-
hostname: parsedUrl.hostname,
|
|
49
|
-
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
50
|
-
path: parsedUrl.path,
|
|
51
|
-
method: method.toUpperCase(),
|
|
52
|
-
headers: reqHeaders,
|
|
53
|
-
timeout: 30000,
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
var req = lib.request(reqOptions, function (res) {
|
|
57
|
-
var data = "";
|
|
58
|
-
res.on("data", function (chunk) {
|
|
59
|
-
data += chunk;
|
|
60
|
-
});
|
|
61
|
-
res.on("end", function () {
|
|
62
|
-
var parsed;
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(data);
|
|
65
|
-
} catch (e) {
|
|
66
|
-
parsed = { raw: data };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Handle redirects
|
|
70
|
-
if (
|
|
71
|
-
res.statusCode >= 300 &&
|
|
72
|
-
res.statusCode < 400 &&
|
|
73
|
-
res.headers.location
|
|
74
|
-
) {
|
|
75
|
-
makeInternalRequest(
|
|
76
|
-
res.statusCode === 303 ? "GET" : method,
|
|
77
|
-
res.headers.location,
|
|
78
|
-
res.statusCode === 303 ? null : body,
|
|
79
|
-
cookies,
|
|
80
|
-
headers,
|
|
81
|
-
baseUrl,
|
|
82
|
-
).then(resolve);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
resolve({
|
|
87
|
-
status: res.statusCode,
|
|
88
|
-
ok: res.statusCode >= 200 && res.statusCode < 300,
|
|
89
|
-
data: parsed,
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
req.on("error", function (err) {
|
|
95
|
-
resolve({ status: 500, ok: false, data: { error: err.message } });
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
req.on("timeout", function () {
|
|
99
|
-
req.destroy();
|
|
100
|
-
resolve({ status: 500, ok: false, data: { error: "Request timed out" } });
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (bodyStr) req.write(bodyStr);
|
|
104
|
-
req.end();
|
|
105
|
-
});
|
|
106
|
-
}
|
|
4
|
+
const reportedEndpoints = new Set();
|
|
107
5
|
|
|
108
6
|
/**
|
|
109
7
|
* Attaches a middleware to the Express app that
|
|
@@ -122,9 +20,6 @@ function attachInterceptor(app, client, options) {
|
|
|
122
20
|
"/public",
|
|
123
21
|
].concat(options.exclude || []);
|
|
124
22
|
|
|
125
|
-
// Track which endpoints we have already reported
|
|
126
|
-
const reportedEndpoints = new Set();
|
|
127
|
-
|
|
128
23
|
app.use(function botVersionInterceptor(req, res, next) {
|
|
129
24
|
const path = req.path || req.url || "";
|
|
130
25
|
const shouldIgnore = ignorePaths.some(function (p) {
|
|
@@ -183,22 +78,13 @@ function attachInterceptor(app, client, options) {
|
|
|
183
78
|
detectedBy: "runtime",
|
|
184
79
|
})
|
|
185
80
|
.catch(function (err) {
|
|
186
|
-
|
|
187
|
-
console.warn(
|
|
188
|
-
"[BotVersion SDK] Failed to report endpoint:",
|
|
189
|
-
err.message,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
81
|
+
console.error("[botversion] update_endpoint failed:", err.message);
|
|
192
82
|
});
|
|
193
83
|
});
|
|
194
84
|
}
|
|
195
85
|
|
|
196
86
|
next();
|
|
197
87
|
});
|
|
198
|
-
// Register executor so WebSocket can forward calls to user's backend
|
|
199
|
-
client.setExecutor(function (method, path, body, cookies, headers, baseUrl) {
|
|
200
|
-
return makeInternalRequest(method, path, body, cookies, headers, baseUrl);
|
|
201
|
-
});
|
|
202
88
|
}
|
|
203
89
|
|
|
204
90
|
/**
|
|
@@ -286,8 +172,59 @@ function buildBodyStructure(body) {
|
|
|
286
172
|
return structure;
|
|
287
173
|
}
|
|
288
174
|
|
|
175
|
+
function attachNextJsInterceptor(client, options) {
|
|
176
|
+
try {
|
|
177
|
+
const http = require("http");
|
|
178
|
+
const originalEmit = http.Server.prototype.emit;
|
|
179
|
+
|
|
180
|
+
http.Server.prototype.emit = function (event, req, res) {
|
|
181
|
+
if (event === "request") {
|
|
182
|
+
const path = req.url ? req.url.split("?")[0] : "";
|
|
183
|
+
const method = req.method ? req.method.toUpperCase() : "";
|
|
184
|
+
|
|
185
|
+
const shouldIgnore = (options.exclude || [])
|
|
186
|
+
.concat(["/health", "/favicon.ico", "/_next", "/static", "/public"])
|
|
187
|
+
.some(function (p) {
|
|
188
|
+
return path.startsWith(p);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const isApiPath = path.startsWith(options.apiPrefix || "/api");
|
|
192
|
+
|
|
193
|
+
if (!shouldIgnore && isApiPath) {
|
|
194
|
+
const normalizedPath = normalizePath(path);
|
|
195
|
+
const endpointKey = method + ":" + normalizedPath;
|
|
196
|
+
|
|
197
|
+
if (!reportedEndpoints.has(endpointKey)) {
|
|
198
|
+
reportedEndpoints.add(endpointKey);
|
|
199
|
+
|
|
200
|
+
setImmediate(function () {
|
|
201
|
+
client
|
|
202
|
+
.updateEndpoint({
|
|
203
|
+
method: method,
|
|
204
|
+
path: normalizedPath,
|
|
205
|
+
detectedBy: "runtime",
|
|
206
|
+
})
|
|
207
|
+
.catch(function (err) {
|
|
208
|
+
console.error("[botversion] update failed:", err.message);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return originalEmit.apply(this, arguments);
|
|
216
|
+
};
|
|
217
|
+
} catch (err) {
|
|
218
|
+
console.error(
|
|
219
|
+
"[botversion] failed to attach Next.js interceptor:",
|
|
220
|
+
err.message,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
289
225
|
module.exports = {
|
|
290
226
|
attachInterceptor,
|
|
227
|
+
attachNextJsInterceptor,
|
|
291
228
|
normalizePath,
|
|
292
229
|
buildBodyStructure,
|
|
293
230
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botversion-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "BotVersion SDK — auto-detect and register your API endpoints",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"author": "Saurav Dhakal",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"@babel/parser": "^7.29.3",
|
|
20
|
+
"@babel/traverse": "^7.29.0",
|
|
21
|
+
"express": "^4.18.2"
|
|
21
22
|
}
|
|
22
23
|
}
|
package/scanner.js
CHANGED
|
@@ -29,14 +29,9 @@ function scanExpressRoutes(app, cwd) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
console.log(`\n[DEBUG] ===== JS SCAN SUMMARY =====`);
|
|
33
32
|
endpoints.forEach((ep) => {
|
|
34
33
|
const status = ep.requestBody ? "✅" : "❌ NULL";
|
|
35
|
-
console.log(
|
|
36
|
-
`[DEBUG] ${status} ${ep.method.padEnd(6)} ${ep.path} → ${JSON.stringify(ep.requestBody)}`,
|
|
37
|
-
);
|
|
38
34
|
});
|
|
39
|
-
console.log(`[DEBUG] ================================\n`);
|
|
40
35
|
|
|
41
36
|
return endpoints;
|
|
42
37
|
}
|
|
@@ -51,7 +46,6 @@ function scanExpressFileStatically(filePath, seen) {
|
|
|
51
46
|
} catch {
|
|
52
47
|
return endpoints;
|
|
53
48
|
}
|
|
54
|
-
console.log(`[DEBUG] Scanning file statically: ${filePath}`);
|
|
55
49
|
|
|
56
50
|
// Match patterns like:
|
|
57
51
|
// app.get('/path', ...)
|
|
@@ -76,10 +70,6 @@ function scanExpressFileStatically(filePath, seen) {
|
|
|
76
70
|
const needsBody = ["POST", "PUT", "PATCH"].includes(effectiveMethod);
|
|
77
71
|
const bodyFields = needsBody ? extractBodyFieldsFromFile(content) : null;
|
|
78
72
|
|
|
79
|
-
console.log(
|
|
80
|
-
`[DEBUG] ${effectiveMethod} ${routePath} → bodyFields: ${JSON.stringify(bodyFields)}`,
|
|
81
|
-
);
|
|
82
|
-
|
|
83
73
|
const routeParamMap = buildRouteParamMap(routePath, []);
|
|
84
74
|
|
|
85
75
|
endpoints.push({
|
|
@@ -133,9 +123,6 @@ function extractRoutes(stack, prefix, endpoints, seen, bodyMap) {
|
|
|
133
123
|
const fields = extractBodyFieldsFromFile(fnStr);
|
|
134
124
|
if (fields) {
|
|
135
125
|
requestBody = fields;
|
|
136
|
-
console.log(
|
|
137
|
-
`[DEBUG] Found body from inline handler for ${method} ${routePath}`,
|
|
138
|
-
);
|
|
139
126
|
break;
|
|
140
127
|
}
|
|
141
128
|
}
|
|
@@ -143,12 +130,8 @@ function extractRoutes(stack, prefix, endpoints, seen, bodyMap) {
|
|
|
143
130
|
// Strategy 2: fall back to bodyMap using handler name
|
|
144
131
|
if (!requestBody) {
|
|
145
132
|
const handlerName = extractHandlerName(layer);
|
|
146
|
-
console.log(
|
|
147
|
-
`[DEBUG] ${method} ${routePath} → handlerName: ${handlerName}`,
|
|
148
|
-
);
|
|
149
133
|
if (handlerName && bodyMap[handlerName]) {
|
|
150
134
|
requestBody = bodyMap[handlerName];
|
|
151
|
-
console.log(`[DEBUG] Found body from bodyMap for ${handlerName}`);
|
|
152
135
|
}
|
|
153
136
|
}
|
|
154
137
|
}
|
|
@@ -190,7 +173,6 @@ function scanNextJsRoutes(pagesDir) {
|
|
|
190
173
|
const apiDir = path.join(pagesDir, "api");
|
|
191
174
|
|
|
192
175
|
if (!fs.existsSync(apiDir)) {
|
|
193
|
-
console.warn("[BotVersion SDK] No pages/api directory found at:", apiDir);
|
|
194
176
|
return endpoints;
|
|
195
177
|
}
|
|
196
178
|
|
|
@@ -345,6 +327,8 @@ function inferFieldType(fieldName, content) {
|
|
|
345
327
|
new RegExp(`${fieldName}\\s*===?\\s*(true|false)`),
|
|
346
328
|
new RegExp(`(true|false)\\s*===?\\s*${fieldName}`),
|
|
347
329
|
new RegExp(`Boolean\\s*\\(\\s*${fieldName}`),
|
|
330
|
+
new RegExp(`typeof\\s+${fieldName}\\s*!==?\\s*["']boolean["']`),
|
|
331
|
+
new RegExp(`typeof\\s+${fieldName}\\s*===?\\s*["']boolean["']`),
|
|
348
332
|
];
|
|
349
333
|
if (boolPatterns.some((p) => p.test(content))) return "boolean";
|
|
350
334
|
|
|
@@ -411,9 +395,6 @@ function extractBodyFieldsFromFile(content) {
|
|
|
411
395
|
? { type: "array", items: { type: "object" } }
|
|
412
396
|
: { type };
|
|
413
397
|
});
|
|
414
|
-
console.log(
|
|
415
|
-
`[DEBUG] extractBodyFieldsFromFile found fields: ${JSON.stringify([...fields])}`,
|
|
416
|
-
);
|
|
417
398
|
|
|
418
399
|
return { type: "object", properties };
|
|
419
400
|
}
|
|
@@ -425,7 +406,6 @@ function scanNextJsAppRoutes(appDir) {
|
|
|
425
406
|
|
|
426
407
|
const apiDir = path.join(appDir, "api");
|
|
427
408
|
if (!fs.existsSync(apiDir)) {
|
|
428
|
-
console.warn("[BotVersion SDK] No app/api directory found at:", apiDir);
|
|
429
409
|
return endpoints;
|
|
430
410
|
}
|
|
431
411
|
|
|
@@ -710,16 +690,8 @@ function scanAllExpressFiles(cwd) {
|
|
|
710
690
|
);
|
|
711
691
|
|
|
712
692
|
if (isExpressFile) {
|
|
713
|
-
console.log(`[DEBUG] Found route file: ${fullPath}`); // already have this
|
|
714
693
|
routeFiles.push(fullPath);
|
|
715
694
|
}
|
|
716
|
-
|
|
717
|
-
// ADD THIS — log files that are being SKIPPED
|
|
718
|
-
else if (/\.(js|ts)$/.test(entry)) {
|
|
719
|
-
console.log(
|
|
720
|
-
`[DEBUG] Skipped (not detected as route file): ${fullPath}`,
|
|
721
|
-
);
|
|
722
|
-
}
|
|
723
695
|
} catch {
|
|
724
696
|
continue;
|
|
725
697
|
}
|
|
@@ -795,23 +767,6 @@ function buildBodyMap(cwd) {
|
|
|
795
767
|
/export\s+const\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{/g,
|
|
796
768
|
];
|
|
797
769
|
|
|
798
|
-
if (content.includes("req.body")) {
|
|
799
|
-
console.log(`[DEBUG] bodyMap: found req.body in ${fullPath}`);
|
|
800
|
-
console.log(
|
|
801
|
-
`[DEBUG] bodyMap: fnNames found: ${JSON.stringify([
|
|
802
|
-
...(() => {
|
|
803
|
-
const names = new Set();
|
|
804
|
-
for (const p of fnPatterns) {
|
|
805
|
-
p.lastIndex = 0;
|
|
806
|
-
let m;
|
|
807
|
-
while ((m = p.exec(content)) !== null) names.add(m[1]);
|
|
808
|
-
}
|
|
809
|
-
return names;
|
|
810
|
-
})(),
|
|
811
|
-
])}`,
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
770
|
// Map every function name in this file to the whole file's body fields.
|
|
816
771
|
// Brace-counting is unreliable across JS/TS variations, so we use
|
|
817
772
|
// file-level field extraction here. Per-function accuracy is handled
|
|
@@ -835,7 +790,6 @@ function buildBodyMap(cwd) {
|
|
|
835
790
|
}
|
|
836
791
|
|
|
837
792
|
walk(cwd, 0);
|
|
838
|
-
console.log(`[DEBUG] bodyMap keys: ${Object.keys(bodyMap)}`);
|
|
839
793
|
return bodyMap;
|
|
840
794
|
}
|
|
841
795
|
|
|
@@ -932,8 +886,6 @@ function scanConfigBasedRoutes(cwd) {
|
|
|
932
886
|
continue;
|
|
933
887
|
}
|
|
934
888
|
|
|
935
|
-
console.log("[BotVersion SDK] Scanning config-based routes in:", filePath);
|
|
936
|
-
|
|
937
889
|
// Pattern 1 — React Router JSX: <Route path="/:projectId/dashboard" />
|
|
938
890
|
const jsxRouteMatches = content.matchAll(
|
|
939
891
|
/<Route[^>]+path=["']([^"']+)["']/g,
|
|
@@ -984,12 +936,6 @@ function addConfigPattern(routePath, seen, patterns) {
|
|
|
984
936
|
if (Object.keys(paramMap).length === 0) return;
|
|
985
937
|
|
|
986
938
|
patterns.push({ pattern: normalized, params: paramMap });
|
|
987
|
-
console.log(
|
|
988
|
-
"[BotVersion SDK] Found config-based route pattern:",
|
|
989
|
-
normalized,
|
|
990
|
-
"→",
|
|
991
|
-
paramMap,
|
|
992
|
-
);
|
|
993
939
|
}
|
|
994
940
|
|
|
995
941
|
function scanFrontendRoutes(cwd) {
|
|
@@ -1093,12 +1039,6 @@ function scanFrontendRoutes(cwd) {
|
|
|
1093
1039
|
if (Object.keys(paramMap).length === 0) return; // no dynamic params, skip static routes
|
|
1094
1040
|
|
|
1095
1041
|
patterns.push({ pattern, params: paramMap });
|
|
1096
|
-
console.log(
|
|
1097
|
-
"[BotVersion SDK] Found frontend route pattern:",
|
|
1098
|
-
pattern,
|
|
1099
|
-
"→",
|
|
1100
|
-
paramMap,
|
|
1101
|
-
);
|
|
1102
1042
|
});
|
|
1103
1043
|
}
|
|
1104
1044
|
|