import { Client, isApiError } from '@botpress/client' import axios from 'axios' import findProcess from 'find-process' import fslib from 'fs' import pathlib from 'path' import * as uuid from 'uuid' import impl from '../../src' import defaults from '../defaults' import * as retry from '../retry' import { Test } from '../typings' import * as utils from '../utils' const handleExitCode = ({ exitCode }: { exitCode: number }) => { if (exitCode !== 0) { throw new Error(`Command exited with code ${exitCode}`) } } const fetchDevBotById = async (client: Client, id: string) => { const { bot } = await client.getBot({ id }).catch((err) => { if (isApiError(err) && err.type === 'ResourceNotFound') { return { bot: undefined } } throw err }) return bot } const readBotCache = async ( botDir: string ): Promise<{ botId?: string tunnelId?: string devId?: string }> => { const botCache = pathlib.join(botDir, '.botpress', 'project.cache.json') if (!fslib.existsSync(botCache)) { return {} } const cacheContent: string = await fslib.promises.readFile(botCache, 'utf-8') try { return JSON.parse(cacheContent) } catch { return {} } } const PORT = 8075 export const devBot: Test = { name: 'cli should allow creating and running a bot locally', handler: async ({ tmpDir, tunnelUrl, dependencies, ...creds }) => { const botpressHomeDir = pathlib.join(tmpDir, '.botpresshome') const baseDir = pathlib.join(tmpDir, 'bots') const botName = uuid.v4() const tunnelId = uuid.v4() const botDir = pathlib.join(baseDir, botName) const argv = { ...defaults, botpressHome: botpressHomeDir, confirm: true, ...creds, } const client = new Client({ apiUrl: creds.apiUrl, token: creds.token, workspaceId: creds.workspaceId, retry: retry.config, }) await impl.init({ ...argv, workDir: baseDir, name: botName, type: 'bot', template: 'empty' }).then(handleExitCode) await utils.fixBotpressDependencies({ workDir: botDir, target: dependencies }) await utils.npmInstall({ workDir: botDir }).then(handleExitCode) await impl.login({ ...argv }).then(handleExitCode) const cmdPromise = impl .dev({ ...argv, workDir: botDir, port: PORT, tunnelUrl, tunnelId, noSecretCaching: true }) .then(handleExitCode) await utils.sleep(5000) const allProcess = await findProcess('port', PORT) const [botProcess] = allProcess if (allProcess.length > 1) { throw new Error(`Expected to find only one process listening on port ${PORT}`) } if (!botProcess) { throw new Error(`Expected to find a process listening on port ${PORT}`) } const botCache = await readBotCache(botDir) const { devId: botId } = botCache if (!botId) { throw new Error('Expected devId to be set in project cache') } const resp = await axios.get(`http://localhost:${PORT}/health`) if (resp.status !== 200) { throw new Error(`Expected health endpoint to return 200, got ${resp.status}`) } process.kill(botProcess.pid) await cmdPromise const apiBot = await fetchDevBotById(client, botId) if (!apiBot) { throw new Error(`Dev Bot ${botId} should have been created in the backend`) } await impl.bots.delete({ ...argv, botRef: apiBot.id }).then(utils.handleExitCode) if (await fetchDevBotById(client, botId)) { throw new Error(`Dev bot ${botId} should have been deleted`) } }, }