botversion-sdk 1.0.4 → 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 CHANGED
@@ -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://app.botversion.com/settings\n`);
105
+ log(` Get your key from: https://botversion.com/settings\n`);
106
106
  process.exit(1);
107
107
  }
108
108
 
@@ -113,10 +113,7 @@ async function main() {
113
113
  step("Fetching project info from platform...");
114
114
  let projectInfo;
115
115
  try {
116
- projectInfo = await fetchProjectInfo(
117
- args.key,
118
- "https://chatbusiness-two.vercel.app",
119
- );
116
+ projectInfo = await fetchProjectInfo(args.key, "https://botversion.com");
120
117
  success(`Project found — ID: ${projectInfo.projectId}`);
121
118
  } catch (err) {
122
119
  error(err.message);
@@ -126,7 +123,6 @@ async function main() {
126
123
  // ── Detect environment ────────────────────────────────────────────────────
127
124
  step("Scanning your project...");
128
125
 
129
- // Handle monorepo
130
126
  const monorepoInfo = detector.detectMonorepo(cwd);
131
127
  if (monorepoInfo.isMonorepo) {
132
128
  warn(
@@ -134,7 +130,6 @@ async function main() {
134
130
  );
135
131
  }
136
132
 
137
- // Always detect from root so frontend/backend split works correctly
138
133
  const detected = detector.detect(cwd);
139
134
 
140
135
  // ── Check if already initialized ─────────────────────────────────────────
@@ -171,39 +166,88 @@ async function main() {
171
166
  `Module system: ${detected.moduleSystem === "esm" ? "ES Modules" : "CommonJS"}`,
172
167
  );
173
168
  info(`Language: ${detected.isTypeScript ? "TypeScript" : "JavaScript"}`);
169
+ info(`Package manager: ${detected.packageManager}`);
174
170
 
175
- // ── Auth detection ────────────────────────────────────────────────────────
176
- step("Detecting auth library...");
177
-
178
- let auth = detected.auth;
179
-
180
- if (!auth.name) {
181
- warn("No auth library detected automatically.");
182
- auth = await prompts.promptAuthLibrary();
183
- detected.auth = auth;
184
- } else if (!auth.supported) {
185
- warn(`Detected auth: ${auth.name} (not yet supported for auto-setup)`);
186
- warn("Will set up without user context — you can add it manually later.");
187
- const proceed = await prompts.confirm("Continue without auth?", true);
188
- if (!proceed) process.exit(0);
189
- auth = { name: auth.name, supported: false };
190
- detected.auth = auth;
191
- } else {
192
- const versionLabel = auth.version ? ` (${auth.version})` : "";
193
- success(`Auth: ${auth.name}${versionLabel}`);
194
- }
171
+ // ─────────────────────────────────────────────────────────────────────────
172
+ // FRAMEWORK: NEXT.JS
173
+ // ─────────────────────────────────────────────────────────────────────────
174
+ if (detected.framework.name === "next") {
175
+ await setupNextJs(detected, args, changes, projectInfo);
195
176
 
196
- // ── Package manager ───────────────────────────────────────────────────────
197
- info(`Package manager: ${detected.packageManager}`);
177
+ // ── Also check for a separate Express backend folder ──────────────────
178
+ step("Checking for separate backend...");
179
+ const backendDirs = ["backend", "server", "api", "services"];
180
+ let expressBackendFound = false;
181
+
182
+ for (const dir of backendDirs) {
183
+ const backendPath = path.join(cwd, dir);
184
+ if (!fs.existsSync(backendPath)) continue;
185
+
186
+ // Check if this folder has its own package.json with express
187
+ const backendPkg = detector.readPackageJson(backendPath);
188
+ const backendDeps = {
189
+ ...((backendPkg && backendPkg.dependencies) || {}),
190
+ ...((backendPkg && backendPkg.devDependencies) || {}),
191
+ };
192
+ const hasExpress = !!backendDeps["express"];
193
+
194
+ // Or check if any JS file in this folder has express routes
195
+ const hasExpressFiles = fs.readdirSync(backendPath).some((file) => {
196
+ if (!/\.(js|ts)$/.test(file)) return false;
197
+ try {
198
+ const content = fs.readFileSync(path.join(backendPath, file), "utf8");
199
+ return (
200
+ content.includes("express()") ||
201
+ content.includes("express(") || // factory pattern
202
+ /class\s+\w+\s+extends\s+express/.test(content) || // subclass
203
+ /(?:create|make|build|setup)App\s*\(/.test(content) || // factory fn
204
+ /(?:app|router|server)\.(get|post|put|delete|patch|all)\s*\(\s*['"`]\//.test(
205
+ content,
206
+ )
207
+ );
208
+ } catch {
209
+ return false;
210
+ }
211
+ });
212
+
213
+ if (hasExpress || hasExpressFiles) {
214
+ expressBackendFound = true;
215
+ warn(`Found Express backend in "${dir}/" folder.`);
216
+
217
+ // Detect the backend separately
218
+ const backendDetected = detector.detect(backendPath);
219
+
220
+ if (backendDetected.entryPoint) {
221
+ success(
222
+ `Backend entry point: ${path.relative(cwd, backendDetected.entryPoint)}`,
223
+ );
224
+ await setupExpress(backendDetected, args, changes, projectInfo);
225
+ } else {
226
+ warn(`Could not find entry point in "${dir}/" automatically.`);
227
+ const manualPath = await prompts.promptEntryPoint();
228
+ const resolvedPath = path.resolve(backendPath, manualPath);
229
+ if (fs.existsSync(resolvedPath)) {
230
+ backendDetected.entryPoint = resolvedPath;
231
+ await setupExpress(backendDetected, args, changes, projectInfo);
232
+ } else {
233
+ error(`File not found: ${resolvedPath}`);
234
+ }
235
+ }
236
+ break; // only set up one backend
237
+ }
238
+ }
239
+
240
+ if (!expressBackendFound) {
241
+ info("No separate Express backend found — skipping.");
242
+ }
243
+ }
198
244
 
199
245
  // ─────────────────────────────────────────────────────────────────────────
200
- // FRAMEWORK: EXPRESS
246
+ // FRAMEWORK: EXPRESS ONLY
201
247
  // ─────────────────────────────────────────────────────────────────────────
202
- if (detected.framework.name === "express") {
248
+ else if (detected.framework.name === "express") {
203
249
  await setupExpress(detected, args, changes, projectInfo);
204
250
 
205
- // ── Inject script tag into frontend (Express only) ──────────────────
206
- // Next.js handles its own script tag injection inside setupNextJs
207
251
  if (detected.frontendMainFile && projectInfo) {
208
252
  const scriptTag = generator.generateScriptTag(projectInfo);
209
253
  const result = writer.injectScriptTag(
@@ -243,45 +287,6 @@ async function main() {
243
287
  }
244
288
  }
245
289
 
246
- // ─────────────────────────────────────────────────────────────────────────
247
- // FRAMEWORK: NEXT.JS
248
- // ─────────────────────────────────────────────────────────────────────────
249
- else if (detected.framework.name === "next") {
250
- await setupNextJs(detected, args, changes, projectInfo);
251
- }
252
-
253
- // ── Write API key to .env / .env.local ────────────────────────────────────
254
- const envFileName =
255
- detected.framework.name === "next" ? ".env.local" : ".env";
256
- const envPath = path.join(detected.cwd, envFileName);
257
- const envLine = `BOTVERSION_API_KEY=${args.key}`;
258
- const envContent = fs.existsSync(envPath)
259
- ? fs.readFileSync(envPath, "utf8")
260
- : "";
261
-
262
- if (!envContent.includes("BOTVERSION_API_KEY")) {
263
- const writeEnv = await prompts.confirm(
264
- `Add BOTVERSION_API_KEY to ${envFileName}?`,
265
- true,
266
- );
267
-
268
- if (writeEnv) {
269
- const envAddition = "\n\n# BotVersion API key\n" + envLine + "\n";
270
- fs.writeFileSync(envPath, envContent.trimEnd() + envAddition, "utf8");
271
- success(`Added BOTVERSION_API_KEY to ${envFileName}`);
272
- changes.modified.push(envFileName);
273
- } else {
274
- warn(`Skipped — add this manually to your ${envFileName}:`);
275
- log(`\n # BotVersion API key`);
276
- log(` BOTVERSION_API_KEY=${args.key}\n`);
277
- changes.manual.push(
278
- `Add to your ${envFileName}:\n\n # BotVersion API key\n BOTVERSION_API_KEY=${args.key}`,
279
- );
280
- }
281
- } else {
282
- info(`BOTVERSION_API_KEY already exists in ${envFileName} — skipping.`);
283
- }
284
-
285
290
  // ── Print summary ─────────────────────────────────────────────────────────
286
291
  log(writer.writeSummary(changes));
287
292
  }
@@ -291,7 +296,6 @@ async function main() {
291
296
  async function setupExpress(detected, args, changes, projectInfo) {
292
297
  step("Setting up Express...");
293
298
 
294
- // Find entry point
295
299
  let entryPoint = detected.entryPoint;
296
300
 
297
301
  if (!entryPoint || !fs.existsSync(entryPoint)) {
@@ -307,7 +311,80 @@ async function setupExpress(detected, args, changes, projectInfo) {
307
311
 
308
312
  success(`Entry point: ${path.relative(detected.cwd, entryPoint)}`);
309
313
 
310
- // Generate the init code
314
+ // ── Write API key to .env ──────────────────────────────────────────────
315
+ const shouldWriteEnv = await prompts.confirm(
316
+ " Add BOTVERSION_API_KEY to your .env file?",
317
+ true,
318
+ );
319
+
320
+ if (shouldWriteEnv) {
321
+ const envResult = writer.writeEnvKey(
322
+ detected.cwd,
323
+ "BOTVERSION_API_KEY",
324
+ args.key,
325
+ "express",
326
+ );
327
+ if (envResult.success) {
328
+ success(
329
+ `Added BOTVERSION_API_KEY to ${path.relative(detected.cwd, envResult.path)}`,
330
+ );
331
+ changes.modified.push(path.relative(detected.cwd, envResult.path));
332
+ } else if (envResult.reason === "already_exists") {
333
+ info("BOTVERSION_API_KEY already exists in .env — skipping.");
334
+ } else {
335
+ warn("Could not write to .env — add this manually:");
336
+ changes.manual.push(
337
+ `Add to your .env file:\n\n BOTVERSION_API_KEY=${args.key}`,
338
+ );
339
+ }
340
+ } else {
341
+ info("Skipped — add this manually to your .env file:");
342
+ changes.manual.push(
343
+ `Add to your .env file:\n\n BOTVERSION_API_KEY=${args.key}`,
344
+ );
345
+ }
346
+
347
+ if (!detected.hasDotenv) {
348
+ warn("dotenv not detected — installing it automatically...");
349
+
350
+ const { execSync } = require("child_process");
351
+ const installCmd =
352
+ detected.packageManager === "yarn"
353
+ ? "yarn add dotenv"
354
+ : detected.packageManager === "pnpm"
355
+ ? "pnpm add dotenv"
356
+ : detected.packageManager === "bun"
357
+ ? "bun add dotenv"
358
+ : "npm install dotenv";
359
+
360
+ try {
361
+ execSync(installCmd, { cwd: detected.cwd, stdio: "inherit" });
362
+ success("dotenv installed successfully");
363
+
364
+ const isESM = detected.moduleSystem === "esm";
365
+ const dotenvLine = isESM
366
+ ? `import 'dotenv/config';`
367
+ : `require('dotenv').config();`;
368
+
369
+ const injectResult = writer.injectAtTop(entryPoint, dotenvLine);
370
+ if (injectResult.success) {
371
+ success("Added dotenv config to entry file");
372
+ changes.modified.push(path.relative(detected.cwd, entryPoint));
373
+ if (injectResult.backup) changes.backups.push(injectResult.backup);
374
+ }
375
+
376
+ detected.hasDotenv = true;
377
+ } catch (err) {
378
+ warn("Could not install dotenv automatically.");
379
+ warn(`Please run manually: ${installCmd}`);
380
+ warn("And add to top of your entry file: require('dotenv').config();");
381
+ changes.manual.push(
382
+ `Install dotenv: ${installCmd}\n` +
383
+ `Add to top of ${path.relative(detected.cwd, entryPoint)}: require('dotenv').config();`,
384
+ );
385
+ }
386
+ }
387
+
311
388
  const generated = generator.generateExpressInit(detected, args.key);
312
389
 
313
390
  // PATTERN 2: Separate app file with module.exports = app
@@ -330,7 +407,7 @@ async function setupExpress(detected, args, changes, projectInfo) {
330
407
  }
331
408
  }
332
409
 
333
- // PATTERN 1 & 3 & 4: app.listen() or server.listen() in entry file
410
+ // PATTERN 1 & 3 & 4: app.listen() in entry file
334
411
  else if (
335
412
  detected.listenCall ||
336
413
  detected.listenInsideCallback ||
@@ -399,20 +476,43 @@ async function setupNextJs(detected, args, changes, projectInfo) {
399
476
  const nextInfo = detected.next;
400
477
  const baseDir = nextInfo.baseDir;
401
478
 
402
- info2(
403
- `Router: ${nextInfo.pagesRouter ? "Pages" : ""}${nextInfo.pagesRouter && nextInfo.appRouter ? " + " : ""}${nextInfo.appRouter ? "App" : ""}`,
479
+ // ── Write API key to .env ──────────────────────────────────────────────
480
+ const shouldWriteEnv = await prompts.confirm(
481
+ " Add BOTVERSION_API_KEY to your .env file automatically?",
482
+ true,
404
483
  );
405
484
 
406
- // ── next-auth config location ─────────────────────────────────────────────
407
- if (detected.auth.name === "next-auth" && !detected.nextAuthConfig) {
408
- warn("Could not find authOptions location automatically.");
409
- const configPath = await prompts.promptNextAuthConfigPath();
410
- detected.nextAuthConfig = {
411
- path: path.resolve(detected.cwd, configPath),
412
- relativePath: configPath,
413
- };
485
+ if (shouldWriteEnv) {
486
+ const envResult = writer.writeEnvKey(
487
+ detected.cwd,
488
+ "BOTVERSION_API_KEY",
489
+ args.key,
490
+ "next",
491
+ );
492
+ if (envResult.success) {
493
+ success(
494
+ `Added BOTVERSION_API_KEY to ${path.relative(detected.cwd, envResult.path)}`,
495
+ );
496
+ changes.modified.push(path.relative(detected.cwd, envResult.path));
497
+ } else if (envResult.reason === "already_exists") {
498
+ info("BOTVERSION_API_KEY already exists in .env — skipping.");
499
+ } else {
500
+ warn("Could not write to .env — add this manually:");
501
+ changes.manual.push(
502
+ `Add to your .env file:\n\n BOTVERSION_API_KEY=${args.key}`,
503
+ );
504
+ }
505
+ } else {
506
+ info("Skipped — add this manually to your .env file:");
507
+ changes.manual.push(
508
+ `Add to your .env file:\n\n BOTVERSION_API_KEY=${args.key}`,
509
+ );
414
510
  }
415
511
 
512
+ info2(
513
+ `Router: ${nextInfo.pagesRouter ? "Pages" : ""}${nextInfo.pagesRouter && nextInfo.appRouter ? " + " : ""}${nextInfo.appRouter ? "App" : ""}`,
514
+ );
515
+
416
516
  // ── 1. Create instrumentation.js ──────────────────────────────────────────
417
517
  const instrExt = detected.generateTs ? ".ts" : ".js";
418
518
  const instrFile = path.join(detected.cwd, `instrumentation${instrExt}`);
@@ -458,61 +558,7 @@ async function setupNextJs(detected, args, changes, projectInfo) {
458
558
  );
459
559
  }
460
560
 
461
- // ── 3. Pages Router chat route ────────────────────────────────────────────
462
- if (nextInfo.pagesRouter) {
463
- const pagesBase = path.join(baseDir, "pages");
464
- const chatDir = path.join(pagesBase, "api", "botversion");
465
- const chatFile = path.join(chatDir, `chat${instrExt}`);
466
-
467
- const chatContent = generator.generateNextPagesChatRoute(detected);
468
- const chatResult = writer.createFile(chatFile, chatContent, args.force);
469
-
470
- const relPath = `${nextInfo.srcDir ? "src/" : ""}pages/api/botversion/chat${instrExt}`;
471
-
472
- if (chatResult.success) {
473
- success(`Created ${relPath}`);
474
- changes.created.push(relPath);
475
- } else if (chatResult.reason === "already_exists") {
476
- const overwrite = await prompts.promptForce(relPath);
477
- if (overwrite) {
478
- writer.createFile(chatFile, chatContent, true);
479
- success(`Overwrote ${relPath}`);
480
- changes.modified.push(relPath);
481
- } else {
482
- warn(`Skipped ${relPath}`);
483
- }
484
- }
485
- }
486
-
487
- // ── 4. App Router chat route ──────────────────────────────────────────────
488
- if (nextInfo.appRouter) {
489
- const appBase = path.join(baseDir, "app");
490
- const chatDir = path.join(appBase, "api", "botversion", "chat");
491
- const chatFile = path.join(chatDir, `route${instrExt}`);
492
-
493
- const chatContent = generator.generateNextAppChatRoute(detected);
494
- const relPath = `${nextInfo.srcDir ? "src/" : ""}app/api/botversion/chat/route${instrExt}`;
495
-
496
- const chatResult = writer.createFile(chatFile, chatContent, args.force);
497
-
498
- if (chatResult.success) {
499
- success(`Created ${relPath}`);
500
- changes.created.push(relPath);
501
- } else if (chatResult.reason === "already_exists") {
502
- const overwrite = await prompts.promptForce(relPath);
503
- if (overwrite) {
504
- writer.createFile(chatFile, chatContent, true);
505
- success(`Overwrote ${relPath}`);
506
- changes.modified.push(relPath);
507
- } else {
508
- warn(`Skipped ${relPath}`);
509
- }
510
- }
511
- }
512
-
513
- // ── 5. Inject script tag into frontend ───────────────────────────────────
514
- // For Next.js the frontend IS the same project
515
- // frontendMainFile will be _app.js or layout.js
561
+ // ── 3. Inject script tag into frontend ───────────────────────────────────
516
562
  if (detected.frontendMainFile && projectInfo) {
517
563
  const scriptTag = generator.generateScriptTag(projectInfo);
518
564
  const result = writer.injectScriptTag(