@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.
- package/CHANGELOG.md +32 -0
- package/executors.json +0 -5
- package/package.json +3 -2
- 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/src/client/webchat/index.tsx.template +2 -0
- package/src/generators/bot-app/files/src/client/webchat/webchat.tsx.template +111 -0
- package/src/generators/bot-app/files/src/server/bot/plugins/flow-builder/index.ts.template +13 -4
- package/src/generators/bot-app/files/src/server/bot/plugins/index.ts.template +0 -3
- package/src/generators/bot-app/files/src/server/lambda/handler.js.template +1 -1
- package/src/generators/bot-app/files/src/server/lambda/package.json +3 -3
- package/src/generators/bot-app/files/vite/node.config.ts.template +2 -4
- package/src/generators/bot-app/files/vite/webchat.config.ts.template +20 -1
- package/src/generators/bot-app/generator.js +6 -5
- package/src/generators/bot-app/lilara-version.json +1 -1
- package/src/lib/api-service.d.ts +19 -20
- package/src/lib/api-service.js +150 -82
- package/src/lib/bot-config.d.ts +10 -7
- package/src/lib/bot-config.js +5 -1
- 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 +58 -18
- package/src/lib/util/executor-helpers.js +501 -102
- package/src/plugin.js +6 -15
- package/src/executors/deploy-local-runtime/executor.d.ts +0 -5
- package/src/executors/deploy-local-runtime/executor.js +0 -144
- 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/src/server/bot/tracking.ts.template +0 -35
- 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
|
|
386
|
-
|
|
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(
|
|
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 = "
|
|
462
|
-
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
|
+
);
|
|
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
|
|
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
|
|
528
|
-
|
|
529
|
-
const
|
|
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.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
|
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
|
-
|
|
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
|
});
|