@botpress/cli 0.2.3 → 0.2.5
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/dist/api/bot-body.js +54 -0
- package/dist/api/bot-body.js.map +7 -0
- package/dist/{api-client.js → api/client.js} +9 -9
- package/dist/api/client.js.map +7 -0
- package/dist/api/integration-body.js +81 -0
- package/dist/api/integration-body.js.map +7 -0
- package/dist/command-implementations/deploy-command.js +29 -69
- package/dist/command-implementations/deploy-command.js.map +3 -3
- package/dist/command-implementations/dev-command.js +22 -8
- package/dist/command-implementations/dev-command.js.map +3 -3
- package/dist/command-implementations/global-command.js.map +1 -1
- package/dist/command-implementations/index.js +2 -2
- package/dist/command-implementations/index.js.map +2 -2
- package/dist/command-implementations/project-command.js +0 -6
- package/dist/command-implementations/project-command.js.map +2 -2
- package/dist/utils/record-utils.js +13 -3
- package/dist/utils/record-utils.js.map +2 -2
- package/dist/utils/record-utils.test.js +34 -0
- package/dist/utils/record-utils.test.js.map +7 -0
- package/e2e/api.ts +25 -0
- package/e2e/defaults.ts +20 -0
- package/e2e/index.ts +83 -0
- package/e2e/tests/create-deploy-bot.ts +50 -0
- package/e2e/tests/create-deploy-integration.ts +55 -0
- package/e2e/tests/dev-bot.ts +57 -0
- package/e2e/typings.ts +12 -0
- package/e2e/utils.ts +42 -0
- package/package.json +11 -5
- package/templates/echo-bot/package.json +2 -2
- package/templates/empty-integration/package.json +2 -2
- package/dist/api-client.js.map +0 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/command-implementations/global-command.ts"],
|
|
4
|
-
"sourcesContent": ["import type { YargsConfig } from '@bpinternal/yargs-extra'\nimport chalk from 'chalk'\nimport fs from 'fs'\nimport latestVersion from 'latest-version'\nimport _ from 'lodash'\nimport * as pathlib from 'path'\nimport semver from 'semver'\nimport type { ApiClientFactory } from '../api
|
|
4
|
+
"sourcesContent": ["import type { YargsConfig } from '@bpinternal/yargs-extra'\nimport chalk from 'chalk'\nimport fs from 'fs'\nimport latestVersion from 'latest-version'\nimport _ from 'lodash'\nimport * as pathlib from 'path'\nimport semver from 'semver'\nimport type { ApiClientFactory } from '../api/client'\nimport type * as config from '../config'\nimport * as consts from '../consts'\nimport * as errors from '../errors'\nimport type { CommandArgv, CommandDefinition } from '../typings'\nimport * as utils from '../utils'\nimport { BaseCommand } from './base-command'\n\nexport type GlobalCommandDefinition = CommandDefinition<typeof config.schemas.global>\nexport type GlobalCache = { apiUrl: string; token: string; workspaceId: string }\n\nexport type ConfigurableGlobalPaths = { botpressHomeDir: string; cliRootDir: utils.path.AbsolutePath }\nexport type ConstantGlobalPaths = typeof consts.fromHomeDir & typeof consts.fromCliRootDir\nexport type AllGlobalPaths = ConfigurableGlobalPaths & ConstantGlobalPaths\n\nclass GlobalPaths extends utils.path.PathStore<keyof AllGlobalPaths> {\n public constructor(argv: CommandArgv<GlobalCommandDefinition>) {\n const absBotpressHome = utils.path.absoluteFrom(utils.path.cwd(), argv.botpressHome)\n super({\n cliRootDir: consts.cliRootDir,\n botpressHomeDir: absBotpressHome,\n ..._.mapValues(consts.fromHomeDir, (p) => utils.path.absoluteFrom(absBotpressHome, p)),\n ..._.mapValues(consts.fromCliRootDir, (p) => utils.path.absoluteFrom(consts.cliRootDir, p)),\n })\n }\n}\n\ntype PackageJson = { name: string; version: string }\n\nconst UPDATE_MSG = (props: PackageJson & { latest: string }) =>\n `${chalk.bold('Update available')} ${chalk.dim(props.version)} \u2192 ${chalk.green(props.latest)}\n\nTo update, run:\n for npm ${chalk.cyan(`npm i -g ${props.name}`)}\n for yarn ${chalk.cyan(`yarn global add ${props.name}`)}\n for pnpm ${chalk.cyan(`pnpm i -g ${props.name}`)}`\n\nexport abstract class GlobalCommand<C extends GlobalCommandDefinition> extends BaseCommand<C> {\n protected api: ApiClientFactory\n protected prompt: utils.prompt.CLIPrompt\n\n public constructor(\n api: ApiClientFactory,\n prompt: utils.prompt.CLIPrompt,\n ...args: ConstructorParameters<typeof BaseCommand<C>>\n ) {\n super(...args)\n this.api = api\n this.prompt = prompt\n }\n\n protected get globalPaths() {\n return new GlobalPaths(this.argv)\n }\n\n protected get globalCache() {\n return new utils.cache.FSKeyValueCache<GlobalCache>(this.globalPaths.abs.globalCacheFile)\n }\n\n protected override bootstrap = async () => {\n const pkgJson = await this._readPackageJson()\n\n const versionText = chalk.bold(`v${pkgJson.version}`)\n this.logger.log(`Botpress CLI ${versionText}`, { prefix: '\uD83E\uDD16' })\n\n await this._notifyUpdate(pkgJson)\n\n const paths = this.globalPaths\n if (paths.abs.botpressHomeDir !== consts.defaultBotpressHome) {\n this.logger.log(`Using custom botpress home: ${paths.abs.botpressHomeDir}`, { prefix: '\uD83C\uDFE0' })\n }\n }\n\n protected override teardown = async () => {\n this.logger.cleanup()\n }\n\n protected async ensureLoginAndCreateClient(credentials: YargsConfig<typeof config.schemas.credentials>) {\n const cache = this.globalCache\n const token = await cache.get('token')\n const workspaceId = credentials.workspaceId ?? (await cache.get('workspaceId'))\n const apiUrl = credentials.apiUrl ?? (await cache.get('apiUrl'))\n\n if (!(token && workspaceId && apiUrl)) {\n throw new errors.NotLoggedInError()\n }\n\n return this.api.newClient({ apiUrl, token, workspaceId }, this.logger)\n }\n\n private _notifyUpdate = async (pkgJson: PackageJson): Promise<void> => {\n try {\n const latest = await latestVersion(pkgJson.name)\n const isOutdated = semver.lt(pkgJson.version, latest)\n if (isOutdated) {\n this.logger.box(UPDATE_MSG({ ...pkgJson, latest }))\n }\n } catch (thrown) {\n const err = errors.BotpressCLIError.map(thrown)\n this.logger.debug(`Failed to check for updates: ${err.message}`)\n }\n }\n\n private _readPackageJson = async (): Promise<PackageJson> => {\n const path = pathlib.join(this.globalPaths.abs.cliRootDir, 'package.json')\n const strContent = await fs.promises.readFile(path, 'utf8')\n const jsonContent = JSON.parse(strContent)\n return jsonContent\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["_", "chalk", "latestVersion", "semver", "fs"]
|
|
7
7
|
}
|
|
@@ -27,7 +27,7 @@ __export(command_implementations_exports, {
|
|
|
27
27
|
default: () => command_implementations_default
|
|
28
28
|
});
|
|
29
29
|
module.exports = __toCommonJS(command_implementations_exports);
|
|
30
|
-
var
|
|
30
|
+
var import_client = require("../api/client");
|
|
31
31
|
var import_logger = require("../logger");
|
|
32
32
|
var utils = __toESM(require("../utils"));
|
|
33
33
|
var import_add_command = require("./add-command");
|
|
@@ -45,7 +45,7 @@ var import_serve_command = require("./serve-command");
|
|
|
45
45
|
const getHandler = (cls) => async (argv) => {
|
|
46
46
|
const logger = new import_logger.Logger(argv);
|
|
47
47
|
const prompt = new utils.prompt.CLIPrompt(argv, logger);
|
|
48
|
-
return new cls(
|
|
48
|
+
return new cls(import_client.ApiClient, prompt, logger, argv).handler();
|
|
49
49
|
};
|
|
50
50
|
var command_implementations_default = {
|
|
51
51
|
login: getHandler(import_login_command.LoginCommand),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/command-implementations/index.ts"],
|
|
4
|
-
"sourcesContent": ["import { ApiClient } from '../api
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import { ApiClient } from '../api/client'\nimport type commandDefinitions from '../command-definitions'\nimport type { ImplementationTree } from '../command-tree'\nimport { Logger } from '../logger'\nimport type { CommandArgv } from '../typings'\nimport * as utils from '../utils'\nimport { AddCommand } from './add-command'\nimport type { BaseCommand } from './base-command'\nimport * as bots from './bot-commands'\nimport { BuildCommand } from './build-command'\nimport { BundleCommand } from './bundle-command'\nimport { DeployCommand } from './deploy-command'\nimport { DevCommand } from './dev-command'\nimport { GenerateCommand } from './gen-command'\nimport type { GlobalCommand, GlobalCommandDefinition } from './global-command'\nimport { InitCommand } from './init-command'\nimport * as integrations from './integration-commands'\nimport { LoginCommand } from './login-command'\nimport { LogoutCommand } from './logout-command'\nimport { ServeCommand } from './serve-command'\n\ntype GlobalCtor<C extends GlobalCommandDefinition> = new (\n ...args: ConstructorParameters<typeof GlobalCommand<C>>\n) => BaseCommand<C>\n\nconst getHandler =\n <C extends GlobalCommandDefinition>(cls: GlobalCtor<C>) =>\n async (argv: CommandArgv<C>) => {\n const logger = new Logger(argv)\n const prompt = new utils.prompt.CLIPrompt(argv, logger)\n return new cls(ApiClient, prompt, logger, argv).handler()\n }\n\nexport default {\n login: getHandler(LoginCommand),\n logout: getHandler(LogoutCommand),\n bots: {\n subcommands: {\n create: getHandler(bots.CreateBotCommand),\n get: getHandler(bots.GetBotCommand),\n delete: getHandler(bots.DeleteBotCommand),\n list: getHandler(bots.ListBotsCommand),\n },\n },\n integrations: {\n subcommands: {\n get: getHandler(integrations.GetIntegrationCommand),\n list: getHandler(integrations.ListIntegrationsCommand),\n delete: getHandler(integrations.DeleteIntegrationCommand),\n },\n },\n init: getHandler(InitCommand),\n generate: getHandler(GenerateCommand),\n bundle: getHandler(BundleCommand),\n build: getHandler(BuildCommand),\n serve: getHandler(ServeCommand),\n deploy: getHandler(DeployCommand),\n add: getHandler(AddCommand),\n dev: getHandler(DevCommand),\n} satisfies ImplementationTree<typeof commandDefinitions>\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA0B;AAG1B,oBAAuB;AAEvB,YAAuB;AACvB,yBAA2B;AAE3B,WAAsB;AACtB,2BAA6B;AAC7B,4BAA8B;AAC9B,4BAA8B;AAC9B,yBAA2B;AAC3B,yBAAgC;AAEhC,0BAA4B;AAC5B,mBAA8B;AAC9B,2BAA6B;AAC7B,4BAA8B;AAC9B,2BAA6B;AAM7B,MAAM,aACJ,CAAoC,QACpC,OAAO,SAAyB;AAC9B,QAAM,SAAS,IAAI,qBAAO,IAAI;AAC9B,QAAM,SAAS,IAAI,MAAM,OAAO,UAAU,MAAM,MAAM;AACtD,SAAO,IAAI,IAAI,yBAAW,QAAQ,QAAQ,IAAI,EAAE,QAAQ;AAC1D;AAEF,IAAO,kCAAQ;AAAA,EACb,OAAO,WAAW,iCAAY;AAAA,EAC9B,QAAQ,WAAW,mCAAa;AAAA,EAChC,MAAM;AAAA,IACJ,aAAa;AAAA,MACX,QAAQ,WAAW,KAAK,gBAAgB;AAAA,MACxC,KAAK,WAAW,KAAK,aAAa;AAAA,MAClC,QAAQ,WAAW,KAAK,gBAAgB;AAAA,MACxC,MAAM,WAAW,KAAK,eAAe;AAAA,IACvC;AAAA,EACF;AAAA,EACA,cAAc;AAAA,IACZ,aAAa;AAAA,MACX,KAAK,WAAW,aAAa,qBAAqB;AAAA,MAClD,MAAM,WAAW,aAAa,uBAAuB;AAAA,MACrD,QAAQ,WAAW,aAAa,wBAAwB;AAAA,IAC1D;AAAA,EACF;AAAA,EACA,MAAM,WAAW,+BAAW;AAAA,EAC5B,UAAU,WAAW,kCAAe;AAAA,EACpC,QAAQ,WAAW,mCAAa;AAAA,EAChC,OAAO,WAAW,iCAAY;AAAA,EAC9B,OAAO,WAAW,iCAAY;AAAA,EAC9B,QAAQ,WAAW,mCAAa;AAAA,EAChC,KAAK,WAAW,6BAAU;AAAA,EAC1B,KAAK,WAAW,6BAAU;AAC5B;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -85,12 +85,6 @@ class ProjectCommand extends import_global_command.GlobalCommand {
|
|
|
85
85
|
await import_fs.default.promises.writeFile(filePath, file.content);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
prepareIntegrations(botImpl, botInfo) {
|
|
89
|
-
const { integrations: integrationList } = botImpl.definition;
|
|
90
|
-
const integrationsToUninstall = (0, import_lodash.default)(botInfo.integrations).keys().filter((key) => !integrationList?.map((i) => i.id).includes(key)).zipObject().mapValues(() => null).value();
|
|
91
|
-
const integrationsToInstall = (0, import_lodash.default)(integrationList ?? []).keyBy((i) => i.id).mapValues(({ enabled, configuration }) => ({ enabled, configuration })).value();
|
|
92
|
-
return { ...integrationsToUninstall, ...integrationsToInstall };
|
|
93
|
-
}
|
|
94
88
|
displayWebhookUrls(bot) {
|
|
95
89
|
if (!import_lodash.default.keys(bot.integrations).length) {
|
|
96
90
|
this.logger.debug("No integrations in bot");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/command-implementations/project-command.ts"],
|
|
4
|
-
"sourcesContent": ["import type * as bpclient from '@botpress/client'\nimport type
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAGA,mBAAkB;AAClB,gBAAe;AACf,oBAAc;AACd,kBAAoB;AACpB,cAAyB;AAEzB,aAAwB;AACxB,aAAwB;AAExB,YAAuB;AACvB,4BAA8B;
|
|
4
|
+
"sourcesContent": ["import type * as bpclient from '@botpress/client'\nimport type * as bpsdk from '@botpress/sdk'\nimport type { YargsConfig } from '@bpinternal/yargs-extra'\nimport chalk from 'chalk'\nimport fs from 'fs'\nimport _ from 'lodash'\nimport pathlib from 'path'\nimport * as codegen from '../code-generation'\nimport type * as config from '../config'\nimport * as consts from '../consts'\nimport * as errors from '../errors'\nimport type { CommandArgv, CommandDefinition } from '../typings'\nimport * as utils from '../utils'\nimport { GlobalCommand } from './global-command'\n\nexport type ProjectCommandDefinition = CommandDefinition<typeof config.schemas.project>\nexport type ProjectCache = { botId: string; devId: string }\n\ntype ConfigurableProjectPaths = { entryPoint: string; outDir: string; workDir: string }\ntype ConstantProjectPaths = typeof consts.fromOutDir & typeof consts.fromWorkDir\ntype AllProjectPaths = ConfigurableProjectPaths & ConstantProjectPaths\n\nclass ProjectPaths extends utils.path.PathStore<keyof AllProjectPaths> {\n public constructor(argv: CommandArgv<ProjectCommandDefinition>) {\n const absWorkDir = utils.path.absoluteFrom(utils.path.cwd(), argv.workDir)\n const absEntrypoint = utils.path.absoluteFrom(absWorkDir, argv.entryPoint)\n const absOutDir = utils.path.absoluteFrom(absWorkDir, argv.outDir)\n super({\n workDir: absWorkDir,\n entryPoint: absEntrypoint,\n outDir: absOutDir,\n ..._.mapValues(consts.fromOutDir, (p) => utils.path.absoluteFrom(absOutDir, p)),\n ..._.mapValues(consts.fromWorkDir, (p) => utils.path.absoluteFrom(absWorkDir, p)),\n })\n }\n}\n\nexport abstract class ProjectCommand<C extends ProjectCommandDefinition> extends GlobalCommand<C> {\n protected get projectPaths() {\n return new ProjectPaths(this.argv)\n }\n\n protected get projectCache() {\n return new utils.cache.FSKeyValueCache<ProjectCache>(this.projectPaths.abs.projectCacheFile)\n }\n\n protected async readIntegrationDefinitionFromFS(): Promise<bpsdk.IntegrationDefinition | undefined> {\n const abs = this.projectPaths.abs\n const rel = this.projectPaths.rel('workDir')\n\n if (!fs.existsSync(abs.definition)) {\n this.logger.debug(`Integration definition not found at ${rel.definition}`)\n return\n }\n\n const { outputFiles } = await utils.esbuild.buildEntrypoint({\n cwd: abs.workDir,\n outfile: '',\n entrypoint: rel.definition,\n write: false,\n })\n\n const artifact = outputFiles[0]\n if (!artifact) {\n throw new errors.BotpressCLIError('Could not read integration definition')\n }\n\n const { default: definition } = utils.require.requireJsCode<{ default: bpsdk.IntegrationDefinition }>(artifact.text)\n return definition\n }\n\n protected async writeGeneratedFilesToOutFolder(files: codegen.File[]) {\n for (const file of files) {\n const filePath = utils.path.absoluteFrom(this.projectPaths.abs.outDir, file.path)\n const dirPath = pathlib.dirname(filePath)\n await fs.promises.mkdir(dirPath, { recursive: true })\n await fs.promises.writeFile(filePath, file.content)\n }\n }\n\n protected displayWebhookUrls(bot: bpclient.Bot) {\n if (!_.keys(bot.integrations).length) {\n this.logger.debug('No integrations in bot')\n return\n }\n\n this.logger.log('Integrations:')\n for (const integration of Object.values(bot.integrations)) {\n if (!integration.enabled) {\n this.logger.log(`${chalk.grey(integration.name)} ${chalk.italic('(disabled)')}: ${integration.webhookUrl}`, {\n prefix: { symbol: '\u25CB', indent: 2 },\n })\n } else {\n this.logger.log(`${chalk.bold(integration.name)} : ${integration.webhookUrl}`, {\n prefix: { symbol: '\u25CF', indent: 2 },\n })\n }\n }\n }\n\n protected async promptSecrets(\n integrationDef: bpsdk.IntegrationDefinition,\n argv: YargsConfig<typeof config.schemas.secrets>\n ): Promise<Record<string, string>> {\n const { secrets: secretDefinitions } = integrationDef\n if (!secretDefinitions) {\n return {}\n }\n\n const secretArgv = this._parseArgvSecrets(argv.secrets)\n const invalidSecret = Object.keys(secretArgv).find((s) => !secretDefinitions.includes(s))\n if (invalidSecret) {\n throw new errors.BotpressCLIError(`Secret ${invalidSecret} is not defined in integration definition`)\n }\n\n const values: Record<string, string> = {}\n for (const secretDef of secretDefinitions) {\n const argvSecret = secretArgv[secretDef]\n if (argvSecret) {\n this.logger.debug(`Using secret \"${secretDef}\" from argv`)\n values[secretDef] = argvSecret\n continue\n }\n\n const prompted = await this.prompt.text(`Enter value for secret \"${secretDef}\"`)\n if (!prompted) {\n throw new errors.BotpressCLIError('Secret is required')\n }\n values[secretDef] = prompted\n }\n\n const envVariables = _.mapKeys(values, (_v, k) => codegen.secretEnvVariableName(k))\n return envVariables\n }\n\n private _parseArgvSecrets(argvSecrets: string[]): Record<string, string> {\n const parsed: Record<string, string> = {}\n for (const secret of argvSecrets) {\n const [key, value] = this._splitOnce(secret, '=')\n if (!value) {\n throw new errors.BotpressCLIError(\n `Secret \"${key}\" is missing a value. Expected format: \"SECRET_NAME=secretValue\"`\n )\n }\n parsed[key!] = value\n }\n\n return parsed\n }\n\n private _splitOnce = (text: string, separator: string): [string, string | undefined] => {\n const index = text.indexOf(separator)\n if (index === -1) {\n return [text, undefined]\n }\n return [text.slice(0, index), text.slice(index + 1)]\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["_", "fs", "pathlib", "chalk"]
|
|
7
7
|
}
|
|
@@ -18,10 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var record_utils_exports = {};
|
|
20
20
|
__export(record_utils_exports, {
|
|
21
|
-
|
|
21
|
+
setNullOnMissingValues: () => setNullOnMissingValues,
|
|
22
|
+
zipObjects: () => zipObjects
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(record_utils_exports);
|
|
24
|
-
const
|
|
25
|
+
const setNullOnMissingValues = (record = {}, oldRecord = {}) => {
|
|
25
26
|
const newRecord = {};
|
|
26
27
|
for (const [key, value] of Object.entries(record)) {
|
|
27
28
|
newRecord[key] = value;
|
|
@@ -33,8 +34,17 @@ const setOnNullMissingValues = (record = {}, oldRecord = {}) => {
|
|
|
33
34
|
}
|
|
34
35
|
return newRecord;
|
|
35
36
|
};
|
|
37
|
+
const zipObjects = (recordA, recordB) => {
|
|
38
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(recordA), ...Object.keys(recordB)]);
|
|
39
|
+
const newRecord = {};
|
|
40
|
+
for (const key of allKeys) {
|
|
41
|
+
newRecord[key] = [recordA[key] ?? null, recordB[key] ?? null];
|
|
42
|
+
}
|
|
43
|
+
return newRecord;
|
|
44
|
+
};
|
|
36
45
|
// Annotate the CommonJS export names for ESM import in node:
|
|
37
46
|
0 && (module.exports = {
|
|
38
|
-
|
|
47
|
+
setNullOnMissingValues,
|
|
48
|
+
zipObjects
|
|
39
49
|
});
|
|
40
50
|
//# sourceMappingURL=record-utils.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/record-utils.ts"],
|
|
4
|
-
"sourcesContent": ["export const
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,MAAM,yBAAyB,CACpC,SAA4B,CAAC,GAC7B,YAA+B,CAAC,MACH;AAC7B,QAAM,YAAsC,CAAC;AAE7C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,cAAU,OAAO;AAAA,EACnB;AAEA,aAAW,SAAS,OAAO,KAAK,SAAS,GAAG;AAC1C,QAAI,CAAC,OAAO,QAAQ;AAClB,gBAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
4
|
+
"sourcesContent": ["export const setNullOnMissingValues = <A, B>(\n record: Record<string, A> = {},\n oldRecord: Record<string, B> = {}\n): Record<string, A | null> => {\n const newRecord: Record<string, A | null> = {}\n\n for (const [key, value] of Object.entries(record)) {\n newRecord[key] = value\n }\n\n for (const value of Object.keys(oldRecord)) {\n if (!record[value]) {\n newRecord[value] = null\n }\n }\n\n return newRecord\n}\n\nexport const zipObjects = <A, B>(\n recordA: Record<string, A>,\n recordB: Record<string, B>\n): Record<string, [A | null, B | null]> => {\n const allKeys = new Set([...Object.keys(recordA), ...Object.keys(recordB)])\n const newRecord: Record<string, [A | null, B | null]> = {}\n\n for (const key of allKeys) {\n newRecord[key] = [recordA[key] ?? null, recordB[key] ?? null]\n }\n\n return newRecord\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,MAAM,yBAAyB,CACpC,SAA4B,CAAC,GAC7B,YAA+B,CAAC,MACH;AAC7B,QAAM,YAAsC,CAAC;AAE7C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,cAAU,OAAO;AAAA,EACnB;AAEA,aAAW,SAAS,OAAO,KAAK,SAAS,GAAG;AAC1C,QAAI,CAAC,OAAO,QAAQ;AAClB,gBAAU,SAAS;AAAA,IACrB;AAAA,EACF;AAEA,SAAO;AACT;AAEO,MAAM,aAAa,CACxB,SACA,YACyC;AACzC,QAAM,UAAU,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,OAAO,GAAG,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC;AAC1E,QAAM,YAAkD,CAAC;AAEzD,aAAW,OAAO,SAAS;AACzB,cAAU,OAAO,CAAC,QAAQ,QAAQ,MAAM,QAAQ,QAAQ,IAAI;AAAA,EAC9D;AAEA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (let key of __getOwnPropNames(from))
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
+
}
|
|
14
|
+
return to;
|
|
15
|
+
};
|
|
16
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
18
|
+
mod
|
|
19
|
+
));
|
|
20
|
+
var import_vitest = require("vitest");
|
|
21
|
+
var recordUtils = __toESM(require("./record-utils"));
|
|
22
|
+
(0, import_vitest.test)("zip objects should return both values only when key is defined in both objects", () => {
|
|
23
|
+
const objA = { a: 1, b: 2, c: 3 };
|
|
24
|
+
const objB = { c: 4, d: 5 };
|
|
25
|
+
const zipped = recordUtils.zipObjects(objA, objB);
|
|
26
|
+
const expected = {
|
|
27
|
+
a: [1, null],
|
|
28
|
+
b: [2, null],
|
|
29
|
+
c: [3, 4],
|
|
30
|
+
d: [null, 5]
|
|
31
|
+
};
|
|
32
|
+
(0, import_vitest.expect)(zipped).toEqual(expected);
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=record-utils.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/record-utils.test.ts"],
|
|
4
|
+
"sourcesContent": ["import { test, expect } from 'vitest'\nimport * as recordUtils from './record-utils'\n\ntest('zip objects should return both values only when key is defined in both objects', () => {\n const objA = { a: 1, b: 2, c: 3 }\n const objB = { c: 4, d: 5 }\n\n const zipped = recordUtils.zipObjects(objA, objB)\n\n const expected = {\n a: [1, null],\n b: [2, null],\n c: [3, 4],\n d: [null, 5],\n }\n\n expect(zipped).toEqual(expected)\n})\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;AAAA,oBAA6B;AAC7B,kBAA6B;AAAA,IAE7B,oBAAK,kFAAkF,MAAM;AAC3F,QAAM,OAAO,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAChC,QAAM,OAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AAE1B,QAAM,SAAS,YAAY,WAAW,MAAM,IAAI;AAEhD,QAAM,WAAW;AAAA,IACf,GAAG,CAAC,GAAG,IAAI;AAAA,IACX,GAAG,CAAC,GAAG,IAAI;AAAA,IACX,GAAG,CAAC,GAAG,CAAC;AAAA,IACR,GAAG,CAAC,MAAM,CAAC;AAAA,EACb;AAEA,4BAAO,MAAM,EAAE,QAAQ,QAAQ;AACjC,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/e2e/api.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Client } from '@botpress/client'
|
|
2
|
+
|
|
3
|
+
export type ApiBot = Awaited<ReturnType<Client['listBots']>>['bots'][0]
|
|
4
|
+
export const fetchAllBots = async (client: Client): Promise<ApiBot[]> => {
|
|
5
|
+
let allBots: ApiBot[] = []
|
|
6
|
+
let nextToken: string | undefined = undefined
|
|
7
|
+
do {
|
|
8
|
+
const { bots, meta } = await client.listBots({ nextToken })
|
|
9
|
+
allBots = allBots.concat(bots)
|
|
10
|
+
nextToken = meta.nextToken
|
|
11
|
+
} while (nextToken)
|
|
12
|
+
return allBots
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ApiIntegration = Awaited<ReturnType<Client['listIntegrations']>>['integrations'][0]
|
|
16
|
+
export const fetchAllIntegrations = async (client: Client): Promise<ApiIntegration[]> => {
|
|
17
|
+
let allIntegrations: ApiIntegration[] = []
|
|
18
|
+
let nextToken: string | undefined = undefined
|
|
19
|
+
do {
|
|
20
|
+
const { integrations, meta } = await client.listIntegrations({ nextToken })
|
|
21
|
+
allIntegrations = allIntegrations.concat(integrations)
|
|
22
|
+
nextToken = meta.nextToken
|
|
23
|
+
} while (nextToken)
|
|
24
|
+
return allIntegrations
|
|
25
|
+
}
|
package/e2e/defaults.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as consts from '../src/consts'
|
|
2
|
+
|
|
3
|
+
const noBuild = false
|
|
4
|
+
const secrets = [] satisfies string[]
|
|
5
|
+
const sourceMap = false
|
|
6
|
+
const verbose = false
|
|
7
|
+
const confirm = true
|
|
8
|
+
const json = false
|
|
9
|
+
const entryPoint = consts.defaultEntrypoint
|
|
10
|
+
const outDir = consts.defaultOutputFolder
|
|
11
|
+
export default {
|
|
12
|
+
noBuild,
|
|
13
|
+
secrets,
|
|
14
|
+
sourceMap,
|
|
15
|
+
verbose,
|
|
16
|
+
confirm,
|
|
17
|
+
json,
|
|
18
|
+
entryPoint,
|
|
19
|
+
outDir,
|
|
20
|
+
}
|
package/e2e/index.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Logger } from '@bpinternal/log4bot'
|
|
2
|
+
import yargs, { YargsConfig, YargsSchema } from '@bpinternal/yargs-extra'
|
|
3
|
+
import * as consts from '../src/consts'
|
|
4
|
+
import { createDeployBot } from './tests/create-deploy-bot'
|
|
5
|
+
import { createDeployIntegration } from './tests/create-deploy-integration'
|
|
6
|
+
import { devBot } from './tests/dev-bot'
|
|
7
|
+
import { Test } from './typings'
|
|
8
|
+
import { sleep, TmpDirectory } from './utils'
|
|
9
|
+
|
|
10
|
+
const tests: Test[] = [createDeployBot, createDeployIntegration, devBot]
|
|
11
|
+
|
|
12
|
+
const timeout = (ms: number) =>
|
|
13
|
+
sleep(ms).then(() => {
|
|
14
|
+
throw new Error(`Timeout after ${ms}ms`)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const TIMEOUT = 20_000
|
|
18
|
+
|
|
19
|
+
const configSchema = {
|
|
20
|
+
timeout: {
|
|
21
|
+
type: 'number',
|
|
22
|
+
default: TIMEOUT,
|
|
23
|
+
},
|
|
24
|
+
verbose: {
|
|
25
|
+
type: 'boolean',
|
|
26
|
+
default: false,
|
|
27
|
+
alias: 'v',
|
|
28
|
+
},
|
|
29
|
+
filter: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
},
|
|
32
|
+
workspaceId: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
demandOption: true,
|
|
35
|
+
},
|
|
36
|
+
token: {
|
|
37
|
+
type: 'string',
|
|
38
|
+
demandOption: true,
|
|
39
|
+
},
|
|
40
|
+
apiUrl: {
|
|
41
|
+
type: 'string',
|
|
42
|
+
default: consts.defaultBotpressApiUrl,
|
|
43
|
+
},
|
|
44
|
+
tunnelUrl: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
default: consts.defaultTunnelUrl,
|
|
47
|
+
},
|
|
48
|
+
} satisfies YargsSchema
|
|
49
|
+
|
|
50
|
+
const main = async (argv: YargsConfig<typeof configSchema>): Promise<never> => {
|
|
51
|
+
const logger = new Logger('e2e', { level: argv.verbose ? 'debug' : 'info' })
|
|
52
|
+
|
|
53
|
+
const filterRegex = argv.filter ? new RegExp(argv.filter) : null
|
|
54
|
+
const filteredTests = tests.filter(({ name }) => (filterRegex ? filterRegex.test(name) : true))
|
|
55
|
+
logger.info(`Running ${filteredTests.length} / ${tests.length} tests`)
|
|
56
|
+
|
|
57
|
+
for (const { name, handler } of filteredTests) {
|
|
58
|
+
const logLine = `### Running test: "${name}" ###`
|
|
59
|
+
const logPad = '#'.repeat(logLine.length)
|
|
60
|
+
logger.info(logPad)
|
|
61
|
+
logger.info(logLine)
|
|
62
|
+
logger.info(logPad + '\n')
|
|
63
|
+
|
|
64
|
+
const tmpDir = TmpDirectory.create()
|
|
65
|
+
try {
|
|
66
|
+
const t0 = Date.now()
|
|
67
|
+
await Promise.race([handler({ tmpDir: tmpDir.path, ...argv }), timeout(argv.timeout)])
|
|
68
|
+
const t1 = Date.now()
|
|
69
|
+
logger.info(`SUCCESS: "${name}" (${t1 - t0}ms)`)
|
|
70
|
+
} catch (thrown) {
|
|
71
|
+
const err = thrown instanceof Error ? thrown : new Error(`${thrown}`)
|
|
72
|
+
logger.attachError(err).error(`FAILURE: "${name}"`)
|
|
73
|
+
process.exit(1)
|
|
74
|
+
} finally {
|
|
75
|
+
tmpDir.cleanup()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
logger.info('All tests passed')
|
|
80
|
+
process.exit(0)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void yargs.command('$0', 'Run E2E Tests', configSchema, main).parse()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Client } from '@botpress/client'
|
|
2
|
+
import pathlib from 'path'
|
|
3
|
+
import * as uuid from 'uuid'
|
|
4
|
+
import impl from '../../src/command-implementations'
|
|
5
|
+
import { ApiBot, fetchAllBots } from '../api'
|
|
6
|
+
import defaults from '../defaults'
|
|
7
|
+
import { Test } from '../typings'
|
|
8
|
+
import * as utils from '../utils'
|
|
9
|
+
|
|
10
|
+
const fetchBot = async (client: Client, botName: string): Promise<ApiBot | undefined> => {
|
|
11
|
+
const bots = await fetchAllBots(client)
|
|
12
|
+
return bots.find(({ name }) => name === botName)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const createDeployBot: Test = {
|
|
16
|
+
name: 'cli should allow creating, building, deploying and mannaging a bot',
|
|
17
|
+
handler: async ({ tmpDir, ...creds }) => {
|
|
18
|
+
const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
|
|
19
|
+
const baseDir = pathlib.join(tmpDir, 'bots')
|
|
20
|
+
const botName = uuid.v4()
|
|
21
|
+
const botDir = pathlib.join(baseDir, botName)
|
|
22
|
+
|
|
23
|
+
const argv = {
|
|
24
|
+
...defaults,
|
|
25
|
+
botpressHome: botpressHomeDir,
|
|
26
|
+
confirm: true,
|
|
27
|
+
...creds,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const client = new Client({ host: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId })
|
|
31
|
+
|
|
32
|
+
await impl.init({ ...argv, workDir: baseDir, name: botName, type: 'bot' }).then(utils.handleExitCode)
|
|
33
|
+
await utils.npmInstall({ workDir: botDir }).then(utils.handleExitCode)
|
|
34
|
+
await impl.build({ ...argv, workDir: botDir }).then(utils.handleExitCode)
|
|
35
|
+
await impl.login({ ...argv }).then(utils.handleExitCode)
|
|
36
|
+
await impl.bots.subcommands.create({ ...argv, name: botName }).then(utils.handleExitCode)
|
|
37
|
+
|
|
38
|
+
const bot = await fetchBot(client, botName)
|
|
39
|
+
if (!bot) {
|
|
40
|
+
throw new Error(`Bot ${botName} should have been created`)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await impl.deploy({ ...argv, workDir: botDir, createNewBot: false, botId: bot.id }).then(utils.handleExitCode)
|
|
44
|
+
await impl.bots.subcommands.delete({ ...argv, botRef: bot.id }).then(utils.handleExitCode)
|
|
45
|
+
|
|
46
|
+
if (await fetchBot(client, botName)) {
|
|
47
|
+
throw new Error(`Bot ${botName} should have been deleted`)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Client } from '@botpress/client'
|
|
2
|
+
|
|
3
|
+
import pathlib from 'path'
|
|
4
|
+
import * as uuid from 'uuid'
|
|
5
|
+
import impl from '../../src/command-implementations'
|
|
6
|
+
import { ApiIntegration, fetchAllIntegrations } from '../api'
|
|
7
|
+
import defaults from '../defaults'
|
|
8
|
+
import { Test } from '../typings'
|
|
9
|
+
import * as utils from '../utils'
|
|
10
|
+
|
|
11
|
+
const fetchIntegration = async (client: Client, integrationName: string): Promise<ApiIntegration | undefined> => {
|
|
12
|
+
const integrations = await fetchAllIntegrations(client)
|
|
13
|
+
return integrations.find(({ name }) => name === integrationName)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const createDeployIntegration: Test = {
|
|
17
|
+
name: 'cli should allow creating, building, deploying and mannaging an integration',
|
|
18
|
+
handler: async ({ tmpDir, ...creds }) => {
|
|
19
|
+
const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
|
|
20
|
+
const baseDir = pathlib.join(tmpDir, 'integrations')
|
|
21
|
+
const integrationName = `myintegration-${uuid.v4()}`.replace(/-/g, '')
|
|
22
|
+
const integrationDir = pathlib.join(baseDir, integrationName)
|
|
23
|
+
|
|
24
|
+
const argv = {
|
|
25
|
+
...defaults,
|
|
26
|
+
botpressHome: botpressHomeDir,
|
|
27
|
+
confirm: true,
|
|
28
|
+
...creds,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const client = new Client({ host: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId })
|
|
32
|
+
|
|
33
|
+
await impl
|
|
34
|
+
.init({ ...argv, workDir: baseDir, name: integrationName, type: 'integration' })
|
|
35
|
+
.then(utils.handleExitCode)
|
|
36
|
+
await utils.npmInstall({ workDir: integrationDir }).then(utils.handleExitCode)
|
|
37
|
+
await impl.build({ ...argv, workDir: integrationDir }).then(utils.handleExitCode)
|
|
38
|
+
await impl.login({ ...argv }).then(utils.handleExitCode)
|
|
39
|
+
|
|
40
|
+
await impl
|
|
41
|
+
.deploy({ ...argv, createNewBot: undefined, botId: undefined, workDir: integrationDir })
|
|
42
|
+
.then(utils.handleExitCode)
|
|
43
|
+
|
|
44
|
+
const integration = await fetchIntegration(client, integrationName)
|
|
45
|
+
if (!integration) {
|
|
46
|
+
throw new Error(`Integration ${integrationName} should have been created`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await impl.integrations.subcommands.delete({ ...argv, integrationRef: integration.id }).then(utils.handleExitCode)
|
|
50
|
+
|
|
51
|
+
if (await fetchIntegration(client, integrationName)) {
|
|
52
|
+
throw new Error(`Integration ${integrationName} should have been deleted`)
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import findProcess from 'find-process'
|
|
2
|
+
import pathlib from 'path'
|
|
3
|
+
import * as uuid from 'uuid'
|
|
4
|
+
import impl from '../../src/command-implementations'
|
|
5
|
+
import defaults from '../defaults'
|
|
6
|
+
import { Test } from '../typings'
|
|
7
|
+
import * as utils from '../utils'
|
|
8
|
+
|
|
9
|
+
const handleExitCode = ({ exitCode }: { exitCode: number }) => {
|
|
10
|
+
if (exitCode !== 0) {
|
|
11
|
+
throw new Error(`Command exited with code ${exitCode}`)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const PORT = 8075
|
|
16
|
+
|
|
17
|
+
export const devBot: Test = {
|
|
18
|
+
name: 'cli should allow creating and running a bot locally',
|
|
19
|
+
handler: async ({ tmpDir, tunnelUrl, ...creds }) => {
|
|
20
|
+
const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
|
|
21
|
+
const baseDir = pathlib.join(tmpDir, 'bots')
|
|
22
|
+
const botName = uuid.v4()
|
|
23
|
+
const botDir = pathlib.join(baseDir, botName)
|
|
24
|
+
|
|
25
|
+
const argv = {
|
|
26
|
+
...defaults,
|
|
27
|
+
botpressHome: botpressHomeDir,
|
|
28
|
+
confirm: true,
|
|
29
|
+
...creds,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await impl.init({ ...argv, workDir: baseDir, name: botName, type: 'bot' }).then(handleExitCode)
|
|
33
|
+
await utils.npmInstall({ workDir: botDir }).then(handleExitCode)
|
|
34
|
+
await impl.login({ ...argv }).then(handleExitCode)
|
|
35
|
+
|
|
36
|
+
const cmdPromise = impl.dev({ ...argv, workDir: botDir, port: PORT, tunnelUrl }).then(handleExitCode)
|
|
37
|
+
await utils.sleep(5000)
|
|
38
|
+
|
|
39
|
+
const allProcess = await findProcess('port', PORT)
|
|
40
|
+
if (allProcess.length === 0) {
|
|
41
|
+
throw new Error(`Expected to find a process listening on port ${PORT}`)
|
|
42
|
+
}
|
|
43
|
+
if (allProcess.length > 1) {
|
|
44
|
+
throw new Error(`Expected to find only one process listening on port ${PORT}`)
|
|
45
|
+
}
|
|
46
|
+
const botProcess = allProcess[0]
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* TODO:
|
|
50
|
+
* - try calling the Bot locally to see if it works
|
|
51
|
+
* - allow listing dev bots in API and find the one we just created (by name)
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
process.kill(botProcess.pid)
|
|
55
|
+
await cmdPromise
|
|
56
|
+
},
|
|
57
|
+
}
|
package/e2e/typings.ts
ADDED
package/e2e/utils.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import childprocess from 'child_process'
|
|
2
|
+
import tmp from 'tmp'
|
|
3
|
+
|
|
4
|
+
export const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
5
|
+
|
|
6
|
+
export class TmpDirectory {
|
|
7
|
+
private _closed = false
|
|
8
|
+
|
|
9
|
+
public static create() {
|
|
10
|
+
return new TmpDirectory(tmp.dirSync({ unsafeCleanup: true }))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private constructor(private _res: tmp.DirResult) {}
|
|
14
|
+
|
|
15
|
+
public get path() {
|
|
16
|
+
if (this._closed) {
|
|
17
|
+
throw new Error('Cannot access tmp directory after cleanup')
|
|
18
|
+
}
|
|
19
|
+
return this._res.name
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public cleanup() {
|
|
23
|
+
if (this._closed) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
this._res.removeCallback()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const npmInstall = async ({ workDir }: { workDir: string }) => {
|
|
31
|
+
const { status } = childprocess.spawnSync('pnpm', ['install'], {
|
|
32
|
+
cwd: workDir,
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
})
|
|
35
|
+
return { exitCode: status ?? 0 }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const handleExitCode = ({ exitCode }: { exitCode: number }) => {
|
|
39
|
+
if (exitCode !== 0) {
|
|
40
|
+
throw new Error(`Command exited with code ${exitCode}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botpress/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Botpress CLI",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "pnpm run bundle && pnpm run template:gen",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"start": "node dist/index.js",
|
|
9
9
|
"type:check": "tsc --noEmit",
|
|
10
10
|
"bundle": "ts-node -T build.ts",
|
|
11
|
-
"template:gen": "pnpm -r --stream -F echo-bot -F empty-integration exec bp gen"
|
|
11
|
+
"template:gen": "pnpm -r --stream -F echo-bot -F empty-integration exec bp gen",
|
|
12
|
+
"e2e:test": "ts-node -T ./e2e",
|
|
13
|
+
"unit:test": "pnpm vitest --run"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [],
|
|
14
16
|
"author": "",
|
|
@@ -18,8 +20,8 @@
|
|
|
18
20
|
},
|
|
19
21
|
"main": "dist/index.js",
|
|
20
22
|
"dependencies": {
|
|
21
|
-
"@botpress/client": "0.
|
|
22
|
-
"@bpinternal/tunnel": "^0.0
|
|
23
|
+
"@botpress/client": "0.2.0",
|
|
24
|
+
"@bpinternal/tunnel": "^0.1.0",
|
|
23
25
|
"@bpinternal/yargs-extra": "^0.0.3",
|
|
24
26
|
"@parcel/watcher": "^2.1.0",
|
|
25
27
|
"@types/lodash": "^4.14.191",
|
|
@@ -45,12 +47,16 @@
|
|
|
45
47
|
"zod-to-json-schema": "^3.20.1"
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
|
-
"@botpress/sdk": "0.1.
|
|
50
|
+
"@botpress/sdk": "0.1.6",
|
|
51
|
+
"@bpinternal/log4bot": "^0.0.4",
|
|
49
52
|
"@types/bluebird": "^3.5.38",
|
|
50
53
|
"@types/prompts": "^2.0.14",
|
|
51
54
|
"@types/semver": "^7.3.11",
|
|
55
|
+
"@types/tmp": "^0.2.3",
|
|
52
56
|
"@types/uuid": "^9.0.1",
|
|
57
|
+
"find-process": "^1.4.7",
|
|
53
58
|
"glob": "^9.3.4",
|
|
59
|
+
"tmp": "^0.2.1",
|
|
54
60
|
"ts-node": "^10.9.1"
|
|
55
61
|
},
|
|
56
62
|
"engines": {
|