@botonic/nx-plugin 2.30.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.
@@ -22,35 +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
29
  getActiveTestWebchatProviderAppId: () => getActiveTestWebchatProviderAppId,
30
+ getActiveWebchatProviderAppId: () => getActiveWebchatProviderAppId,
30
31
  getAppIdFromEnvFile: () => getAppIdFromEnvFile,
31
32
  getAppIdFromEnvFileForConfig: () => getAppIdFromEnvFileForConfig,
32
33
  getAvailableBots: () => getAvailableBots,
33
34
  handleAuthentication: () => handleAuthentication,
34
35
  handleExecutorError: () => handleExecutorError,
36
+ isBotDeployedRestartRequired: () => isBotDeployedRestartRequired,
35
37
  logWorkingAsAndEnvironment: () => logWorkingAsAndEnvironment,
36
38
  performDeployLocalRuntimeWithEndpoint: () => performDeployLocalRuntimeWithEndpoint,
37
- removeAppBotonicJson: () => removeAppBotonicJson,
38
39
  resolveHubtypeEnvironment: () => resolveHubtypeEnvironment,
39
40
  resolveProjectPath: () => resolveProjectPath,
41
+ selectBotForServe: () => selectBotForServe,
42
+ selectedBotHasActiveWhatsapp: () => selectedBotHasActiveWhatsapp,
40
43
  writeAppIdToEnvFile: () => writeAppIdToEnvFile,
41
44
  writeAppIdToLocalEnv: () => writeAppIdToLocalEnv,
42
- writeEnvVarToEnvFile: () => writeEnvVarToEnvFile,
43
- writeLocalRuntimeBotToAppFolder: () => writeLocalRuntimeBotToAppFolder
45
+ writeEnvVarToEnvFile: () => writeEnvVarToEnvFile
44
46
  });
45
47
  module.exports = __toCommonJS(executor_helpers_exports);
46
48
  var import_enquirer = require("enquirer");
47
49
  var import_fs = require("fs");
50
+ var import_os = require("os");
48
51
  var import_path = require("path");
52
+ var import_zip_a_folder = require("zip-a-folder");
49
53
  var import_api_service = require("../api-service");
50
54
  var import_bot_config = require("../bot-config");
51
- var import_constants = require("../constants");
52
55
  var import_file_system = require("./file-system");
53
56
  var import_sam_template = require("./sam-template");
57
+ var import_system = require("./system");
54
58
  function resolveProjectPath(context) {
55
59
  if (!context.projectName) {
56
60
  throw new Error("Project name is not defined in executor context");
@@ -140,14 +144,14 @@ async function getAvailableBots(botonicApiService) {
140
144
  return resp.data.results;
141
145
  }
142
146
  const MAX_BOT_NAME_LENGTH = 25;
143
- async function createNewBotWithName(botonicApiService, botName) {
147
+ async function createNewBotWithName(botonicApiService, botName, isTest = false) {
144
148
  if (botName.length > MAX_BOT_NAME_LENGTH) {
145
149
  throw new Error(
146
150
  `Bot name must be at most ${MAX_BOT_NAME_LENGTH} characters. Please choose a shorter name.`
147
151
  );
148
152
  }
149
153
  console.log(`\u{1F916} Creating bot "${botName}"...`);
150
- await botonicApiService.createBot(botName);
154
+ await botonicApiService.createBot(botName, isTest);
151
155
  console.log(`\u2705 Bot "${botName}" created successfully!`);
152
156
  }
153
157
  function getAppIdFromEnvFile(projectRoot, appIdKey) {
@@ -367,9 +371,7 @@ async function ensureHubtypeLoginBeforeTunnel(context, projectRoot, options = {}
367
371
  options
368
372
  );
369
373
  const botonicApiService = new import_api_service.BotonicAPIService({
370
- isLocalRuntimeDeployment: true,
371
374
  projectRoot,
372
- workspaceRoot: (0, import_path.resolve)(context.root),
373
375
  environmentVariables,
374
376
  targetEnvironment
375
377
  });
@@ -383,63 +385,15 @@ async function ensureHubtypeLoginBeforeTunnel(context, projectRoot, options = {}
383
385
  }
384
386
  await logWorkingAsAndEnvironment(botonicApiService);
385
387
  }
386
- async function ensureLocalRuntimeBotBeforeTunnel(context, projectRoot, options = {}) {
387
- const { targetEnvironment, environmentVariables } = resolveHubtypeEnvironment(
388
- context,
389
- options
390
- );
391
- const botonicApiService = new import_api_service.BotonicAPIService({
392
- isLocalRuntimeDeployment: true,
393
- projectRoot,
394
- workspaceRoot: (0, import_path.resolve)(context.root),
395
- environmentVariables,
396
- targetEnvironment
397
- });
398
- if (!botonicApiService.oauth) {
399
- if (options.email && options.password) {
400
- await botonicApiService.login(options.email, options.password);
401
- botonicApiService.saveAllCredentials();
402
- } else {
403
- await handleAuthentication(botonicApiService);
404
- }
405
- }
406
- await ensureLocalRuntimeBot(botonicApiService);
407
- writeLocalRuntimeBotToAppFolder(projectRoot, botonicApiService.botInfo());
408
- botonicApiService.saveAllCredentials();
409
- }
410
- function writeLocalRuntimeBotToAppFolder(projectRoot, bot) {
411
- const path = (0, import_path.join)(projectRoot, import_constants.BOT_CREDENTIALS_FILENAME);
412
- (0, import_file_system.writeJSON)(path, { bot });
413
- }
414
- function removeAppBotonicJson(projectRoot) {
415
- const path = (0, import_path.join)(projectRoot, import_constants.BOT_CREDENTIALS_FILENAME);
416
- if ((0, import_fs.existsSync)(path)) {
417
- (0, import_fs.unlinkSync)(path);
418
- }
419
- }
420
- async function promptConfirmStagingBot(botName) {
421
- console.log(
422
- `
423
- \u26A0\uFE0F Bot "${botName}" will be created as a staging bot on the Hubtype platform.`
424
- );
425
- console.log(
426
- ` To create a production bot, use the Hubtype dashboard or the deploy-to-hubtype command instead.
427
- `
428
- );
429
- const confirmation = await (0, import_enquirer.prompt)({
430
- type: "confirm",
431
- name: "confirm_staging",
432
- message: "\u{1F6A6} Do you want to proceed?"
433
- });
434
- return confirmation.confirm_staging;
435
- }
436
- async function ensureLocalRuntimeBot(botonicApiService) {
437
- if (botonicApiService.hasLocalRuntimeBot()) {
388
+ async function ensureBot(botonicApiService) {
389
+ if (botonicApiService.bot) {
438
390
  return;
439
391
  }
440
392
  const bots = await getAvailableBots(botonicApiService);
441
393
  if (bots.length === 0) {
442
- 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
+ );
443
397
  const response2 = await (0, import_enquirer.prompt)({
444
398
  type: "input",
445
399
  name: "bot_name",
@@ -453,18 +407,17 @@ async function ensureLocalRuntimeBot(botonicApiService) {
453
407
  }
454
408
  });
455
409
  const name = response2.bot_name.trim();
456
- if (!await promptConfirmStagingBot(name)) {
457
- throw new Error("Bot creation cancelled.");
458
- }
459
410
  await createNewBotWithName(botonicApiService, name);
460
411
  return;
461
412
  }
462
- const CREATE_NEW_CHOICE = "__create_new__";
463
- 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
+ );
464
417
  const response = await (0, import_enquirer.prompt)({
465
418
  type: "select",
466
419
  name: "bot_id",
467
- message: "\u{1F916} Select a bot or create new:",
420
+ message: "\u{1F916} Select a bot:",
468
421
  choices: [
469
422
  ...bots.map((bot2) => ({
470
423
  name: bot2.id,
@@ -474,6 +427,9 @@ async function ensureLocalRuntimeBot(botonicApiService) {
474
427
  ]
475
428
  });
476
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
+ );
477
433
  const nameResponse = await (0, import_enquirer.prompt)({
478
434
  type: "input",
479
435
  name: "bot_name",
@@ -487,21 +443,34 @@ async function ensureLocalRuntimeBot(botonicApiService) {
487
443
  }
488
444
  });
489
445
  const name = nameResponse.bot_name.trim();
490
- if (!await promptConfirmStagingBot(name)) {
491
- throw new Error("Bot creation cancelled.");
492
- }
493
446
  await createNewBotWithName(botonicApiService, name);
494
447
  return;
495
448
  }
496
449
  const bot = bots.find((b) => b.id === response.bot_id);
497
- if (bot) {
498
- botonicApiService.setLocalRuntimeBot(bot);
499
- console.log(`\u2705 Selected bot: ${bot.name}`);
500
- } else {
450
+ if (!bot) {
501
451
  throw new Error("Bot selection failed");
502
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
+ }
503
467
  }
504
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
+ }
505
474
  function getActiveTestWebchatProviderAppId(results) {
506
475
  return results?.find(
507
476
  (p) => Boolean(p.is_test) && Boolean(p.is_active) && p.provider === WEBCHAT_PROVIDER
@@ -513,9 +482,7 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
513
482
  options
514
483
  );
515
484
  const botonicApiService = new import_api_service.BotonicAPIService({
516
- isLocalRuntimeDeployment: true,
517
485
  projectRoot,
518
- workspaceRoot: (0, import_path.resolve)(context.root),
519
486
  environmentVariables,
520
487
  targetEnvironment
521
488
  });
@@ -527,44 +494,72 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
527
494
  await handleAuthentication(botonicApiService);
528
495
  }
529
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
+ }
530
502
  const maxAttempts = 2;
531
503
  let lastError;
532
504
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
533
505
  try {
534
- await ensureLocalRuntimeBot(botonicApiService);
535
- writeLocalRuntimeBotToAppFolder(projectRoot, botonicApiService.botInfo());
536
- 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);
537
509
  const botConfigJson = await import_bot_config.BotConfig.get(projectRoot);
538
- await botonicApiService.deployLocalRuntime({
539
- botConfigJson,
540
- lambdaFunctionName: functionName,
541
- lambdaEndpoint: endpoint
542
- });
543
- const providersResp = await botonicApiService.getProviders();
544
- const appId = getActiveTestWebchatProviderAppId(
545
- providersResp.data.results
546
- );
547
- if (!appId) {
548
- throw new Error(
549
- "No active test webchat provider found for this bot. Local runtime requires a webchat test provider account."
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);
535
+ if (appId) {
536
+ const resolvedTargetEnv = TARGET_ENV_NAMES.includes(
537
+ targetEnvironment
538
+ ) ? targetEnvironment : "local";
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."
550
543
  );
551
544
  }
552
- const resolvedTargetEnv = TARGET_ENV_NAMES.includes(
553
- targetEnvironment
554
- ) ? targetEnvironment : "local";
555
- await writeAppIdToEnvFile(projectRoot, appId, resolvedTargetEnv);
556
545
  botonicApiService.saveAllCredentials();
557
546
  return {
558
- 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,
559
552
  targetEnvironment,
560
- environmentVariables
553
+ environmentVariables,
554
+ lambdaFunctionName,
555
+ teardownWebchat: async () => {
556
+ }
561
557
  };
562
558
  } catch (err) {
563
559
  lastError = err;
564
560
  const status = err?.response?.status;
565
561
  if (status === 404 && attempt < maxAttempts) {
566
- removeAppBotonicJson(projectRoot);
567
- botonicApiService.setLocalRuntimeBot(null);
562
+ botonicApiService.setCurrentBot(null);
568
563
  continue;
569
564
  }
570
565
  throw err;
@@ -572,29 +567,422 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
572
567
  }
573
568
  throw lastError;
574
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
+ }
575
961
  // Annotate the CommonJS export names for ESM import in node:
576
962
  0 && (module.exports = {
577
963
  askEmailPassword,
578
964
  askLogin,
579
965
  askSignup,
580
966
  createNewBotWithName,
967
+ deployBotToHubtype,
968
+ ensureBot,
581
969
  ensureHubtypeLoginBeforeTunnel,
582
- ensureLocalRuntimeBot,
583
- ensureLocalRuntimeBotBeforeTunnel,
584
970
  findMatchingConfigurationKey,
585
971
  getActiveTestWebchatProviderAppId,
972
+ getActiveWebchatProviderAppId,
586
973
  getAppIdFromEnvFile,
587
974
  getAppIdFromEnvFileForConfig,
588
975
  getAvailableBots,
589
976
  handleAuthentication,
590
977
  handleExecutorError,
978
+ isBotDeployedRestartRequired,
591
979
  logWorkingAsAndEnvironment,
592
980
  performDeployLocalRuntimeWithEndpoint,
593
- removeAppBotonicJson,
594
981
  resolveHubtypeEnvironment,
595
982
  resolveProjectPath,
983
+ selectBotForServe,
984
+ selectedBotHasActiveWhatsapp,
596
985
  writeAppIdToEnvFile,
597
986
  writeAppIdToLocalEnv,
598
- writeEnvVarToEnvFile,
599
- writeLocalRuntimeBotToAppFolder
987
+ writeEnvVarToEnvFile
600
988
  });