@botpress/cli 2.0.4 → 2.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botpress/cli",
3
- "version": "2.0.4",
3
+ "version": "2.1.1",
4
4
  "description": "Botpress CLI",
5
5
  "scripts": {
6
6
  "build": "pnpm run bundle && pnpm run template:gen",
@@ -21,8 +21,9 @@
21
21
  "main": "dist/index.js",
22
22
  "dependencies": {
23
23
  "@apidevtools/json-schema-ref-parser": "^11.7.0",
24
- "@botpress/client": "0.37.1",
25
- "@botpress/sdk": "2.0.3",
24
+ "@botpress/chat": "0.4.4",
25
+ "@botpress/client": "0.38.0",
26
+ "@botpress/sdk": "2.0.4",
26
27
  "@bpinternal/const": "^0.0.20",
27
28
  "@bpinternal/tunnel": "^0.1.1",
28
29
  "@bpinternal/yargs-extra": "^0.0.3",
@@ -5,8 +5,8 @@
5
5
  },
6
6
  "private": true,
7
7
  "dependencies": {
8
- "@botpress/client": "0.37.1",
9
- "@botpress/sdk": "2.0.3"
8
+ "@botpress/client": "0.38.0",
9
+ "@botpress/sdk": "2.0.4"
10
10
  },
11
11
  "devDependencies": {
12
12
  "@types/node": "^18.19.67",
@@ -6,8 +6,8 @@
6
6
  },
7
7
  "private": true,
8
8
  "dependencies": {
9
- "@botpress/client": "0.37.1",
10
- "@botpress/sdk": "2.0.3"
9
+ "@botpress/client": "0.38.0",
10
+ "@botpress/sdk": "2.0.4"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@types/node": "^18.19.67",
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "private": true,
8
8
  "dependencies": {
9
- "@botpress/sdk": "2.0.3"
9
+ "@botpress/sdk": "2.0.4"
10
10
  },
11
11
  "devDependencies": {
12
12
  "@types/node": "^18.19.67",
@@ -6,8 +6,8 @@
6
6
  },
7
7
  "private": true,
8
8
  "dependencies": {
9
- "@botpress/client": "0.37.1",
10
- "@botpress/sdk": "2.0.3"
9
+ "@botpress/client": "0.38.0",
10
+ "@botpress/sdk": "2.0.4"
11
11
  },
12
12
  "devDependencies": {
13
13
  "@types/node": "^18.19.67",
@@ -6,8 +6,8 @@
6
6
  },
7
7
  "private": true,
8
8
  "dependencies": {
9
- "@botpress/client": "0.37.1",
10
- "@botpress/sdk": "2.0.3",
9
+ "@botpress/client": "0.38.0",
10
+ "@botpress/sdk": "2.0.4",
11
11
  "axios": "^1.6.8"
12
12
  },
13
13
  "devDependencies": {
package/e2e/api.ts DELETED
@@ -1,25 +0,0 @@
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
7
- do {
8
- const { bots, meta } = await client.listBots({ nextToken })
9
- allBots = [...allBots, ...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
19
- do {
20
- const { integrations, meta } = await client.listIntegrations({ nextToken })
21
- allIntegrations = [...allIntegrations, ...integrations]
22
- nextToken = meta.nextToken
23
- } while (nextToken)
24
- return allIntegrations
25
- }
package/e2e/defaults.ts DELETED
@@ -1,20 +0,0 @@
1
- const noBuild = false
2
- const secrets = [] satisfies string[]
3
- const sourceMap = false
4
- const verbose = false
5
- const confirm = true
6
- const json = false
7
- const allowDeprecated = false
8
- const isPublic = false
9
- const minify = true
10
- export default {
11
- minify,
12
- noBuild,
13
- secrets,
14
- sourceMap,
15
- verbose,
16
- confirm,
17
- json,
18
- allowDeprecated,
19
- public: isPublic,
20
- }
package/e2e/index.ts DELETED
@@ -1,118 +0,0 @@
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 { installAllInterfaces } from './tests/install-interfaces'
8
- import { addIntegration } from './tests/install-package'
9
- import { requiredSecrets } from './tests/integration-secrets'
10
- import { prependWorkspaceHandle, enforceWorkspaceHandle } from './tests/manage-workspace-handle'
11
- import { Test } from './typings'
12
- import { sleep, TmpDirectory } from './utils'
13
-
14
- const tests: Test[] = [
15
- createDeployBot,
16
- createDeployIntegration,
17
- devBot,
18
- requiredSecrets,
19
- prependWorkspaceHandle,
20
- enforceWorkspaceHandle,
21
- addIntegration,
22
- installAllInterfaces,
23
- ]
24
-
25
- const timeout = (ms: number) =>
26
- sleep(ms).then(() => {
27
- throw new Error(`Timeout after ${ms}ms`)
28
- })
29
-
30
- const TIMEOUT = 45_000
31
-
32
- const configSchema = {
33
- timeout: {
34
- type: 'number',
35
- default: TIMEOUT,
36
- },
37
- verbose: {
38
- type: 'boolean',
39
- default: false,
40
- alias: 'v',
41
- },
42
- filter: {
43
- type: 'string',
44
- },
45
- workspaceId: {
46
- type: 'string',
47
- demandOption: true,
48
- },
49
- workspaceHandle: {
50
- type: 'string',
51
- demandOption: true,
52
- },
53
- token: {
54
- type: 'string',
55
- demandOption: true,
56
- },
57
- apiUrl: {
58
- type: 'string',
59
- default: consts.defaultBotpressApiUrl,
60
- },
61
- tunnelUrl: {
62
- type: 'string',
63
- default: consts.defaultTunnelUrl,
64
- },
65
- sdkPath: {
66
- type: 'string',
67
- description: 'Path to the Botpress SDK to install; Allows using a version not released on NPM yet.',
68
- },
69
- clientPath: {
70
- type: 'string',
71
- description: 'Path to the Botpress Client to install; Allows using a version not released on NPM yet.',
72
- },
73
- } satisfies YargsSchema
74
-
75
- const main = async (argv: YargsConfig<typeof configSchema>): Promise<never> => {
76
- const logger = new Logger('e2e', { level: argv.verbose ? 'debug' : 'info' })
77
-
78
- const filterRegex = argv.filter ? new RegExp(argv.filter) : null
79
- const filteredTests = tests.filter(({ name }) => (filterRegex ? filterRegex.test(name) : true))
80
- logger.info(`Running ${filteredTests.length} / ${tests.length} tests`)
81
-
82
- const dependencies = { '@botpress/sdk': argv.sdkPath, '@botpress/client': argv.clientPath } satisfies Record<
83
- string,
84
- string | undefined
85
- >
86
-
87
- for (const { name, handler } of filteredTests) {
88
- const logLine = `### Running test: "${name}" ###`
89
- const logPad = '#'.repeat(logLine.length)
90
- logger.info(logPad)
91
- logger.info(logLine)
92
- logger.info(logPad + '\n')
93
-
94
- const loggerNamespace = name.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '')
95
-
96
- const tmpDir = TmpDirectory.create()
97
- try {
98
- const t0 = Date.now()
99
- await Promise.race([
100
- handler({ tmpDir: tmpDir.path, dependencies, logger: logger.sub(loggerNamespace), ...argv }),
101
- timeout(argv.timeout),
102
- ])
103
- const t1 = Date.now()
104
- logger.info(`SUCCESS: "${name}" (${t1 - t0}ms)`)
105
- } catch (thrown) {
106
- const err = thrown instanceof Error ? thrown : new Error(`${thrown}`)
107
- logger.attachError(err).error(`FAILURE: "${name}"`)
108
- process.exit(1)
109
- } finally {
110
- tmpDir.cleanup()
111
- }
112
- }
113
-
114
- logger.info('All tests passed')
115
- process.exit(0)
116
- }
117
-
118
- void yargs.command('$0', 'Run E2E Tests', configSchema, main).parse()
@@ -1,51 +0,0 @@
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, dependencies, ...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({ apiUrl: 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.fixBotpressDependencies({ workDir: botDir, target: dependencies })
34
- await utils.npmInstall({ workDir: botDir }).then(utils.handleExitCode)
35
- await impl.build({ ...argv, workDir: botDir }).then(utils.handleExitCode)
36
- await impl.login({ ...argv }).then(utils.handleExitCode)
37
- await impl.bots.subcommands.create({ ...argv, name: botName, ifNotExists: false }).then(utils.handleExitCode)
38
-
39
- const bot = await fetchBot(client, botName)
40
- if (!bot) {
41
- throw new Error(`Bot ${botName} should have been created`)
42
- }
43
-
44
- await impl.deploy({ ...argv, workDir: botDir, createNewBot: false, botId: bot.id }).then(utils.handleExitCode)
45
- await impl.bots.subcommands.delete({ ...argv, botRef: bot.id }).then(utils.handleExitCode)
46
-
47
- if (await fetchBot(client, botName)) {
48
- throw new Error(`Bot ${botName} should have been deleted`)
49
- }
50
- },
51
- }
@@ -1,62 +0,0 @@
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, dependencies, workspaceHandle, logger, ...creds }) => {
19
- const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
20
- const baseDir = pathlib.join(tmpDir, 'integrations')
21
-
22
- const integrationSuffix = uuid.v4().replace(/-/g, '')
23
- const name = `myintegration${integrationSuffix}`
24
- const integrationName = `${workspaceHandle}/${name}`
25
- const integrationDirName = `${workspaceHandle}-${name}`
26
- const integrationDir = pathlib.join(baseDir, integrationDirName)
27
-
28
- const argv = {
29
- ...defaults,
30
- botpressHome: botpressHomeDir,
31
- confirm: true,
32
- ...creds,
33
- }
34
-
35
- const client = new Client({ apiUrl: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId })
36
-
37
- await impl
38
- .init({ ...argv, workDir: baseDir, name: integrationName, type: 'integration' })
39
- .then(utils.handleExitCode)
40
- await utils.fixBotpressDependencies({ workDir: integrationDir, target: dependencies })
41
- await utils.npmInstall({ workDir: integrationDir }).then(utils.handleExitCode)
42
- await impl.build({ ...argv, workDir: integrationDir }).then(utils.handleExitCode)
43
- await impl.login({ ...argv }).then(utils.handleExitCode)
44
-
45
- await impl
46
- .deploy({ ...argv, createNewBot: undefined, botId: undefined, workDir: integrationDir })
47
- .then(utils.handleExitCode)
48
-
49
- logger.debug(`Fetching integration "${integrationName}"`)
50
- const integration = await fetchIntegration(client, integrationName)
51
- if (!integration) {
52
- throw new Error(`Integration ${integrationName} should have been created`)
53
- }
54
-
55
- logger.debug(`Deleting integration "${integrationName}"`)
56
- await impl.integrations.subcommands.delete({ ...argv, integrationRef: integration.id }).then(utils.handleExitCode)
57
-
58
- if (await fetchIntegration(client, integrationName)) {
59
- throw new Error(`Integration ${integrationName} should have been deleted`)
60
- }
61
- },
62
- }
@@ -1,59 +0,0 @@
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, dependencies, ...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.fixBotpressDependencies({ workDir: botDir, target: dependencies })
34
- await utils.npmInstall({ workDir: botDir }).then(handleExitCode)
35
- await impl.login({ ...argv }).then(handleExitCode)
36
-
37
- const cmdPromise = impl.dev({ ...argv, workDir: botDir, port: PORT, tunnelUrl }).then(handleExitCode)
38
- await utils.sleep(5000)
39
-
40
- const allProcess = await findProcess('port', PORT)
41
-
42
- const [botProcess] = allProcess
43
- if (allProcess.length > 1) {
44
- throw new Error(`Expected to find only one process listening on port ${PORT}`)
45
- }
46
- if (!botProcess) {
47
- throw new Error(`Expected to find a process listening on port ${PORT}`)
48
- }
49
-
50
- /**
51
- * TODO:
52
- * - try calling the Bot locally to see if it works
53
- * - allow listing dev bots in API and find the one we just created (by name)
54
- */
55
-
56
- process.kill(botProcess.pid)
57
- await cmdPromise
58
- },
59
- }
@@ -1,49 +0,0 @@
1
- import pathlib from 'path'
2
- import impl from '../../src/command-implementations'
3
- import defaults from '../defaults'
4
- import { Test } from '../typings'
5
- import * as utils from '../utils'
6
-
7
- export const installAllInterfaces: Test = {
8
- name: 'cli should allow installing public interfaces',
9
- handler: async ({ tmpDir, logger, ...creds }) => {
10
- const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
11
- const baseDir = pathlib.join(tmpDir, 'interfaces')
12
-
13
- const argv = {
14
- ...defaults,
15
- botpressHome: botpressHomeDir,
16
- confirm: true,
17
- ...creds,
18
- }
19
-
20
- await impl.login({ ...argv }).then(utils.handleExitCode)
21
-
22
- const interfaces: string[] = [
23
- 'creatable',
24
- 'deletable',
25
- 'hitl',
26
- 'listable',
27
- 'llm',
28
- 'readable',
29
- 'speechToText',
30
- 'textToImage',
31
- 'typingIndicator',
32
- 'updatable',
33
- ]
34
-
35
- for (const iface of interfaces) {
36
- logger.info(`Installing interface: ${iface}`)
37
- await impl
38
- .add({
39
- ...argv,
40
- packageRef: iface,
41
- packageType: 'interface',
42
- installPath: baseDir,
43
- useDev: false,
44
- })
45
- .then(utils.handleExitCode)
46
- // TODO: also run a type check on the installed interface
47
- }
48
- },
49
- }
@@ -1,164 +0,0 @@
1
- import * as client from '@botpress/client'
2
- import * as sdk from '@botpress/sdk'
3
- import * as fslib from 'fs'
4
- import * as pathlib from 'path'
5
- import * as uuid from 'uuid'
6
- import * as apiUtils from '../../src/api'
7
- import impl from '../../src/command-implementations'
8
- import defaults from '../defaults'
9
- import { Test, TestProps } from '../typings'
10
- import * as utils from '../utils'
11
-
12
- const issueSchema = sdk.z.object({
13
- id: sdk.z.string(),
14
- priority: sdk.z.enum(['high', 'medium', 'low']),
15
- title: sdk.z.string(),
16
- body: sdk.z.string(),
17
- })
18
- const INTEGRATION = {
19
- version: '0.0.1',
20
- title: 'An Integration',
21
- description: 'An integration',
22
- user: { tags: { id: { title: 'ID', description: 'The user ID' } } },
23
- configuration: { schema: sdk.z.object({}), identifier: { required: true, linkTemplateScript: '' } },
24
- configurations: {
25
- token: {
26
- title: 'API Token',
27
- description: 'The token to authenticate with',
28
- schema: sdk.z.object({ token: sdk.z.string() }),
29
- },
30
- },
31
- actions: {
32
- getIssue: {
33
- input: { schema: sdk.z.object({ id: sdk.z.string() }) },
34
- output: { schema: issueSchema },
35
- },
36
- },
37
- events: {
38
- issueCreated: {
39
- title: 'Issue Created',
40
- description: 'An issue was created',
41
- schema: issueSchema,
42
- },
43
- },
44
- channels: {
45
- issueComment: {
46
- title: 'Issue Comment',
47
- description: 'Comment on an issue',
48
- messages: { text: sdk.messages.defaults.text },
49
- conversation: { tags: { id: { title: 'ID', description: 'The issue ID' } } },
50
- message: { tags: { id: { title: 'ID', description: 'The issue comment ID' } } },
51
- },
52
- },
53
- entities: {
54
- issue: {
55
- title: 'Issue',
56
- description: 'An issue',
57
- schema: issueSchema,
58
- },
59
- },
60
- states: {
61
- lastCreatedIssue: {
62
- type: 'integration',
63
- schema: issueSchema,
64
- },
65
- },
66
- identifier: {
67
- extractScript: '',
68
- },
69
- } satisfies Omit<sdk.IntegrationDefinitionProps, 'name'>
70
-
71
- const getHomeDir = (props: { tmpDir: string }) => pathlib.join(props.tmpDir, '.botpresshome')
72
- const initBot = async (props: TestProps, definitionFile: string) => {
73
- const { tmpDir, dependencies, ...creds } = props
74
- const argv = {
75
- ...defaults,
76
- botpressHome: getHomeDir(props),
77
- confirm: true,
78
- ...creds,
79
- }
80
- const botName = uuid.v4().replace(/-/g, '')
81
- const botDir = pathlib.join(tmpDir, botName)
82
- await impl.init({ ...argv, workDir: tmpDir, name: botName, type: 'bot' }).then(utils.handleExitCode)
83
- await utils.fixBotpressDependencies({ workDir: botDir, target: dependencies })
84
- await utils.npmInstall({ workDir: botDir }).then(utils.handleExitCode)
85
- await fslib.promises.writeFile(pathlib.join(botDir, 'bot.definition.ts'), definitionFile)
86
- return { botDir }
87
- }
88
-
89
- // TODO: add an equivalent test with an interface once interfaces can be created by any workspace
90
-
91
- export const addIntegration: Test = {
92
- name: 'cli should allow installing an integration',
93
- handler: async (props) => {
94
- const { tmpDir, workspaceHandle, logger, ...creds } = props
95
- const argv = {
96
- ...defaults,
97
- botpressHome: getHomeDir({ tmpDir }),
98
- confirm: true,
99
- ...creds,
100
- }
101
-
102
- const bpClient = new client.Client({ apiUrl: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId })
103
-
104
- const integrationSuffix = uuid.v4().replace(/-/g, '')
105
- const name = `myintegration${integrationSuffix}`
106
- const integrationName = `${workspaceHandle}/${name}`
107
-
108
- const createIntegrationBody = await apiUtils.prepareCreateIntegrationBody(
109
- new sdk.IntegrationDefinition({
110
- ...INTEGRATION,
111
- name: integrationName,
112
- })
113
- )
114
-
115
- const { integration } = await bpClient.createIntegration({
116
- ...createIntegrationBody,
117
- dev: true, // this way we ensure the integration will eventually be janitored if the test fails
118
- url: creds.apiUrl,
119
- })
120
-
121
- try {
122
- logger.info('Initializing bot')
123
- const { botDir } = await initBot(
124
- props,
125
- [
126
- 'import * as sdk from "@botpress/sdk"',
127
- `import anIntegration from "./bp_modules/${workspaceHandle}-${name}"`,
128
- 'export default new sdk.BotDefinition({}).addIntegration(anIntegration, {',
129
- ' enabled: true,',
130
- ' configurationType: null,',
131
- ' configuration: {},',
132
- '})',
133
- ].join('\n')
134
- )
135
-
136
- logger.info('Logging in')
137
- await impl.login(argv).then(utils.handleExitCode)
138
-
139
- logger.info('Installing integration')
140
- await impl
141
- .add({
142
- ...argv,
143
- packageType: undefined,
144
- installPath: botDir,
145
- packageRef: integration.id,
146
- useDev: false,
147
- })
148
- .then(utils.handleExitCode)
149
-
150
- logger.info('Building bot')
151
- await impl.build({ ...argv, workDir: botDir }).then(utils.handleExitCode)
152
- await utils.tscCheck({ workDir: botDir }).then(utils.handleExitCode)
153
- } finally {
154
- await impl.integrations.subcommands
155
- .delete({
156
- ...argv,
157
- integrationRef: integration.id,
158
- })
159
- .catch(() => {
160
- logger.warn(`Failed to delete integration ${integration.id}`) // this is not the purpose of the test
161
- })
162
- }
163
- },
164
- }
@@ -1,102 +0,0 @@
1
- import { Client } from '@botpress/client'
2
- import * as sdk from '@botpress/sdk'
3
- import fs from 'fs'
4
- import pathlib from 'path'
5
- import * as uuid from 'uuid'
6
- import impl from '../../src/command-implementations'
7
- import { fetchAllIntegrations, ApiIntegration } from '../api'
8
- import defaults from '../defaults'
9
- import { Test } from '../typings'
10
- import * as utils from '../utils'
11
-
12
- type SecretDef = NonNullable<sdk.IntegrationDefinitionProps['secrets']>
13
-
14
- const fetchIntegration = async (client: Client, integrationName: string): Promise<ApiIntegration | undefined> => {
15
- const integrations = await fetchAllIntegrations(client)
16
- return integrations.find(({ name }) => name === integrationName)
17
- }
18
-
19
- const appendSecretDefinition = (originalTsContent: string, secrets: SecretDef): string => {
20
- const regex = /( *)version: (['"].*['"]),/
21
- const replacement = [
22
- 'version: $2,',
23
- 'secrets: {',
24
- ...Object.entries(secrets).map(([secretName, secretDef]) => ` ${secretName}: ${JSON.stringify(secretDef)},`),
25
- '},',
26
- ]
27
- .map((s) => `$1${s}`) // for indentation
28
- .join('\n')
29
-
30
- const modifiedTsContent = originalTsContent.replace(regex, replacement)
31
- return modifiedTsContent
32
- }
33
-
34
- export const requiredSecrets: Test = {
35
- name: 'cli should require required secrets',
36
- handler: async ({ tmpDir, workspaceHandle, dependencies, ...creds }) => {
37
- const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
38
- const baseDir = pathlib.join(tmpDir, 'integrations')
39
-
40
- const integrationSuffix = uuid.v4().replace(/-/g, '')
41
- const name = `myintegration${integrationSuffix}`
42
- const integrationName = `${workspaceHandle}/${name}`
43
- const integrationDirName = `${workspaceHandle}-${name}`
44
- const integrationDir = pathlib.join(baseDir, integrationDirName)
45
-
46
- const definitionPath = pathlib.join(integrationDir, 'integration.definition.ts')
47
-
48
- const argv = {
49
- ...defaults,
50
- botpressHome: botpressHomeDir,
51
- confirm: true,
52
- ...creds,
53
- }
54
-
55
- const client = new Client({ apiUrl: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId })
56
-
57
- await impl
58
- .init({ ...argv, workDir: baseDir, name: integrationName, type: 'integration' })
59
- .then(utils.handleExitCode)
60
-
61
- const originalDefinition: string = fs.readFileSync(definitionPath, 'utf-8')
62
- const modifiedDefinition = appendSecretDefinition(originalDefinition, {
63
- REQUIRED_SECRET: {},
64
- OPTIONAL_SECRET: { optional: true },
65
- })
66
- fs.writeFileSync(definitionPath, modifiedDefinition, 'utf-8')
67
- await impl.build({ ...argv, workDir: integrationDir }).then(utils.handleExitCode)
68
-
69
- await utils.fixBotpressDependencies({ workDir: integrationDir, target: dependencies })
70
- await utils.npmInstall({ workDir: integrationDir }).then(utils.handleExitCode)
71
- await impl.build({ ...argv, workDir: integrationDir }).then(utils.handleExitCode)
72
- await impl.login({ ...argv }).then(utils.handleExitCode)
73
-
74
- const { exitCode } = await impl.deploy({
75
- ...argv,
76
- workDir: integrationDir,
77
- secrets: ['OPTIONAL_SECRET=lol'],
78
- botId: undefined,
79
- createNewBot: undefined,
80
- })
81
- if (exitCode === 0) {
82
- throw new Error('Expected deploy to fail')
83
- }
84
-
85
- await impl
86
- .deploy({
87
- ...argv,
88
- workDir: integrationDir,
89
- secrets: ['REQUIRED_SECRET=lol'],
90
- botId: undefined,
91
- createNewBot: undefined,
92
- })
93
- .then(utils.handleExitCode)
94
-
95
- // cleanup deployed integration
96
- const integration = await fetchIntegration(client, integrationName)
97
- if (!integration) {
98
- throw new Error(`Integration ${integrationName} should have been created`)
99
- }
100
- await client.deleteIntegration({ id: integration.id })
101
- },
102
- }
@@ -1,105 +0,0 @@
1
- import { Client } from '@botpress/client'
2
- import pathlib from 'path'
3
- import impl from '../../src/command-implementations'
4
- import { ApiIntegration, fetchAllIntegrations } from '../api'
5
- import defaults from '../defaults'
6
- import { Test } from '../typings'
7
- import * as utils from '../utils'
8
-
9
- const fetchIntegration = async (client: Client, integrationName: string): Promise<ApiIntegration | undefined> => {
10
- const integrations = await fetchAllIntegrations(client)
11
- return integrations.find(({ name }) => name === integrationName)
12
- }
13
-
14
- export const prependWorkspaceHandle: Test = {
15
- name: 'cli should automatically preprend the workspace handle to the integration name when deploying',
16
- handler: async ({ tmpDir, dependencies, workspaceHandle, logger, ...creds }) => {
17
- const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
18
- const baseDir = pathlib.join(tmpDir, 'integrations')
19
-
20
- const integrationSuffix = utils.getUUID()
21
- const integrationName = `myintegration${integrationSuffix}`
22
- const integrationDirName = integrationName
23
- const integrationDir = pathlib.join(baseDir, integrationDirName)
24
-
25
- const argv = {
26
- ...defaults,
27
- botpressHome: botpressHomeDir,
28
- confirm: true,
29
- ...creds,
30
- }
31
-
32
- const client = new Client({ apiUrl: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId })
33
-
34
- await impl
35
- .init({ ...argv, workDir: baseDir, name: integrationName, type: 'integration' })
36
- .then(utils.handleExitCode)
37
- await utils.fixBotpressDependencies({ workDir: integrationDir, target: dependencies })
38
- await utils.npmInstall({ workDir: integrationDir }).then(utils.handleExitCode)
39
- await impl.build({ ...argv, workDir: integrationDir }).then(utils.handleExitCode)
40
- await impl.login({ ...argv }).then(utils.handleExitCode)
41
-
42
- await impl
43
- .deploy({ ...argv, createNewBot: undefined, botId: undefined, workDir: integrationDir })
44
- .then(utils.handleExitCode)
45
-
46
- logger.debug(`Fetching integration "${integrationName}"`)
47
- let integration = await fetchIntegration(client, integrationName)
48
- if (integration) {
49
- throw new Error(`Integration ${integrationName} should not have been created`)
50
- }
51
-
52
- const expectedIntegrationName = `${workspaceHandle}/${integrationName}`
53
- logger.debug(`Fetching integration "${expectedIntegrationName}"`)
54
- integration = await fetchIntegration(client, expectedIntegrationName)
55
- if (!integration) {
56
- throw new Error(`Integration ${expectedIntegrationName} should have been created`)
57
- }
58
-
59
- logger.debug(`Deleting integration "${integrationName}"`)
60
- await impl.integrations.subcommands.delete({ ...argv, integrationRef: integration.id }).then(({ exitCode }) => {
61
- exitCode !== 0 && logger.warn(`Failed to delete integration "${integrationName}"`) // not enough to fail the test
62
- })
63
- },
64
- }
65
-
66
- export const enforceWorkspaceHandle: Test = {
67
- name: 'cli should fail when attempting to deploy an integration with incorrect workspace handle',
68
- handler: async ({ tmpDir, dependencies, ...creds }) => {
69
- const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome')
70
- const baseDir = pathlib.join(tmpDir, 'integrations')
71
-
72
- const randomSuffix = utils.getUUID().slice(0, 8)
73
-
74
- const name = 'myintegration'
75
- const handle = `myhandle${randomSuffix}`
76
- const integrationName = `${handle}/${name}`
77
- const integrationDirName = `${handle}-${name}`
78
- const integrationDir = pathlib.join(baseDir, integrationDirName)
79
-
80
- const argv = {
81
- ...defaults,
82
- botpressHome: botpressHomeDir,
83
- confirm: true,
84
- ...creds,
85
- }
86
-
87
- await impl
88
- .init({ ...argv, workDir: baseDir, name: integrationName, type: 'integration' })
89
- .then(utils.handleExitCode)
90
- await utils.fixBotpressDependencies({ workDir: integrationDir, target: dependencies })
91
- await utils.npmInstall({ workDir: integrationDir }).then(utils.handleExitCode)
92
- await impl.login({ ...argv }).then(utils.handleExitCode)
93
-
94
- const { exitCode } = await impl.deploy({
95
- ...argv,
96
- createNewBot: undefined,
97
- botId: undefined,
98
- workDir: integrationDir,
99
- })
100
-
101
- if (exitCode === 0) {
102
- throw new Error(`Integration ${integrationName} should not have been deployed`)
103
- }
104
- },
105
- }
package/e2e/typings.ts DELETED
@@ -1,17 +0,0 @@
1
- import { Logger } from '@bpinternal/log4bot'
2
-
3
- export type TestProps = {
4
- logger: Logger
5
- tmpDir: string
6
- workspaceId: string
7
- workspaceHandle: string
8
- token: string
9
- apiUrl: string
10
- tunnelUrl: string
11
- dependencies: Record<string, string | undefined>
12
- }
13
-
14
- export type Test = {
15
- name: string
16
- handler: (props: TestProps) => Promise<void>
17
- }
package/e2e/utils.ts DELETED
@@ -1,99 +0,0 @@
1
- import childprocess from 'child_process'
2
- import fs from 'fs'
3
- import _ from 'lodash'
4
- import pathlib from 'path'
5
- import tmp from 'tmp'
6
- import * as uuid from 'uuid'
7
-
8
- type PackageJson = {
9
- name: string
10
- version?: string
11
- description?: string
12
- scripts?: Record<string, string>
13
- dependencies?: Record<string, string>
14
- devDependencies?: Record<string, string>
15
- peerDependencies?: Record<string, string>
16
- }
17
-
18
- export const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
19
-
20
- export class TmpDirectory {
21
- private _closed = false
22
-
23
- public static create() {
24
- return new TmpDirectory(tmp.dirSync({ unsafeCleanup: true }))
25
- }
26
-
27
- private constructor(private _res: tmp.DirResult) {}
28
-
29
- public get path() {
30
- if (this._closed) {
31
- throw new Error('Cannot access tmp directory after cleanup')
32
- }
33
- return this._res.name
34
- }
35
-
36
- public cleanup() {
37
- if (this._closed) {
38
- return
39
- }
40
- this._res.removeCallback()
41
- }
42
- }
43
-
44
- export type RunCommandOptions = {
45
- workDir: string
46
- }
47
-
48
- export type RunCommandOutput = {
49
- exitCode: number
50
- }
51
-
52
- export const runCommand = async (cmd: string, { workDir }: RunCommandOptions): Promise<RunCommandOutput> => {
53
- const [program, ...args] = cmd.split(' ')
54
- if (!program) {
55
- throw new Error('Cannot run empty command')
56
- }
57
- const { error, status } = childprocess.spawnSync(program, args, {
58
- cwd: workDir,
59
- stdio: 'inherit',
60
- })
61
- if (error) {
62
- throw error
63
- }
64
- return { exitCode: status ?? 0 }
65
- }
66
-
67
- export const npmInstall = ({ workDir }: RunCommandOptions): Promise<RunCommandOutput> => {
68
- return runCommand('pnpm install', { workDir })
69
- }
70
-
71
- export const tscCheck = ({ workDir }: RunCommandOptions): Promise<RunCommandOutput> => {
72
- return runCommand('tsc --noEmit', { workDir })
73
- }
74
-
75
- export const fixBotpressDependencies = async ({
76
- workDir,
77
- target,
78
- }: {
79
- workDir: string
80
- target: Record<string, string | undefined>
81
- }) => {
82
- const packageJsonPath = pathlib.join(workDir, 'package.json')
83
- const originalPackageJson: PackageJson = require(packageJsonPath)
84
-
85
- const newPackageJson = {
86
- ...originalPackageJson,
87
- dependencies: _.mapValues(originalPackageJson.dependencies ?? {}, (version, name) => target[name] ?? version),
88
- }
89
-
90
- fs.writeFileSync(packageJsonPath, JSON.stringify(newPackageJson, null, 2))
91
- }
92
-
93
- export const handleExitCode = ({ exitCode }: { exitCode: number }) => {
94
- if (exitCode !== 0) {
95
- throw new Error(`Command exited with code ${exitCode}`)
96
- }
97
- }
98
-
99
- export const getUUID = () => uuid.v4().replace(/-/g, '')