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.
- package/bin/scan-backend.js +75 -0
- package/bin/scan-frontend.js +66 -0
- package/client.js +13 -7
- package/index.js +152 -24
- package/interceptor.js +190 -14
- package/package.json +4 -2
- package/scanner.js +7 -106
- package/bin/init.js +0 -605
- package/cli/detector.js +0 -1149
- package/cli/generator.js +0 -187
- package/cli/prompts.js +0 -163
- package/cli/writer.js +0 -511
|
@@ -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 (
|
|
102
|
+
BotVersionClient.prototype.registerRoutePatterns = function (
|
|
103
|
+
patterns,
|
|
104
|
+
classification,
|
|
105
|
+
) {
|
|
103
106
|
var self = this;
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
// ──
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
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;
|