brainwise-demo-cli
Version:
brainwise-Newcli is an open source chatbot platform based on Rasa.
294 lines (252 loc) • 9.52 kB
JavaScript
import path from 'path';
import { promisify } from 'util';
import fs from 'fs';
import axios from 'axios';
import yaml from 'js-yaml';
export const BF_CONFIG_DIR = '.brainwise';
export const BF_CONFIG_FILE = 'brainwise.yml';
export const DOCKER_COMPOSE_TEMPLATE_FILENAME = 'docker-compose-template.yml';
export const DOCKER_COMPOSE_FILENAME = 'docker-compose.yml';
export const DEFAULT_MODEL_FILENAME = 'default_model.tar.gz';
export const ENV_FILENAME = '.env';
import shell from 'shelljs';
import { Docker } from 'docker-cli-js';
import chalk from 'chalk';
import boxen from 'boxen';
import check from 'check-node-version';
import compareVersions from 'compare-version';
export function fixDir(dir) {
return dir ? dir : process.cwd();
}
export function consoleError(error) {
try{
console.log(boxen(error))
} catch (e) {
console.log(error)
}
}
export function startSpinner(spinner, message) {
if (spinner) {
spinner.start(message);
} else {
console.log(message)
}
}
export function setSpinnerText(spinner, message) {
if (spinner) {
spinner.text = message;
} else {
console.log(message)
}
}
export function setSpinnerInfo(spinner, message) {
if (spinner) {
spinner.info = message;
} else {
console.log(message)
}
}
export function succeedSpinner(spinner, message) {
if (spinner) {
spinner.succeed(message);
} else {
console.log(message)
}
}
export function failSpinner(spinner, message, params = {}) {
const { exit = true } = params;
if (spinner) {
spinner.fail(message);
} else {
console.log(message)
}
if (exit) process.exit(1);
}
export function stopSpinner(spinner) {
if (spinner) {
spinner.stop();
}
}
export async function getLatestVersion() {
try {
const response = await axios.get('https://registry.npmjs.org/Brainwise-cli');
if (response.status === 200) {
return response.data['dist-tags'].latest;
}
// If the call fails we just return the current version
return getbrainwiseVersion();
} catch(e) {
return getbrainwiseVersion();
}
}
export function getProjectVersion() {
return getProjectConfig(fixDir(null)).version;
}
export function shouldUpdateProject() {
const brainwiseVersion = getbrainwiseVersion();
const projectVersion = getProjectVersion();
return brainwiseVersion !== projectVersion;
}
export async function shouldUpdateNpmPackage() {
const currentVersion = getbrainwiseVersion();
const latestVersion = await getLatestVersion();
return compareVersions(latestVersion, currentVersion) == 1;
}
export async function displayUpdateMessage() {
const shouldUpdate = await shouldUpdateNpmPackage();
if (shouldUpdate) {
console.log(boxen(`A new version of Brainwise-cli is available. Run ${chalk.cyan.bold('npm install -g brainwise-cli')} to update.`, { padding: 1, margin: 1 }))
}
return shouldUpdate;
}
/*
Augment the brainwise.yml file with version and project specific values
*/
export async function updateProjectFile(projectAbsPath, images) {
const config = getProjectConfig(projectAbsPath);
if (!config.version) {
config.version = getbrainwiseVersion();
}
config.images.current = JSON.parse(JSON.stringify(config.images.default)); // deep copy
Object.keys(config.images.current).forEach(service => {
if (images[service]) config.images.current[service] = images[service];
});
fs.writeFileSync(getProjectInfoFilePath(projectAbsPath), yaml.safeDump(config));
updateEnvFile(projectAbsPath);
}
export async function updateEnvFile(projectAbsPath) {
const config = getProjectConfig(projectAbsPath);
let envFileContent = '#################################################################\n';
envFileContent += '# This file is generated. #\n';
envFileContent += '# Environment variables can be changed or added in brainwise.yml #\n';
envFileContent += '#################################################################\n\n';
Object.keys(config.env).forEach(variable => {
envFileContent += `${variable.toUpperCase()}=${config.env[variable]}\n`;
});
fs.writeFileSync(getProjectEnvFilePath(projectAbsPath), envFileContent);
}
export async function generateDockerCompose(exclude = [], dir) {
const dc = getComposeFile(dir, DOCKER_COMPOSE_TEMPLATE_FILENAME);
exclude.forEach(excl => { // remove reference to excluded services
if (excl in dc.services) delete dc.services[excl]
Object.values(dc.services).forEach(service => {
if ({}.hasOwnProperty.call(service, 'depends_on')) {
service.depends_on = service.depends_on.filter(depend => depend !== excl);
if (service.depends_on.length === 0) delete service.depends_on;
}
})
})
const config = getProjectConfig(dir);
const dcCopy = JSON.parse(JSON.stringify(dc));
Object.keys(dc.services).forEach(service => {
dcCopy.services[service].image = config.images.current[service]
})
// for (let key in dcCopy.services) {
// console.log(key, dcCopy.services[key].depends_on)
// }
fs.writeFileSync(getComposeFilePath(dir), yaml.safeDump(dcCopy));
return true;
}
export function getProjectConfig(projectAbsPath) {
return yaml.safeLoad(fs.readFileSync(getProjectInfoFilePath(projectAbsPath), 'utf-8'));
}
export async function verifySystem() {
const docker = new Docker();
const result = await docker.command('info');
// const version = result.object.server_version;
if (!result.object) throw `You must install Docker to use brainwise-demo-cli. Please visit ${chalk.green('https://www.docker.com/products/docker-desktop')}`;
const results = await promisify(check)({ node: '>= 8.9'});
if (!results.versions.node.isSatisfied) {
throw `You must upgrade your Node.js installation to use brainwise-demo-cli. Please visit ${chalk.green('https://nodejs.org/en/download/')}`
};
}
export function getbrainwiseVersion() {
return JSON.parse(fs.readFileSync(path.join(__dirname, '../../brainwise-demo-cli/package.json'))).version;
}
export function getComposeFilePath(dir, fileName = DOCKER_COMPOSE_FILENAME) {
return path.join(fixDir(dir), BF_CONFIG_DIR, fileName);
}
export function getProjectInfoDirPath(dir) {
return path.join(fixDir(dir), BF_CONFIG_DIR);
}
export function getProjectInfoFilePath(dir) {
return path.join(fixDir(dir), BF_CONFIG_DIR, BF_CONFIG_FILE );
}
export function getProjectEnvFilePath(dir) {
return path.join(fixDir(dir), BF_CONFIG_DIR, ENV_FILENAME );
}
export function isProjectDir(dir) {
return fs.existsSync(getComposeFilePath(fixDir(dir)));
}
export function getComposeFile(dir, fileName) {
return yaml.safeLoad(fs.readFileSync(getComposeFilePath(dir, fileName), 'utf-8'));
}
export function getServices(dir) {
const services = getComposeFile(dir).services;
return Object.keys(services)
.filter(s => !!services[s].image)
.map(s => services[s].image);
}
export async function getMissingImgs(dir) {
const docker = new Docker({});
let availableImgs = await docker.command('images --format "{{.Repository}}:{{.Tag}}"');
availableImgs = availableImgs.raw.split('\n');
return getServices(dir).filter(service => !availableImgs.includes(service));
}
export function getServiceNames(dir) {
const services = getComposeFile(dir).services;
return Object.keys(services);
}
export function getDefaultServiceNames(dir) {
const services = getComposeFile(dir, DOCKER_COMPOSE_TEMPLATE_FILENAME).services;
return Object.keys(services);
}
export function getService(serviceName, dir) {
return getComposeFile(dir).services[serviceName];
}
export function getExternalPort(serviceName, dir) {
return getService(serviceName, dir).ports[0].split(':')[0];
}
export function getContainerAndImageNames(dir, services) {
let svcs = services || getComposeFile(dir).services;
return {
containers: Object.keys(svcs).map(s => services[s].container_name),
images: Object.keys(svcs).map(s => services[s].image),
};
}
export function getServiceUrl(serviceName) {
return `http://localhost:${getExternalPort(serviceName)}`
}
export function getComposeWorkingDir(workingDirectory) {
return workingDirectory ? workingDirectory : './';
}
export async function wait(millis) {
return promisify(setTimeout)(millis);
}
export function capitalize(s){
if (typeof s !== 'string') return ''
return s.charAt(0).toUpperCase() + s.slice(1)
}
export async function shellAsync(command, options) {
return promisify(shell.exec)(command, options);
}
export async function waitForService(serviceName) {
const serviceUrl = getServiceUrl(serviceName);
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
setTimeout(function() {
reject()
}, 90000)
let response;
try {
response = await axios.get(serviceUrl);
} catch (e) {
response = e.response;
}
if (response && [200, 404].includes(response.status)) {
clearInterval(interval);
return resolve();
}
}, 1000);
});
}