botversion-sdk 1.0.5 → 1.1.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/index.js CHANGED
@@ -1,10 +1,266 @@
1
- // botversion-sdk/index.js
2
1
  "use strict";
3
2
 
4
3
  var scanner = require("./scanner");
5
4
  var interceptor = require("./interceptor");
6
5
  var BotVersionClient = require("./client");
7
6
 
7
+ // ── Framework auto-detection ─────────────────────────────────────────────────
8
+ // Reads package.json from the customer's codebase and figures out
9
+ // which backend and frontend frameworks they are using.
10
+ function detectFrameworks(cwd) {
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+
14
+ const result = {
15
+ backend: "unknown",
16
+ frontend: "unknown",
17
+ };
18
+
19
+ let pkg = {};
20
+ try {
21
+ const pkgPath = path.join(cwd, "package.json");
22
+ if (fs.existsSync(pkgPath)) {
23
+ pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
24
+ }
25
+ } catch {
26
+ return result;
27
+ }
28
+
29
+ const deps = Object.assign(
30
+ {},
31
+ pkg.dependencies || {},
32
+ pkg.devDependencies || {},
33
+ );
34
+
35
+ // ── Backend detection ──────────────────────────────────────────────────────
36
+ if (deps["@nestjs/core"]) {
37
+ result.backend = "nestjs";
38
+ } else if (deps["fastify"]) {
39
+ result.backend = "fastify";
40
+ } else if (deps["@adonisjs/core"]) {
41
+ result.backend = "adonis";
42
+ } else if (deps["@hapi/hapi"] || deps["hapi"]) {
43
+ result.backend = "hapi";
44
+ } else if (deps["koa"]) {
45
+ result.backend = "koa";
46
+ } else if (deps["next"]) {
47
+ result.backend = "nextjs";
48
+ } else if (deps["express"]) {
49
+ result.backend = "express";
50
+ } else if (deps["@remix-run/node"] || deps["@remix-run/serve"]) {
51
+ result.backend = "remix";
52
+ } else if (deps["nuxt"] || deps["nuxt3"] || deps["@nuxt/core"]) {
53
+ result.backend = "nuxt";
54
+ } else if (deps["@sveltejs/kit"]) {
55
+ result.backend = "sveltekit";
56
+ }
57
+
58
+ // ── Frontend detection ─────────────────────────────────────────────────────
59
+ if (deps["next"]) {
60
+ result.frontend = "nextjs";
61
+ } else if (deps["nuxt"] || deps["nuxt3"] || deps["@nuxt/core"]) {
62
+ result.frontend = "nuxt";
63
+ } else if (deps["@sveltejs/kit"]) {
64
+ result.frontend = "sveltekit";
65
+ } else if (deps["@remix-run/react"]) {
66
+ result.frontend = "remix";
67
+ } else if (deps["@tanstack/router"] || deps["@tanstack/react-router"]) {
68
+ result.frontend = "tanstack";
69
+ } else if (deps["vue"] && (deps["vue-router"] || deps["@nuxtjs/router"])) {
70
+ result.frontend = "vue";
71
+ } else if (deps["@angular/core"]) {
72
+ result.frontend = "angular";
73
+ } else if (deps["svelte"]) {
74
+ result.frontend = "svelte";
75
+ } else if (deps["react"] && deps["react-router-dom"]) {
76
+ result.frontend = "react-router";
77
+ } else if (deps["astro"]) {
78
+ result.frontend = "astro";
79
+ } else if (deps["react"]) {
80
+ result.frontend = "react";
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ // ── Run the right backend scanner ────────────────────────────────────────────
87
+ function runBackendScanner(detectedBackend, app, options, cwd) {
88
+ const endpoints = [];
89
+
90
+ switch (detectedBackend) {
91
+ case "express":
92
+ endpoints.push(...scanner.scanExpressRoutes(app, cwd));
93
+ break;
94
+
95
+ case "nextjs": {
96
+ const fs = require("fs");
97
+ const path = require("path");
98
+
99
+ const possibleAppDirs = [
100
+ path.join(cwd, "app"),
101
+ path.join(cwd, "src", "app"),
102
+ ];
103
+ const possiblePagesDirs = [
104
+ path.join(cwd, "pages"),
105
+ path.join(cwd, "src", "pages"),
106
+ ];
107
+
108
+ for (const dir of possibleAppDirs) {
109
+ if (fs.existsSync(dir)) {
110
+ endpoints.push(...scanner.scanNextJsAppRoutes(dir));
111
+ }
112
+ }
113
+ for (const dir of possiblePagesDirs) {
114
+ if (fs.existsSync(dir)) {
115
+ endpoints.push(...scanner.scanNextJsRoutes(dir));
116
+ }
117
+ }
118
+ break;
119
+ }
120
+
121
+ case "fastify":
122
+ endpoints.push(...scanner.scanFastifyRoutes(cwd));
123
+ break;
124
+
125
+ case "nestjs":
126
+ endpoints.push(...scanner.scanNestJsRoutes(cwd));
127
+ break;
128
+
129
+ case "koa":
130
+ endpoints.push(...scanner.scanKoaRoutes(cwd));
131
+ break;
132
+
133
+ case "hapi":
134
+ endpoints.push(...scanner.scanHapiRoutes(cwd));
135
+ break;
136
+
137
+ case "adonis":
138
+ endpoints.push(...scanner.scanAdonisRoutes(cwd));
139
+ break;
140
+
141
+ case "nuxt":
142
+ endpoints.push(...scanner.scanNuxtServerRoutes(cwd));
143
+ break;
144
+
145
+ case "sveltekit":
146
+ endpoints.push(...scanner.scanSvelteKitServerRoutes(cwd));
147
+ break;
148
+
149
+ case "remix":
150
+ endpoints.push(...scanner.scanRemixServerRoutes(cwd));
151
+ break;
152
+
153
+ default:
154
+ // Unknown backend — try Express as best guess
155
+ if (app) {
156
+ endpoints.push(...scanner.scanExpressRoutes(app, cwd));
157
+ }
158
+ break;
159
+ }
160
+
161
+ return endpoints;
162
+ }
163
+
164
+ // ── Attach the right runtime interceptor ─────────────────────────────────────
165
+ function attachRuntimeInterceptor(detectedBackend, app, client, options) {
166
+ switch (detectedBackend) {
167
+ case "express":
168
+ interceptor.attachInterceptor(app, client, {
169
+ exclude: options.exclude || [],
170
+ apiPrefix: options.apiPrefix || null,
171
+ debug: options.debug || false,
172
+ });
173
+ break;
174
+
175
+ case "nextjs":
176
+ interceptor.attachNextJsInterceptor(client, {
177
+ exclude: options.exclude || [],
178
+ apiPrefix: options.apiPrefix || "/api",
179
+ debug: options.debug || false,
180
+ });
181
+ break;
182
+
183
+ case "fastify":
184
+ if (app) {
185
+ interceptor.attachFastifyInterceptor(app, client, {
186
+ exclude: options.exclude || [],
187
+ apiPrefix: options.apiPrefix || null,
188
+ debug: options.debug || false,
189
+ });
190
+ }
191
+ break;
192
+
193
+ case "koa":
194
+ if (app) {
195
+ interceptor.attachKoaInterceptor(app, client, {
196
+ exclude: options.exclude || [],
197
+ apiPrefix: options.apiPrefix || null,
198
+ debug: options.debug || false,
199
+ });
200
+ }
201
+ break;
202
+
203
+ case "hapi":
204
+ if (app) {
205
+ interceptor.attachHapiInterceptor(app, client, {
206
+ exclude: options.exclude || [],
207
+ apiPrefix: options.apiPrefix || null,
208
+ debug: options.debug || false,
209
+ });
210
+ }
211
+ break;
212
+
213
+ case "nestjs":
214
+ interceptor.attachNestJsInterceptor(client, {
215
+ exclude: options.exclude || [],
216
+ apiPrefix: options.apiPrefix || "/",
217
+ debug: options.debug || false,
218
+ });
219
+ break;
220
+
221
+ case "sveltekit":
222
+ interceptor.attachSvelteKitInterceptor(client, {
223
+ exclude: options.exclude || [],
224
+ apiPrefix: options.apiPrefix || "/",
225
+ debug: options.debug || false,
226
+ });
227
+ break;
228
+
229
+ case "nuxt":
230
+ interceptor.attachNuxtInterceptor(client, {
231
+ exclude: options.exclude || [],
232
+ apiPrefix: options.apiPrefix || "/api",
233
+ debug: options.debug || false,
234
+ });
235
+ break;
236
+
237
+ case "remix":
238
+ interceptor.attachRemixInterceptor(client, {
239
+ exclude: options.exclude || [],
240
+ apiPrefix: options.apiPrefix || "/",
241
+ debug: options.debug || false,
242
+ });
243
+ break;
244
+
245
+ case "adonis":
246
+ interceptor.attachAdonisInterceptor(client, {
247
+ exclude: options.exclude || [],
248
+ apiPrefix: options.apiPrefix || "/",
249
+ debug: options.debug || false,
250
+ });
251
+ break;
252
+
253
+ default:
254
+ // Unknown — fall back to Next.js http server patch as best guess
255
+ interceptor.attachNextJsInterceptor(client, {
256
+ exclude: options.exclude || [],
257
+ apiPrefix: options.apiPrefix || "/",
258
+ debug: options.debug || false,
259
+ });
260
+ break;
261
+ }
262
+ }
263
+
8
264
  var BotVersion = {
9
265
  _client: null,
10
266
  _initialized: false,
@@ -26,22 +282,14 @@ var BotVersion = {
26
282
  app = null;
27
283
  }
28
284
 
29
- if (!options.apiKey) {
30
- console.error("[BotVersion SDK] ❌ apiKey is required.");
31
- return;
32
- }
33
-
34
285
  // Restore from global if module was re-imported after hot reload
35
286
  if (global._botVersionClient) {
36
287
  this._client = global._botVersionClient;
37
288
  this._options = global._botVersionOptions;
38
289
  this._initialized = true;
39
- console.warn("[BotVersion SDK] Restored from global — skipping re-init");
40
- return;
41
- }
42
-
43
- if (this._initialized) {
44
- console.warn("[BotVersion SDK] Already initialized — skipping");
290
+ const savedOptions = global._botVersionOptions || {};
291
+ const savedFramework = global._botVersionFramework || "nextjs";
292
+ attachRuntimeInterceptor(savedFramework, app, this._client, savedOptions);
45
293
  return;
46
294
  }
47
295
 
@@ -51,7 +299,7 @@ var BotVersion = {
51
299
 
52
300
  this._client = new BotVersionClient({
53
301
  apiKey: options.apiKey,
54
- platformUrl: options.platformUrl || "http://localhost:3000",
302
+ platformUrl: options.platformUrl || "https://botversion.com",
55
303
  debug: options.debug || false,
56
304
  timeout: options.timeout || 30000,
57
305
  });
@@ -60,185 +308,44 @@ var BotVersion = {
60
308
  global._botVersionOptions = options;
61
309
 
62
310
  var self = this;
311
+ var cwd = options.cwd || process.cwd();
63
312
  var debug = options.debug || false;
64
313
 
65
- if (debug) {
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
- }
314
+ // ── Auto-detect frameworks ───────────────────────────────────────────────
315
+ var detected = detectFrameworks(cwd);
73
316
 
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
- }
317
+ // Allow manual override from options
318
+ var detectedBackend = options.framework || detected.backend;
319
+ var detectedFrontend = options.frontendFramework || detected.frontend;
94
320
 
95
- // ── Runtime interceptor — Express only ───────────────────────────────────
96
- if (app && app.use) {
97
- interceptor.attachInterceptor(app, self._client, {
98
- exclude: options.exclude || [],
99
- apiPrefix: options.apiPrefix || null,
100
- debug: debug,
101
- });
102
- if (debug) {
103
- console.log("[BotVersion SDK] ✅ Runtime interceptor attached");
104
- }
105
- } else if (!app) {
106
- if (debug) {
107
- console.log(
108
- "[BotVersion SDK] ℹ Runtime interceptor skipped — Next.js mode uses file scanning only",
109
- );
110
- }
321
+ global._botVersionFramework = detectedBackend;
322
+
323
+ if (debug) {
324
+ // Detected: detectedBackend, detectedFrontend
111
325
  }
112
326
 
327
+ // ── Attach runtime interceptor ───────────────────────────────────────────
328
+ attachRuntimeInterceptor(detectedBackend, app, self._client, options);
329
+
113
330
  // ── Static scan ──────────────────────────────────────────────────────────
114
331
  setTimeout(function () {
115
- var endpoints = [];
116
-
117
- // Express scan
118
- // Express scan
119
- if (app) {
120
- console.log("[BotVersion SDK] Scanning Express routes...");
121
- var cwd = options.cwd || process.cwd();
122
- 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
- }
136
-
137
- // Next.js scan
138
- // Next.js scan — auto detect all possible structures
139
- if (!app) {
140
- const fs = require("fs");
141
- const path = require("path");
142
- const cwd = process.cwd();
143
-
144
- const possibleAppDirs = [
145
- path.join(cwd, "app"),
146
- path.join(cwd, "src", "app"),
147
- ];
148
-
149
- const possiblePagesDirs = [
150
- path.join(cwd, "pages"),
151
- path.join(cwd, "src", "pages"),
152
- ];
153
-
154
- for (const dir of possibleAppDirs) {
155
- if (fs.existsSync(dir)) {
156
- console.log("[BotVersion SDK] Scanning App Router routes at:", dir);
157
- const routes = scanner.scanNextJsAppRoutes(dir);
158
- endpoints = endpoints.concat(routes);
159
- console.log(
160
- "[BotVersion SDK] Found",
161
- routes.length,
162
- "App Router routes",
163
- );
164
- }
165
- }
166
-
167
- for (const dir of possiblePagesDirs) {
168
- if (fs.existsSync(dir)) {
169
- console.log(
170
- "[BotVersion SDK] Scanning Pages Router routes at:",
171
- dir,
172
- );
173
- const routes = scanner.scanNextJsRoutes(dir);
174
- endpoints = endpoints.concat(routes);
175
- console.log(
176
- "[BotVersion SDK] Found",
177
- routes.length,
178
- "Pages Router routes",
179
- );
180
- }
181
- }
182
- }
332
+ // Run backend scanner
333
+ var endpoints = runBackendScanner(detectedBackend, app, options, cwd);
183
334
 
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
- // Send to platform
335
+ // Send endpoints to platform
194
336
  if (endpoints.length > 0) {
195
- console.log(
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
- });
337
+ self._client.registerEndpoints(endpoints);
215
338
  }
216
339
 
217
- var routePatterns = scanner.scanFrontendRoutes(
218
- options.cwd || process.cwd(),
219
- );
340
+ // Run frontend route scanner
341
+ var routePatterns = scanner.scanFrontendRoutes(cwd);
342
+
220
343
  if (routePatterns.length > 0) {
221
- console.log(
222
- "[BotVersion SDK] Found",
223
- routePatterns.length,
224
- "frontend route patterns",
225
- );
226
- self._client
227
- .registerRoutePatterns(routePatterns)
228
- .then(function () {
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
- });
344
+ self._client.registerRoutePatterns(routePatterns).catch(function () {
345
+ // silently ignore — non-critical
346
+ });
237
347
  }
238
-
239
- self._client.connect();
240
- console.log("[BotVersion SDK] ✅ Initialization complete");
241
- }, 500);
348
+ }, 3000);
242
349
  },
243
350
 
244
351
  getEndpoints: function () {
@@ -270,36 +377,3 @@ module.exports.default = BotVersion;
270
377
  module.exports.init = BotVersion.init;
271
378
  module.exports.getEndpoints = BotVersion.getEndpoints;
272
379
  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
- }