@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.
- package/CHANGELOG.md +13 -0
- package/executors.json +0 -5
- package/package.json +1 -1
- package/src/executors/delete-bot/executor.js +0 -2
- package/src/executors/deploy-to-hubtype/executor.js +24 -160
- package/src/executors/e2e-webchat/botonic-package-publish.spec.ts +7 -11
- package/src/executors/integrate-provider/executor.js +0 -2
- package/src/executors/login-to-hubtype/executor.js +0 -2
- package/src/executors/logout-from-hubtype/executor.js +2 -2
- package/src/executors/serve-bot/executor.js +142 -24
- package/src/executors/serve-bot/schema.json +13 -5
- package/src/generators/bot-app/files/vite/node.config.ts.template +2 -7
- package/src/lib/api-service.d.ts +19 -20
- package/src/lib/api-service.js +150 -82
- package/src/lib/constants.d.ts +2 -3
- package/src/lib/constants.js +6 -9
- package/src/lib/credentials-handler.d.ts +9 -18
- package/src/lib/credentials-handler.js +42 -24
- package/src/lib/interfaces.d.ts +10 -13
- package/src/lib/util/executor-helpers.d.ts +52 -23
- package/src/lib/util/executor-helpers.js +494 -106
- package/src/plugin.js +5 -14
- package/src/executors/deploy-local-runtime/executor.d.ts +0 -5
- package/src/executors/deploy-local-runtime/executor.js +0 -148
- package/src/executors/deploy-local-runtime/schema.d.js +0 -16
- package/src/executors/deploy-local-runtime/schema.json +0 -34
- package/src/generators/bot-app/files/vite/botonic-ssr-deps.ts.template +0 -56
- package/src/generators/preset/files/package.json +0 -26
|
@@ -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
|
|
387
|
-
|
|
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(
|
|
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 = "
|
|
463
|
-
console.log(
|
|
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
|
|
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
|
|
535
|
-
|
|
536
|
-
const
|
|
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.
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
|
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
|
-
|
|
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
|
});
|