botversion-sdk 1.1.7 → 2.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.
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ var path = require("path");
5
+ var fs = require("fs");
6
+ var sdk = require("../index");
7
+ var BotVersionClient = require("../client");
8
+
9
+ // Auto-load env files from the customer's project, same precedence Next.js
10
+ // uses: .env first, then .env.local (which is allowed to override .env).
11
+ // dotenv.config() never overwrites a variable already set in process.env,
12
+ // so if the customer already exported the key in their shell/CI, that
13
+ // still takes priority over anything in these files.
14
+ (function loadEnvFiles() {
15
+ var dotenv;
16
+ try {
17
+ dotenv = require("dotenv");
18
+ } catch (e) {
19
+ return; // dotenv not installed for some reason — skip silently
20
+ }
21
+ var cwd = process.cwd();
22
+ [".env", ".env.local"].forEach(function (file) {
23
+ var filePath = path.join(cwd, file);
24
+ if (fs.existsSync(filePath)) {
25
+ dotenv.config({ path: filePath });
26
+ }
27
+ });
28
+ })();
29
+
30
+ var apiKey = process.env.BOTVERSION_API_KEY;
31
+ var platformUrl =
32
+ process.env.BOTVERSION_PLATFORM_URL || "https://console.botversion.com";
33
+
34
+ if (!apiKey) {
35
+ console.error(
36
+ "[BotVersion] BOTVERSION_API_KEY environment variable is not set. Skipping backend endpoint scan.",
37
+ );
38
+ process.exit(0);
39
+ }
40
+
41
+ var cwd = process.cwd();
42
+ var client = new BotVersionClient({ apiKey: apiKey, platformUrl: platformUrl });
43
+
44
+ var detected = sdk.detectFrameworks(cwd);
45
+ var projectType = sdk.classifyInstallation(detected.backend, detected.frontend);
46
+
47
+ var shouldScanBackend =
48
+ projectType === "fullstack" ||
49
+ projectType === "backend-only" ||
50
+ projectType === "mixed";
51
+
52
+ if (!shouldScanBackend) {
53
+ process.exit(0);
54
+ }
55
+
56
+ // No live `app` instance exists at build time — pass null.
57
+ // All scanner functions read directly from the filesystem via cwd.
58
+ var endpoints = sdk.runBackendScanner(detected.backend, null, {}, cwd);
59
+
60
+ if (endpoints.length === 0) {
61
+ process.exit(0);
62
+ }
63
+
64
+ client
65
+ .registerEndpointsNow(endpoints)
66
+ .then(function () {
67
+ process.exit(0);
68
+ })
69
+ .catch(function (err) {
70
+ console.error(
71
+ "[BotVersion] Failed to report backend endpoints:",
72
+ err.message,
73
+ );
74
+ process.exit(0);
75
+ });
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ var path = require("path");
5
+ var fs = require("fs");
6
+ var scanner = require("../scanner");
7
+ var BotVersionClient = require("../client");
8
+ var sdk = require("../index");
9
+
10
+ // Auto-load env files from the customer's project, same precedence Next.js
11
+ // uses: .env first, then .env.local (which is allowed to override .env).
12
+ // dotenv.config() never overwrites a variable already set in process.env,
13
+ // so if the customer already exported the key in their shell/CI, that
14
+ // still takes priority over anything in these files.
15
+ (function loadEnvFiles() {
16
+ var dotenv;
17
+ try {
18
+ dotenv = require("dotenv");
19
+ } catch (e) {
20
+ return; // dotenv not installed for some reason — skip silently
21
+ }
22
+ var cwd = process.cwd();
23
+ [".env", ".env.local"].forEach(function (file) {
24
+ var filePath = path.join(cwd, file);
25
+ if (fs.existsSync(filePath)) {
26
+ dotenv.config({ path: filePath });
27
+ }
28
+ });
29
+ })();
30
+
31
+ var apiKey = process.env.BOTVERSION_API_KEY;
32
+ var platformUrl =
33
+ process.env.BOTVERSION_PLATFORM_URL || "https://console.botversion.com";
34
+
35
+ if (!apiKey) {
36
+ console.error(
37
+ "[BotVersion] BOTVERSION_API_KEY environment variable is not set. Skipping frontend route scan.",
38
+ );
39
+ process.exit(0);
40
+ }
41
+
42
+ var cwd = process.cwd();
43
+ var client = new BotVersionClient({ apiKey: apiKey, platformUrl: platformUrl });
44
+
45
+ var detected = sdk.detectFrameworks(cwd);
46
+ var projectType = sdk.classifyInstallation(detected.backend, detected.frontend);
47
+ var routePatterns = scanner.scanFrontendRoutes(cwd);
48
+
49
+ client
50
+ .registerRoutePatterns(routePatterns, {
51
+ source: "cli",
52
+ projectType: projectType,
53
+ detectedBackend: detected.backend,
54
+ detectedFrontend: detected.frontend,
55
+ sdkLanguage: "javascript",
56
+ })
57
+ .then(function () {
58
+ process.exit(0);
59
+ })
60
+ .catch(function (err) {
61
+ console.error(
62
+ "[BotVersion] Failed to report frontend routes:",
63
+ err.message,
64
+ );
65
+ process.exit(0);
66
+ });
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 || "https://botversion.com";
14
+ this.platformUrl = options.platformUrl || "https://console.botversion.com";
15
15
  this.debug = options.debug || false;
16
16
  this.timeout = options.timeout || 30000;
17
17
 
@@ -99,14 +99,20 @@ BotVersionClient.prototype.updateEndpoint = function (endpoint) {
99
99
  });
100
100
  };
101
101
 
102
- BotVersionClient.prototype.registerRoutePatterns = function (patterns) {
102
+ BotVersionClient.prototype.registerRoutePatterns = function (
103
+ patterns,
104
+ classification,
105
+ ) {
103
106
  var self = this;
104
- if (!patterns || patterns.length === 0) return Promise.resolve();
105
-
106
- return self._post("/api/sdk/register-route-patterns", {
107
+ var body = {
107
108
  workspaceKey: self.apiKey,
108
- patterns: patterns,
109
- });
109
+ patterns: patterns || [],
110
+ classification: classification || null,
111
+ };
112
+ if ((!patterns || patterns.length === 0) && !classification) {
113
+ return Promise.resolve();
114
+ }
115
+ return self._post("/api/sdk/register-route-patterns", body);
110
116
  };
111
117
 
112
118
  /**
package/index.js CHANGED
@@ -161,6 +161,91 @@ function runBackendScanner(detectedBackend, app, options, cwd) {
161
161
  return endpoints;
162
162
  }
163
163
 
164
+ var FULLSTACK_FRAMEWORKS = ["nextjs", "nuxt", "sveltekit", "remix"];
165
+ var BACKEND_ONLY_FRAMEWORKS = [
166
+ "express",
167
+ "fastify",
168
+ "nestjs",
169
+ "koa",
170
+ "hapi",
171
+ "adonis",
172
+ ];
173
+
174
+ function classifyInstallation(detectedBackend, detectedFrontend) {
175
+ if (FULLSTACK_FRAMEWORKS.indexOf(detectedBackend) !== -1) {
176
+ return "fullstack";
177
+ }
178
+ if (
179
+ BACKEND_ONLY_FRAMEWORKS.indexOf(detectedBackend) !== -1 &&
180
+ detectedFrontend === "unknown"
181
+ ) {
182
+ return "backend-only";
183
+ }
184
+ if (detectedBackend === "unknown" && detectedFrontend !== "unknown") {
185
+ return "frontend-only";
186
+ }
187
+ if (detectedBackend === "unknown" && detectedFrontend === "unknown") {
188
+ return "unknown";
189
+ }
190
+ return "mixed";
191
+ }
192
+
193
+ // ── Run a full scan (backend endpoints + frontend routes) and report ────────
194
+ // This used to run automatically 3 seconds after server start. Now it only
195
+ // runs when the BotVersion dashboard's "Scan" button sends a request to this
196
+ // SDK's internal scan-trigger endpoint (see interceptor.js). This fixes scans
197
+ // not happening on serverless redeploys (Vercel, etc.) since there's no
198
+ // "restart" event to hook into there.
199
+ function runFullScan(
200
+ detectedBackend,
201
+ detectedFrontend,
202
+ app,
203
+ options,
204
+ cwd,
205
+ client,
206
+ ) {
207
+ var projectType = classifyInstallation(detectedBackend, detectedFrontend);
208
+
209
+ var result = {
210
+ endpointCount: 0,
211
+ routeCount: 0,
212
+ projectType: projectType,
213
+ detectedBackend: detectedBackend,
214
+ detectedFrontend: detectedFrontend,
215
+ sdkLanguage: "javascript",
216
+ };
217
+
218
+ var shouldScanBackend =
219
+ projectType === "fullstack" ||
220
+ projectType === "backend-only" ||
221
+ projectType === "mixed";
222
+
223
+ var shouldScanFrontend =
224
+ projectType === "fullstack" || projectType === "frontend-only";
225
+
226
+ if (shouldScanBackend) {
227
+ var endpoints = runBackendScanner(detectedBackend, app, options, cwd);
228
+ result.endpointCount = endpoints.length;
229
+
230
+ if (endpoints.length > 0) {
231
+ client.registerEndpoints(endpoints);
232
+ }
233
+ }
234
+
235
+ if (shouldScanFrontend) {
236
+ var routePatterns = scanner.scanFrontendRoutes(cwd);
237
+ result.routeCount = routePatterns.length;
238
+
239
+ if (routePatterns.length > 0) {
240
+ client.registerRoutePatterns(routePatterns).catch(function () {
241
+ // silently ignore — non-critical
242
+ });
243
+ }
244
+ }
245
+
246
+ return result;
247
+ }
248
+
164
249
  // ── Attach the right runtime interceptor ─────────────────────────────────────
165
250
  function attachRuntimeInterceptor(detectedBackend, app, client, options) {
166
251
  switch (detectedBackend) {
@@ -169,6 +254,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
169
254
  exclude: options.exclude || [],
170
255
  apiPrefix: options.apiPrefix || null,
171
256
  debug: options.debug || false,
257
+ scanSecret: options.scanSecret,
258
+ onScanRequested: options.onScanRequested,
172
259
  });
173
260
  break;
174
261
 
@@ -177,6 +264,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
177
264
  exclude: options.exclude || [],
178
265
  apiPrefix: options.apiPrefix || "/api",
179
266
  debug: options.debug || false,
267
+ scanSecret: options.scanSecret,
268
+ onScanRequested: options.onScanRequested,
180
269
  });
181
270
  break;
182
271
 
@@ -186,6 +275,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
186
275
  exclude: options.exclude || [],
187
276
  apiPrefix: options.apiPrefix || null,
188
277
  debug: options.debug || false,
278
+ scanSecret: options.scanSecret,
279
+ onScanRequested: options.onScanRequested,
189
280
  });
190
281
  }
191
282
  break;
@@ -196,6 +287,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
196
287
  exclude: options.exclude || [],
197
288
  apiPrefix: options.apiPrefix || null,
198
289
  debug: options.debug || false,
290
+ scanSecret: options.scanSecret,
291
+ onScanRequested: options.onScanRequested,
199
292
  });
200
293
  }
201
294
  break;
@@ -206,6 +299,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
206
299
  exclude: options.exclude || [],
207
300
  apiPrefix: options.apiPrefix || null,
208
301
  debug: options.debug || false,
302
+ scanSecret: options.scanSecret,
303
+ onScanRequested: options.onScanRequested,
209
304
  });
210
305
  }
211
306
  break;
@@ -215,6 +310,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
215
310
  exclude: options.exclude || [],
216
311
  apiPrefix: options.apiPrefix || "/",
217
312
  debug: options.debug || false,
313
+ scanSecret: options.scanSecret,
314
+ onScanRequested: options.onScanRequested,
218
315
  });
219
316
  break;
220
317
 
@@ -223,6 +320,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
223
320
  exclude: options.exclude || [],
224
321
  apiPrefix: options.apiPrefix || "/",
225
322
  debug: options.debug || false,
323
+ scanSecret: options.scanSecret,
324
+ onScanRequested: options.onScanRequested,
226
325
  });
227
326
  break;
228
327
 
@@ -231,6 +330,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
231
330
  exclude: options.exclude || [],
232
331
  apiPrefix: options.apiPrefix || "/api",
233
332
  debug: options.debug || false,
333
+ scanSecret: options.scanSecret,
334
+ onScanRequested: options.onScanRequested,
234
335
  });
235
336
  break;
236
337
 
@@ -239,6 +340,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
239
340
  exclude: options.exclude || [],
240
341
  apiPrefix: options.apiPrefix || "/",
241
342
  debug: options.debug || false,
343
+ scanSecret: options.scanSecret,
344
+ onScanRequested: options.onScanRequested,
242
345
  });
243
346
  break;
244
347
 
@@ -247,6 +350,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
247
350
  exclude: options.exclude || [],
248
351
  apiPrefix: options.apiPrefix || "/",
249
352
  debug: options.debug || false,
353
+ scanSecret: options.scanSecret,
354
+ onScanRequested: options.onScanRequested,
250
355
  });
251
356
  break;
252
357
 
@@ -256,6 +361,8 @@ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
256
361
  exclude: options.exclude || [],
257
362
  apiPrefix: options.apiPrefix || "/",
258
363
  debug: options.debug || false,
364
+ scanSecret: options.scanSecret,
365
+ onScanRequested: options.onScanRequested,
259
366
  });
260
367
  break;
261
368
  }
@@ -289,7 +396,22 @@ var BotVersion = {
289
396
  this._initialized = true;
290
397
  const savedOptions = global._botVersionOptions || {};
291
398
  const savedFramework = global._botVersionFramework || "nextjs";
292
- attachRuntimeInterceptor(savedFramework, app, this._client, savedOptions);
399
+ const savedFrontend = global._botVersionFrontendFramework || "unknown";
400
+ const savedProjectType = classifyInstallation(
401
+ savedFramework,
402
+ savedFrontend,
403
+ );
404
+ if (
405
+ savedProjectType !== "frontend-only" &&
406
+ savedProjectType !== "unknown"
407
+ ) {
408
+ attachRuntimeInterceptor(
409
+ savedFramework,
410
+ app,
411
+ this._client,
412
+ savedOptions,
413
+ );
414
+ }
293
415
  return;
294
416
  }
295
417
 
@@ -299,7 +421,7 @@ var BotVersion = {
299
421
 
300
422
  this._client = new BotVersionClient({
301
423
  apiKey: options.apiKey,
302
- platformUrl: options.platformUrl || "https://botversion.com",
424
+ platformUrl: options.platformUrl || "https://console.botversion.com",
303
425
  debug: options.debug || false,
304
426
  timeout: options.timeout || 30000,
305
427
  });
@@ -319,33 +441,36 @@ var BotVersion = {
319
441
  var detectedFrontend = options.frontendFramework || detected.frontend;
320
442
 
321
443
  global._botVersionFramework = detectedBackend;
444
+ global._botVersionFrontendFramework = detectedFrontend;
445
+
446
+ var projectType = classifyInstallation(detectedBackend, detectedFrontend);
322
447
 
323
448
  if (debug) {
324
- // Detected: detectedBackend, detectedFrontend
449
+ // Detected: detectedBackend, detectedFrontend, projectType
325
450
  }
326
451
 
327
- // ── Attach runtime interceptor ───────────────────────────────────────────
328
- attachRuntimeInterceptor(detectedBackend, app, self._client, options);
329
-
330
- // ── Static scan ──────────────────────────────────────────────────────────
331
- setTimeout(function () {
332
- // Run backend scanner
333
- var endpoints = runBackendScanner(detectedBackend, app, options, cwd);
334
-
335
- // Send endpoints to platform
336
- if (endpoints.length > 0) {
337
- self._client.registerEndpoints(endpoints);
338
- }
339
-
340
- // Run frontend route scanner
341
- var routePatterns = scanner.scanFrontendRoutes(cwd);
452
+ // ── Wire up the on-demand scan trigger ───────────────────────────────────
453
+ // Instead of scanning automatically on restart, the interceptor now
454
+ // listens for a special request from the BotVersion dashboard and runs
455
+ // the scan only when the user clicks "Scan" there.
456
+ options.scanSecret = options.apiKey;
457
+ options.onScanRequested = function () {
458
+ return runFullScan(
459
+ detectedBackend,
460
+ detectedFrontend,
461
+ app,
462
+ options,
463
+ cwd,
464
+ self._client,
465
+ );
466
+ };
342
467
 
343
- if (routePatterns.length > 0) {
344
- self._client.registerRoutePatterns(routePatterns).catch(function () {
345
- // silently ignore — non-critical
346
- });
347
- }
348
- }, 3000);
468
+ // ── Attach runtime interceptor ───────────────────────────────────────────
469
+ // Skip for frontend-only or unknown projects — there's no backend
470
+ // server on this process worth intercepting live traffic on.
471
+ if (projectType !== "frontend-only" && projectType !== "unknown") {
472
+ attachRuntimeInterceptor(detectedBackend, app, self._client, options);
473
+ }
349
474
  },
350
475
 
351
476
  getEndpoints: function () {
@@ -377,3 +502,6 @@ module.exports.default = BotVersion;
377
502
  module.exports.init = BotVersion.init;
378
503
  module.exports.getEndpoints = BotVersion.getEndpoints;
379
504
  module.exports.registerEndpoint = BotVersion.registerEndpoint;
505
+ module.exports.detectFrameworks = detectFrameworks;
506
+ module.exports.classifyInstallation = classifyInstallation;
507
+ module.exports.runBackendScanner = runBackendScanner;