@botonic/nx-plugin 2.29.0 → 2.31.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/executors.json +0 -5
  3. package/package.json +3 -2
  4. package/src/executors/delete-bot/executor.js +0 -2
  5. package/src/executors/deploy-to-hubtype/executor.js +24 -160
  6. package/src/executors/e2e-webchat/botonic-package-publish.spec.ts +7 -11
  7. package/src/executors/integrate-provider/executor.js +0 -2
  8. package/src/executors/login-to-hubtype/executor.js +0 -2
  9. package/src/executors/logout-from-hubtype/executor.js +2 -2
  10. package/src/executors/serve-bot/executor.js +142 -24
  11. package/src/executors/serve-bot/schema.json +13 -5
  12. package/src/generators/bot-app/files/src/client/webchat/index.tsx.template +2 -0
  13. package/src/generators/bot-app/files/src/client/webchat/webchat.tsx.template +111 -0
  14. package/src/generators/bot-app/files/src/server/bot/plugins/flow-builder/index.ts.template +13 -4
  15. package/src/generators/bot-app/files/src/server/bot/plugins/index.ts.template +0 -3
  16. package/src/generators/bot-app/files/src/server/lambda/handler.js.template +1 -1
  17. package/src/generators/bot-app/files/src/server/lambda/package.json +3 -3
  18. package/src/generators/bot-app/files/vite/node.config.ts.template +2 -4
  19. package/src/generators/bot-app/files/vite/webchat.config.ts.template +20 -1
  20. package/src/generators/bot-app/generator.js +6 -5
  21. package/src/generators/bot-app/lilara-version.json +1 -1
  22. package/src/lib/api-service.d.ts +19 -20
  23. package/src/lib/api-service.js +150 -82
  24. package/src/lib/bot-config.d.ts +10 -7
  25. package/src/lib/bot-config.js +5 -1
  26. package/src/lib/constants.d.ts +2 -3
  27. package/src/lib/constants.js +6 -9
  28. package/src/lib/credentials-handler.d.ts +9 -18
  29. package/src/lib/credentials-handler.js +42 -24
  30. package/src/lib/interfaces.d.ts +10 -13
  31. package/src/lib/util/executor-helpers.d.ts +58 -18
  32. package/src/lib/util/executor-helpers.js +501 -102
  33. package/src/plugin.js +6 -15
  34. package/src/executors/deploy-local-runtime/executor.d.ts +0 -5
  35. package/src/executors/deploy-local-runtime/executor.js +0 -144
  36. package/src/executors/deploy-local-runtime/schema.d.js +0 -16
  37. package/src/executors/deploy-local-runtime/schema.json +0 -34
  38. package/src/generators/bot-app/files/src/server/bot/tracking.ts.template +0 -35
  39. package/src/generators/preset/files/package.json +0 -26
@@ -22,34 +22,39 @@ __export(executor_helpers_exports, {
22
22
  askLogin: () => askLogin,
23
23
  askSignup: () => askSignup,
24
24
  createNewBotWithName: () => createNewBotWithName,
25
+ deployBotToHubtype: () => deployBotToHubtype,
26
+ ensureBot: () => ensureBot,
25
27
  ensureHubtypeLoginBeforeTunnel: () => ensureHubtypeLoginBeforeTunnel,
26
- ensureLocalRuntimeBot: () => ensureLocalRuntimeBot,
27
- ensureLocalRuntimeBotBeforeTunnel: () => ensureLocalRuntimeBotBeforeTunnel,
28
28
  findMatchingConfigurationKey: () => findMatchingConfigurationKey,
29
+ getActiveTestWebchatProviderAppId: () => getActiveTestWebchatProviderAppId,
30
+ getActiveWebchatProviderAppId: () => getActiveWebchatProviderAppId,
29
31
  getAppIdFromEnvFile: () => getAppIdFromEnvFile,
30
32
  getAppIdFromEnvFileForConfig: () => getAppIdFromEnvFileForConfig,
31
33
  getAvailableBots: () => getAvailableBots,
32
34
  handleAuthentication: () => handleAuthentication,
33
35
  handleExecutorError: () => handleExecutorError,
36
+ isBotDeployedRestartRequired: () => isBotDeployedRestartRequired,
34
37
  logWorkingAsAndEnvironment: () => logWorkingAsAndEnvironment,
35
38
  performDeployLocalRuntimeWithEndpoint: () => performDeployLocalRuntimeWithEndpoint,
36
- removeAppBotonicJson: () => removeAppBotonicJson,
37
39
  resolveHubtypeEnvironment: () => resolveHubtypeEnvironment,
38
40
  resolveProjectPath: () => resolveProjectPath,
41
+ selectBotForServe: () => selectBotForServe,
42
+ selectedBotHasActiveWhatsapp: () => selectedBotHasActiveWhatsapp,
39
43
  writeAppIdToEnvFile: () => writeAppIdToEnvFile,
40
44
  writeAppIdToLocalEnv: () => writeAppIdToLocalEnv,
41
- writeEnvVarToEnvFile: () => writeEnvVarToEnvFile,
42
- writeLocalRuntimeBotToAppFolder: () => writeLocalRuntimeBotToAppFolder
45
+ writeEnvVarToEnvFile: () => writeEnvVarToEnvFile
43
46
  });
44
47
  module.exports = __toCommonJS(executor_helpers_exports);
45
48
  var import_enquirer = require("enquirer");
46
49
  var import_fs = require("fs");
50
+ var import_os = require("os");
47
51
  var import_path = require("path");
52
+ var import_zip_a_folder = require("zip-a-folder");
48
53
  var import_api_service = require("../api-service");
49
54
  var import_bot_config = require("../bot-config");
50
- var import_constants = require("../constants");
51
55
  var import_file_system = require("./file-system");
52
56
  var import_sam_template = require("./sam-template");
57
+ var import_system = require("./system");
53
58
  function resolveProjectPath(context) {
54
59
  if (!context.projectName) {
55
60
  throw new Error("Project name is not defined in executor context");
@@ -139,14 +144,14 @@ async function getAvailableBots(botonicApiService) {
139
144
  return resp.data.results;
140
145
  }
141
146
  const MAX_BOT_NAME_LENGTH = 25;
142
- async function createNewBotWithName(botonicApiService, botName) {
147
+ async function createNewBotWithName(botonicApiService, botName, isTest = false) {
143
148
  if (botName.length > MAX_BOT_NAME_LENGTH) {
144
149
  throw new Error(
145
150
  `Bot name must be at most ${MAX_BOT_NAME_LENGTH} characters. Please choose a shorter name.`
146
151
  );
147
152
  }
148
153
  console.log(`\u{1F916} Creating bot "${botName}"...`);
149
- await botonicApiService.createBot(botName);
154
+ await botonicApiService.createBot(botName, isTest);
150
155
  console.log(`\u2705 Bot "${botName}" created successfully!`);
151
156
  }
152
157
  function getAppIdFromEnvFile(projectRoot, appIdKey) {
@@ -366,9 +371,7 @@ async function ensureHubtypeLoginBeforeTunnel(context, projectRoot, options = {}
366
371
  options
367
372
  );
368
373
  const botonicApiService = new import_api_service.BotonicAPIService({
369
- isLocalRuntimeDeployment: true,
370
374
  projectRoot,
371
- workspaceRoot: (0, import_path.resolve)(context.root),
372
375
  environmentVariables,
373
376
  targetEnvironment
374
377
  });
@@ -382,63 +385,15 @@ async function ensureHubtypeLoginBeforeTunnel(context, projectRoot, options = {}
382
385
  }
383
386
  await logWorkingAsAndEnvironment(botonicApiService);
384
387
  }
385
- async function ensureLocalRuntimeBotBeforeTunnel(context, projectRoot, options = {}) {
386
- const { targetEnvironment, environmentVariables } = resolveHubtypeEnvironment(
387
- context,
388
- options
389
- );
390
- const botonicApiService = new import_api_service.BotonicAPIService({
391
- isLocalRuntimeDeployment: true,
392
- projectRoot,
393
- workspaceRoot: (0, import_path.resolve)(context.root),
394
- environmentVariables,
395
- targetEnvironment
396
- });
397
- if (!botonicApiService.oauth) {
398
- if (options.email && options.password) {
399
- await botonicApiService.login(options.email, options.password);
400
- botonicApiService.saveAllCredentials();
401
- } else {
402
- await handleAuthentication(botonicApiService);
403
- }
404
- }
405
- await ensureLocalRuntimeBot(botonicApiService);
406
- writeLocalRuntimeBotToAppFolder(projectRoot, botonicApiService.botInfo());
407
- botonicApiService.saveAllCredentials();
408
- }
409
- function writeLocalRuntimeBotToAppFolder(projectRoot, bot) {
410
- const path = (0, import_path.join)(projectRoot, import_constants.BOT_CREDENTIALS_FILENAME);
411
- (0, import_file_system.writeJSON)(path, { bot });
412
- }
413
- function removeAppBotonicJson(projectRoot) {
414
- const path = (0, import_path.join)(projectRoot, import_constants.BOT_CREDENTIALS_FILENAME);
415
- if ((0, import_fs.existsSync)(path)) {
416
- (0, import_fs.unlinkSync)(path);
417
- }
418
- }
419
- async function promptConfirmStagingBot(botName) {
420
- console.log(
421
- `
422
- \u26A0\uFE0F Bot "${botName}" will be created as a staging bot on the Hubtype platform.`
423
- );
424
- console.log(
425
- ` To create a production bot, use the Hubtype dashboard or the deploy-to-hubtype command instead.
426
- `
427
- );
428
- const confirmation = await (0, import_enquirer.prompt)({
429
- type: "confirm",
430
- name: "confirm_staging",
431
- message: "\u{1F6A6} Do you want to proceed?"
432
- });
433
- return confirmation.confirm_staging;
434
- }
435
- async function ensureLocalRuntimeBot(botonicApiService) {
436
- if (botonicApiService.hasLocalRuntimeBot()) {
388
+ async function ensureBot(botonicApiService) {
389
+ if (botonicApiService.bot) {
437
390
  return;
438
391
  }
439
392
  const bots = await getAvailableBots(botonicApiService);
440
393
  if (bots.length === 0) {
441
- console.log("\u{1F916} No bots yet. Let's create one for local runtime.\n");
394
+ console.log(
395
+ "\u{1F916} No bots found. Let's create one.\n \u26A0\uFE0F After creating, you'll need to deploy it with 'nx run <bot>:deploy-to-hubtype' before serve will work.\n"
396
+ );
442
397
  const response2 = await (0, import_enquirer.prompt)({
443
398
  type: "input",
444
399
  name: "bot_name",
@@ -452,18 +407,17 @@ async function ensureLocalRuntimeBot(botonicApiService) {
452
407
  }
453
408
  });
454
409
  const name = response2.bot_name.trim();
455
- if (!await promptConfirmStagingBot(name)) {
456
- throw new Error("Bot creation cancelled.");
457
- }
458
410
  await createNewBotWithName(botonicApiService, name);
459
411
  return;
460
412
  }
461
- const CREATE_NEW_CHOICE = "__create_new__";
462
- console.log("\u{1F4CB} Select a bot for local runtime or create a new one:\n");
413
+ const CREATE_NEW_CHOICE = "New Bot";
414
+ console.log(
415
+ "\u{1F4CB} Select a deployed bot to use for local development, or create a new one:\n \u2139\uFE0F The bot must already be deployed (botonic_v2 strategy) for serve to work.\n"
416
+ );
463
417
  const response = await (0, import_enquirer.prompt)({
464
418
  type: "select",
465
419
  name: "bot_id",
466
- message: "\u{1F916} Select a bot or create new:",
420
+ message: "\u{1F916} Select a bot:",
467
421
  choices: [
468
422
  ...bots.map((bot2) => ({
469
423
  name: bot2.id,
@@ -473,6 +427,9 @@ async function ensureLocalRuntimeBot(botonicApiService) {
473
427
  ]
474
428
  });
475
429
  if (response.bot_id === CREATE_NEW_CHOICE) {
430
+ console.log(
431
+ " \u26A0\uFE0F After creating, you'll need to deploy it with 'nx run <bot>:deploy-to-hubtype' before serve will work.\n"
432
+ );
476
433
  const nameResponse = await (0, import_enquirer.prompt)({
477
434
  type: "input",
478
435
  name: "bot_name",
@@ -486,19 +443,38 @@ async function ensureLocalRuntimeBot(botonicApiService) {
486
443
  }
487
444
  });
488
445
  const name = nameResponse.bot_name.trim();
489
- if (!await promptConfirmStagingBot(name)) {
490
- throw new Error("Bot creation cancelled.");
491
- }
492
446
  await createNewBotWithName(botonicApiService, name);
493
447
  return;
494
448
  }
495
449
  const bot = bots.find((b) => b.id === response.bot_id);
496
- if (bot) {
497
- botonicApiService.setLocalRuntimeBot(bot);
498
- console.log(`\u2705 Selected bot: ${bot.name}`);
499
- } else {
450
+ if (!bot) {
500
451
  throw new Error("Bot selection failed");
501
452
  }
453
+ await botonicApiService.selectBot(bot);
454
+ console.log(`\u2705 Selected bot: ${bot.name}`);
455
+ }
456
+ async function refreshBotProviders(botonicApiService) {
457
+ process.stdout.write("\u23F3 Fetching bot information...");
458
+ try {
459
+ const providers = await botonicApiService.fetchProviders(
460
+ botonicApiService.botInfo().id
461
+ );
462
+ botonicApiService.saveProviders(providers);
463
+ process.stdout.write("\r\u2705 Bot information fetched. \n");
464
+ } catch {
465
+ process.stdout.write("\r\u26A0\uFE0F Could not fetch bot information. \n");
466
+ }
467
+ }
468
+ const WEBCHAT_PROVIDER = "webchat";
469
+ function getActiveWebchatProviderAppId(results) {
470
+ return results?.find(
471
+ (p) => !p.is_test && Boolean(p.is_active) && p.provider === WEBCHAT_PROVIDER
472
+ )?.id;
473
+ }
474
+ function getActiveTestWebchatProviderAppId(results) {
475
+ return results?.find(
476
+ (p) => Boolean(p.is_test) && Boolean(p.is_active) && p.provider === WEBCHAT_PROVIDER
477
+ )?.id;
502
478
  }
503
479
  async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpoint, options = {}) {
504
480
  const { targetEnvironment, environmentVariables } = resolveHubtypeEnvironment(
@@ -506,9 +482,7 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
506
482
  options
507
483
  );
508
484
  const botonicApiService = new import_api_service.BotonicAPIService({
509
- isLocalRuntimeDeployment: true,
510
485
  projectRoot,
511
- workspaceRoot: (0, import_path.resolve)(context.root),
512
486
  environmentVariables,
513
487
  targetEnvironment
514
488
  });
@@ -520,41 +494,72 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
520
494
  await handleAuthentication(botonicApiService);
521
495
  }
522
496
  }
497
+ if (options.selectedBotId && botonicApiService.bot?.id !== options.selectedBotId) {
498
+ const bots = await getAvailableBots(botonicApiService);
499
+ const bot = bots.find((b) => b.id === options.selectedBotId);
500
+ if (bot) await botonicApiService.selectBot(bot);
501
+ }
523
502
  const maxAttempts = 2;
524
503
  let lastError;
525
504
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
526
505
  try {
527
- await ensureLocalRuntimeBot(botonicApiService);
528
- writeLocalRuntimeBotToAppFolder(projectRoot, botonicApiService.botInfo());
529
- const functionName = (0, import_sam_template.getLocalLambdaFunctionName)(projectRoot);
506
+ await ensureBot(botonicApiService);
507
+ await refreshBotProviders(botonicApiService);
508
+ const lambdaFunctionName = (0, import_sam_template.getLocalLambdaFunctionName)(projectRoot);
530
509
  const botConfigJson = await import_bot_config.BotConfig.get(projectRoot);
531
- await botonicApiService.deployLocalRuntime({
532
- botConfigJson,
533
- lambdaFunctionName: functionName,
534
- lambdaEndpoint: endpoint
535
- });
536
- const providersResp = await botonicApiService.getProviders();
537
- const appId = providersResp.data.results?.filter(
538
- (p) => p.is_test && p.is_active
539
- )[0]?.id;
540
- const resolvedTargetEnv = TARGET_ENV_NAMES.includes(
541
- targetEnvironment
542
- ) ? targetEnvironment : "local";
510
+ await botonicApiService.refreshTokenIfNeeded();
511
+ const botId = botonicApiService.botInfo().id;
512
+ if (options.whatsappPhone) {
513
+ try {
514
+ await botonicApiService.registerDevSessionWhatsapp(
515
+ botId,
516
+ options.whatsappPhone,
517
+ endpoint,
518
+ lambdaFunctionName,
519
+ botConfigJson
520
+ );
521
+ } catch (regErr) {
522
+ throwIfBotNotDeployed(regErr, botonicApiService.botInfo().name);
523
+ throw regErr;
524
+ }
525
+ console.log(
526
+ `\u2705 WhatsApp local routing registered for ${options.whatsappPhone}`
527
+ );
528
+ } else {
529
+ console.log(
530
+ `\u2705 Webchat local routing ready (registration deferred until user connects)`
531
+ );
532
+ }
533
+ const providers = botonicApiService.bot?.providers ?? [];
534
+ const appId = getActiveWebchatProviderAppId(providers) ?? getActiveTestWebchatProviderAppId(providers);
543
535
  if (appId) {
536
+ const resolvedTargetEnv = TARGET_ENV_NAMES.includes(
537
+ targetEnvironment
538
+ ) ? targetEnvironment : "local";
544
539
  await writeAppIdToEnvFile(projectRoot, appId, resolvedTargetEnv);
540
+ } else {
541
+ console.warn(
542
+ "\u26A0\uFE0F No active webchat provider found for this bot. Set VITE_HUBTYPE_APP_ID manually in .env to use the local webchat widget."
543
+ );
545
544
  }
546
545
  botonicApiService.saveAllCredentials();
547
546
  return {
548
- botId: botonicApiService.botInfo().id,
547
+ botId,
548
+ apiUrl: botonicApiService.baseUrl,
549
+ accessToken: botonicApiService.getOauth().access_token ?? "",
550
+ refreshToken: botonicApiService.getOauth().refresh_token ?? "",
551
+ clientId: botonicApiService.clientId,
549
552
  targetEnvironment,
550
- environmentVariables
553
+ environmentVariables,
554
+ lambdaFunctionName,
555
+ teardownWebchat: async () => {
556
+ }
551
557
  };
552
558
  } catch (err) {
553
559
  lastError = err;
554
560
  const status = err?.response?.status;
555
561
  if (status === 404 && attempt < maxAttempts) {
556
- removeAppBotonicJson(projectRoot);
557
- botonicApiService.setLocalRuntimeBot(null);
562
+ botonicApiService.setCurrentBot(null);
558
563
  continue;
559
564
  }
560
565
  throw err;
@@ -562,28 +567,422 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
562
567
  }
563
568
  throw lastError;
564
569
  }
570
+ function throwIfBotNotDeployed(err, botName) {
571
+ const data = err?.response?.data;
572
+ const message = typeof data === "string" ? data : JSON.stringify(data ?? "");
573
+ if (message.includes("deployed Botonic v2 bot") || message.includes("deployment_strategy")) {
574
+ throw new Error(
575
+ `\u274C Bot "${botName}" must be deployed before it can be used for local development.
576
+ Run: nx run <bot>:deploy-to-hubtype
577
+ Then retry: nx run <bot>:serve`
578
+ );
579
+ }
580
+ }
581
+ async function selectBotForServe(context, projectRoot, options = {}) {
582
+ const { targetEnvironment, environmentVariables } = resolveHubtypeEnvironment(
583
+ context,
584
+ options
585
+ );
586
+ const botonicApiService = new import_api_service.BotonicAPIService({
587
+ projectRoot,
588
+ environmentVariables,
589
+ targetEnvironment
590
+ });
591
+ const projectName = context.projectName ?? "";
592
+ if (options.botName) {
593
+ await selectBotByName(
594
+ botonicApiService,
595
+ options.botName,
596
+ projectRoot,
597
+ projectName,
598
+ targetEnvironment
599
+ );
600
+ } else {
601
+ await selectBotInteractive(
602
+ botonicApiService,
603
+ projectRoot,
604
+ projectName,
605
+ targetEnvironment
606
+ );
607
+ }
608
+ const selectedBotId = botonicApiService.bot?.id;
609
+ if (selectedBotId) {
610
+ const resolvedEnv = TARGET_ENV_NAMES.includes(
611
+ targetEnvironment
612
+ ) ? targetEnvironment : "local";
613
+ writeEnvVarToEnvFile(
614
+ projectRoot,
615
+ "VITE_DEV_BOT_ID",
616
+ selectedBotId,
617
+ resolvedEnv
618
+ );
619
+ }
620
+ return selectedBotId;
621
+ }
622
+ async function selectBotByName(botonicApiService, botName, projectRoot, projectName, configuration) {
623
+ const bots = await getAvailableBots(botonicApiService);
624
+ const bot = bots.find((b) => b.name === botName);
625
+ if (bot) {
626
+ if (!bot.is_test) {
627
+ throw new Error(
628
+ `Bot "${botName}" is a production bot and cannot be used for local development. Use a staging bot instead.`
629
+ );
630
+ }
631
+ await botonicApiService.selectBot(bot);
632
+ console.log(`\u2705 Selected bot: ${botLabel(bot)}`);
633
+ } else {
634
+ console.log(
635
+ `\u{1F916} Bot "${botName}" not found. Creating it as a staging bot...`
636
+ );
637
+ await createNewBotWithName(botonicApiService, botName, true);
638
+ await autoDeploy(
639
+ botonicApiService,
640
+ projectRoot,
641
+ projectName,
642
+ botName,
643
+ configuration
644
+ );
645
+ }
646
+ botonicApiService.saveAllCredentials();
647
+ }
648
+ const CREATE_STAGING_CHOICE = "Create Staging Bot";
649
+ function botLabel(bot) {
650
+ return bot.is_test ? `${bot.name} (Staging bot)` : `${bot.name} (Production bot)`;
651
+ }
652
+ const CHANNEL_ICONS = {
653
+ whatsapp: "\u{1F4AC} WhatsApp",
654
+ webchat: "\u{1F310} Webchat",
655
+ facebook: "\u{1F499} Facebook",
656
+ telegram: "\u2708\uFE0F Telegram",
657
+ twitter: "\u{1F426} Twitter"
658
+ };
659
+ function botLabelWithChannels(bot) {
660
+ const base = botLabel(bot);
661
+ const activeChannels = (bot.providers ?? []).filter((p) => p.is_active).map((p) => CHANNEL_ICONS[p.provider] ?? p.provider);
662
+ return activeChannels.length > 0 ? `${base} \u2014 ${activeChannels.join(", ")}` : base;
663
+ }
664
+ async function promptNewBotName() {
665
+ const response = await (0, import_enquirer.prompt)({
666
+ type: "input",
667
+ name: "bot_name",
668
+ message: "\u{1F916} Bot name:",
669
+ validate: (input) => {
670
+ if (!input.trim()) return "Bot name cannot be empty";
671
+ if (input.length > MAX_BOT_NAME_LENGTH) {
672
+ return `Bot name must be at most ${MAX_BOT_NAME_LENGTH} characters`;
673
+ }
674
+ return true;
675
+ }
676
+ });
677
+ return response.bot_name.trim();
678
+ }
679
+ async function selectBotInteractive(botonicApiService, projectRoot, projectName, configuration) {
680
+ const bots = await getAvailableBots(botonicApiService);
681
+ if (bots.length === 0) {
682
+ console.log("\u{1F916} No bots found. Creating a new one for local development.\n");
683
+ const name = await promptNewBotName();
684
+ await createNewBotWithName(botonicApiService, name, true);
685
+ await autoDeploy(
686
+ botonicApiService,
687
+ projectRoot,
688
+ projectName,
689
+ name,
690
+ configuration
691
+ );
692
+ return;
693
+ }
694
+ const stagingBots = bots.filter((b) => b.is_test);
695
+ if (stagingBots.length === 0) {
696
+ console.log(
697
+ "\u{1F916} No staging bots found. Creating one for local development.\n"
698
+ );
699
+ const name = await promptNewBotName();
700
+ await createNewBotWithName(botonicApiService, name, true);
701
+ await autoDeploy(
702
+ botonicApiService,
703
+ projectRoot,
704
+ projectName,
705
+ name,
706
+ configuration
707
+ );
708
+ return;
709
+ }
710
+ process.stdout.write("\u23F3 Fetching bot information...");
711
+ const botsWithProviders = await Promise.all(
712
+ stagingBots.map(async (bot2) => {
713
+ try {
714
+ const providers = await botonicApiService.fetchProviders(bot2.id);
715
+ const enriched = { ...bot2, providers };
716
+ botonicApiService.saveBotEntry(enriched);
717
+ return enriched;
718
+ } catch {
719
+ return bot2;
720
+ }
721
+ })
722
+ );
723
+ process.stdout.write("\r\u2705 Bot information fetched. \n");
724
+ const previousBotId = botonicApiService.bot?.id;
725
+ const sortedBots = [...botsWithProviders].sort(
726
+ (a, b) => {
727
+ if (a.id === previousBotId) return -1;
728
+ if (b.id === previousBotId) return 1;
729
+ return 0;
730
+ }
731
+ );
732
+ console.log("\u{1F4CB} Select a bot for local development:\n");
733
+ const response = await (0, import_enquirer.prompt)({
734
+ type: "select",
735
+ name: "bot_id",
736
+ message: "\u{1F916} Select a bot:",
737
+ // initial: 0 defaults to the first item (previously used bot after sort)
738
+ initial: 0,
739
+ choices: [
740
+ ...sortedBots.map((bot2) => ({
741
+ name: bot2.id,
742
+ message: botLabelWithChannels(bot2)
743
+ })),
744
+ {
745
+ name: CREATE_STAGING_CHOICE,
746
+ message: "+ Create Staging Bot (recommended for dev sessions)"
747
+ }
748
+ ]
749
+ });
750
+ if (response.bot_id === CREATE_STAGING_CHOICE) {
751
+ const name = await promptNewBotName();
752
+ await createNewBotWithName(botonicApiService, name, true);
753
+ await autoDeploy(
754
+ botonicApiService,
755
+ projectRoot,
756
+ projectName,
757
+ name,
758
+ configuration
759
+ );
760
+ return;
761
+ }
762
+ const bot = sortedBots.find((b) => b.id === response.bot_id);
763
+ if (!bot) {
764
+ throw new Error("Bot selection failed");
765
+ }
766
+ await botonicApiService.selectBot(bot);
767
+ console.log(
768
+ `\u2705 Selected bot: ${botLabel(bot)}`
769
+ );
770
+ botonicApiService.saveAllCredentials();
771
+ }
772
+ async function autoDeploy(botonicApiService, projectRoot, projectName, botName, configuration) {
773
+ console.log(
774
+ `
775
+ \u{1F680} New bot "${botName}" detected \u2014 deploying before serve can start...
776
+ `
777
+ );
778
+ await deployBotToHubtype(botonicApiService, projectRoot, projectName);
779
+ const botId = botonicApiService.bot?.id;
780
+ if (botId) {
781
+ try {
782
+ await botonicApiService.createWebchatIntegration(botId, botName);
783
+ console.log(` \u{1F50C} Webchat integration created for bot "${botName}".`);
784
+ } catch {
785
+ }
786
+ }
787
+ botonicApiService.saveAllCredentials();
788
+ const resolvedConfig = configuration ?? "local";
789
+ console.log(
790
+ `
791
+ \u2705 Bot "${botName}" deployed successfully.
792
+ Run the following command to start serving:
793
+
794
+ pnpm nx run ${projectName}:serve:${resolvedConfig} --botName="${botName}"
795
+ `
796
+ );
797
+ throw new BotDeployedRestartRequired(botName, projectName);
798
+ }
799
+ class BotDeployedRestartRequired extends Error {
800
+ constructor(botName, projectName) {
801
+ super(
802
+ `Bot "${botName}" was just deployed. Run: pnpm nx serve ${projectName}`
803
+ );
804
+ this.name = "BotDeployedRestartRequired";
805
+ }
806
+ }
807
+ function isBotDeployedRestartRequired(error) {
808
+ return error instanceof Error && error.name === "BotDeployedRestartRequired";
809
+ }
810
+ function selectedBotHasActiveWhatsapp(botId) {
811
+ try {
812
+ const botsJsonPath = (0, import_path.join)((0, import_os.homedir)(), ".botonic", "bots.json");
813
+ if (!(0, import_fs.existsSync)(botsJsonPath)) return false;
814
+ const registry = JSON.parse((0, import_fs.readFileSync)(botsJsonPath, "utf8"));
815
+ const bot = Object.values(registry).flat().find((e) => e.id === botId);
816
+ return bot?.providers?.some((p) => p.provider === "whatsapp" && p.is_active) ?? false;
817
+ } catch {
818
+ return false;
819
+ }
820
+ }
821
+ const BOTONIC_BUNDLE_FILE = "botonic_bundle.zip";
822
+ const BOTONIC_TEMP_DIRNAME = "tmp";
823
+ async function createBundle(projectRoot) {
824
+ console.log("\u{1F4E6} Creating deployment bundle...");
825
+ if ((0, import_file_system.pathExists)((0, import_path.join)(projectRoot, BOTONIC_TEMP_DIRNAME))) {
826
+ (0, import_file_system.removeRecursively)((0, import_path.join)(projectRoot, BOTONIC_TEMP_DIRNAME));
827
+ }
828
+ const webchatHtmlPath = (0, import_path.join)(projectRoot, "dist", "webchat", "webchat.html");
829
+ const webchatIndexPath = (0, import_path.join)(projectRoot, "dist", "webchat", "index.html");
830
+ if ((0, import_file_system.pathExists)(webchatHtmlPath)) {
831
+ (0, import_fs.renameSync)(webchatHtmlPath, webchatIndexPath);
832
+ }
833
+ const webviewsHtmlPath = (0, import_path.join)(
834
+ projectRoot,
835
+ "dist",
836
+ "webviews",
837
+ "webviews.html"
838
+ );
839
+ const webviewsIndexPath = (0, import_path.join)(projectRoot, "dist", "webviews", "index.html");
840
+ if ((0, import_file_system.pathExists)(webviewsHtmlPath)) {
841
+ (0, import_fs.renameSync)(webviewsHtmlPath, webviewsIndexPath);
842
+ }
843
+ (0, import_file_system.createDir)((0, import_path.join)(projectRoot, BOTONIC_TEMP_DIRNAME));
844
+ (0, import_file_system.copy)(
845
+ (0, import_path.join)(projectRoot, "dist"),
846
+ (0, import_path.join)(projectRoot, BOTONIC_TEMP_DIRNAME, "dist")
847
+ );
848
+ const zipRes = await import_zip_a_folder.ZipAFolder.zip(
849
+ (0, import_path.join)(projectRoot, BOTONIC_TEMP_DIRNAME),
850
+ (0, import_path.join)(projectRoot, BOTONIC_BUNDLE_FILE)
851
+ );
852
+ if (zipRes instanceof Error) {
853
+ throw zipRes;
854
+ }
855
+ const zipStats = (0, import_fs.statSync)((0, import_path.join)(projectRoot, BOTONIC_BUNDLE_FILE));
856
+ console.log("\u2705 Bundle created successfully!");
857
+ if (zipStats.size >= 20 * 10 ** 6) {
858
+ throw new Error(
859
+ `Bundle too large: ${(zipStats.size / 1024 / 1024).toFixed(2)}MB (max: 20MB)`
860
+ );
861
+ }
862
+ }
863
+ async function deployBundle(botonicApiService, botConfigJson, projectRoot) {
864
+ console.log("\u{1F680} Deploying to Hubtype Cloud...");
865
+ try {
866
+ const deploy = await botonicApiService.deployBot(
867
+ (0, import_path.join)(projectRoot, BOTONIC_BUNDLE_FILE),
868
+ botConfigJson
869
+ );
870
+ if (deploy.response?.status === 403 || !deploy.data.deploy_id) {
871
+ throw new Error(
872
+ `Deploy Botonic Error: ${String(deploy.response?.data?.status)}`
873
+ );
874
+ }
875
+ console.log("\u23F3 Waiting for deployment to complete...");
876
+ while (true) {
877
+ await (0, import_system.sleep)(500);
878
+ const deployStatus = await botonicApiService.deployStatus(
879
+ deploy.data.deploy_id
880
+ );
881
+ if (deployStatus.data.is_completed) {
882
+ if (deployStatus.data.status === "deploy_status_completed_ok") {
883
+ console.log("\u2705 Deployment completed successfully!");
884
+ return { hasDeployErrors: false };
885
+ } else {
886
+ throw new Error(deployStatus.data.error);
887
+ }
888
+ }
889
+ }
890
+ } catch (error) {
891
+ console.error("\u274C Deployment failed");
892
+ let reason = String(error);
893
+ if (error.response?.data) {
894
+ reason = error.response.data.join("");
895
+ }
896
+ console.error(`${reason}`);
897
+ return { hasDeployErrors: true };
898
+ }
899
+ }
900
+ async function displayDeployResults(botonicApiService, { hasDeployErrors }) {
901
+ try {
902
+ const providersRes = await botonicApiService.getProviders();
903
+ const providers = providersRes.data.results ?? [];
904
+ if (hasDeployErrors) return false;
905
+ if (!providers.length) {
906
+ const botId = botonicApiService.botInfo().id;
907
+ const accessToken = botonicApiService.getOauth().access_token;
908
+ const links = `Now, you can integrate a channel in:
909
+ https://app.hubtype.com/bots/${botId}/integrations?access_token=${accessToken}`;
910
+ console.log(links);
911
+ } else {
912
+ displayProviders(providers);
913
+ }
914
+ return true;
915
+ } catch (e) {
916
+ console.error(` There was an error getting the providers: ${String(e)}`);
917
+ return false;
918
+ }
919
+ }
920
+ function displayProviders(providers) {
921
+ console.log("\u{1F389} DEPLOYMENT SUCCESSFUL!");
922
+ console.log("\u{1F680} Your bot is now live and ready to chat!");
923
+ console.log("\u{1F4F1} Your bot is published on:");
924
+ providers.forEach((p) => {
925
+ if (p.provider === "whatsapp")
926
+ console.log(` \u{1F4AC} [WhatsApp] https://wa.me/${p.username}`);
927
+ if (p.provider === "facebook")
928
+ console.log(` \u{1F4AC} [Facebook] https://m.me/${p.username}`);
929
+ if (p.provider === "telegram")
930
+ console.log(` \u{1F4AC} [Telegram] https://t.me/${p.username}`);
931
+ if (p.provider === "twitter")
932
+ console.log(` \u{1F4AC} [Twitter] https://t.me/${p.username}`);
933
+ if (p.provider === "generic")
934
+ console.log(` \u{1F4AC} [Generic] Your app or website`);
935
+ });
936
+ }
937
+ async function deployBotToHubtype(botonicApiService, projectRoot, projectName) {
938
+ console.log("\u{1F528} Preparing your bot for deployment...\n");
939
+ const buildOut = await botonicApiService.build({
940
+ projectRoot,
941
+ projectName
942
+ });
943
+ if (!buildOut) {
944
+ throw new Error("Build failed");
945
+ }
946
+ const botConfigJson = await import_bot_config.BotConfig.get(projectRoot);
947
+ await createBundle(projectRoot);
948
+ const { hasDeployErrors } = await deployBundle(
949
+ botonicApiService,
950
+ botConfigJson,
951
+ projectRoot
952
+ );
953
+ await displayDeployResults(botonicApiService, { hasDeployErrors });
954
+ (0, import_fs.rmSync)((0, import_path.join)(projectRoot, BOTONIC_BUNDLE_FILE));
955
+ (0, import_file_system.removeRecursively)((0, import_path.join)(projectRoot, BOTONIC_TEMP_DIRNAME));
956
+ botonicApiService.saveAllCredentials();
957
+ if (hasDeployErrors) {
958
+ throw new Error("Deployment failed. Check the output above for details.");
959
+ }
960
+ }
565
961
  // Annotate the CommonJS export names for ESM import in node:
566
962
  0 && (module.exports = {
567
963
  askEmailPassword,
568
964
  askLogin,
569
965
  askSignup,
570
966
  createNewBotWithName,
967
+ deployBotToHubtype,
968
+ ensureBot,
571
969
  ensureHubtypeLoginBeforeTunnel,
572
- ensureLocalRuntimeBot,
573
- ensureLocalRuntimeBotBeforeTunnel,
574
970
  findMatchingConfigurationKey,
971
+ getActiveTestWebchatProviderAppId,
972
+ getActiveWebchatProviderAppId,
575
973
  getAppIdFromEnvFile,
576
974
  getAppIdFromEnvFileForConfig,
577
975
  getAvailableBots,
578
976
  handleAuthentication,
579
977
  handleExecutorError,
978
+ isBotDeployedRestartRequired,
580
979
  logWorkingAsAndEnvironment,
581
980
  performDeployLocalRuntimeWithEndpoint,
582
- removeAppBotonicJson,
583
981
  resolveHubtypeEnvironment,
584
982
  resolveProjectPath,
983
+ selectBotForServe,
984
+ selectedBotHasActiveWhatsapp,
585
985
  writeAppIdToEnvFile,
586
986
  writeAppIdToLocalEnv,
587
- writeEnvVarToEnvFile,
588
- writeLocalRuntimeBotToAppFolder
987
+ writeEnvVarToEnvFile
589
988
  });