UNPKG

@onboardbase/cli

Version:

[![Version](https://img.shields.io/npm/v/@onboardbase/cli.svg)](https://www.npmjs.com/package/@onboardbase/cli) [![Downloads/week](https://img.shields.io/npm/dw/@onboardbase/cli.svg)](https://www.npmjs.com/package/@onboardbase/cli) [![License](https://img

460 lines (459 loc) 21 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = require("fs"); const os_1 = require("os"); const path_1 = require("path"); const YAML = require("yaml"); const utils_1 = require("../utils"); const chalk = require("chalk"); const rimraf = require("rimraf"); const axios_1 = require("axios"); const kill = require("tree-kill"); class ConfigManager { constructor() { this.store = {}; this.runningProcess = { socket: undefined, main: undefined, }; this.defaultApiHostBaseUrl = "https://api.onboardbase.com/graphql"; this.defaultDashboardBaseUrl = "https://app.onboardbase.com"; this.configFile = ".onboardbase.yaml"; this.onboardbaseDirectory = (0, path_1.join)((0, os_1.homedir)(), ".onboardbase"); this.onboardbaseFallbackDirectory = (0, path_1.join)(this.onboardbaseDirectory, "fallback"); this.onboardbaseConfigFile = (0, path_1.join)(this.onboardbaseDirectory, this.configFile); this.onboardbaseVersioningDirectory = (0, path_1.join)(this.onboardbaseDirectory, ".onboardbase-project-versions.yaml"); this.onboardbaseDatabaseDirectory = (0, path_1.join)(this.onboardbaseDirectory, "db", "onboardbase.json"); this.rsaKeys = { publicKey: undefined, privateKey: undefined, backendPublicKey: undefined, }; this.shouldCreateProjectLog = true; this.projectFallbackConfigFile = (0, path_1.join)(process.cwd(), "onboardbase.yaml"); this.projectNewConfigFile = (0, path_1.join)(process.cwd(), ".onboardbase.yaml"); /** * check for .yml extension first before falling back to .yaml. */ this.projectConfigPath = (0, path_1.join)(process.cwd(), ".onboardbase.yml"); /** * Add a fallback for .yml extension incase a user chooses to use that in replace of .yaml * * Also check to see if the user has the old config file, that is `onboardbase.yaml`, * if user does, just fallback to that instead of assuming the user doesn't have any * local onboardbase config file. * * @todo * * Fallback should no longer be supported in the next couple of releases so as to enforce * using the new `.onboardbase.yaml` file instead. */ this.projectConfigFile = (0, fs_1.existsSync)(this.projectConfigPath) ? this.projectConfigPath : (0, fs_1.existsSync)(this.projectFallbackConfigFile) ? this.projectFallbackConfigFile : this.projectNewConfigFile; } /** * * @param apiHost string * This should only be called when authenticating. * How does it work? let's assume a user has two organizations, they have authenticated for one already and are trying * to authenticate for another organization, we need to map the 1st organization api-host to the scope of the organization * they're trying to auth with (that is, the 2nd organization). * Why? Because deviceTokens are scoped to api's. So the api that handled the authentication for the 2nd org * is also the api that has the organization's deviceToken. * * I hope you get it. */ setAuthApiHost(apiHost) { this.authApiHost = apiHost; } getAuthApiHost() { return this.authApiHost; } setSyncSocketClient(socketClient) { this.syncSocketClient = socketClient; } getSynSocketClient() { return this.syncSocketClient; } storeEncrytionSecretKey(secretKey) { this.encryptionSecretKey = secretKey; } getEncryptionSecretKey() { return this.encryptionSecretKey; } /** * * @param payload * * this is basically a decoded information of the user's authToken * & the ID of the current project the user is running for the current * CLI & Login Session */ setAuthSessionDetails(payload) { this.authSessionDetails = payload; } getAuthSessionDetails() { return this.authSessionDetails; } throwAuthenticationError() { console.error(chalk.bold.red("Please you need to login to start using the CLI.")); process.exit(1); } checkAndCreateOnboardbaseDirectories() { // Create global onboardbase directory if it doesnt exist if (!(0, utils_1.isExist)(this.onboardbaseDirectory)) (0, fs_1.mkdirSync)(this.onboardbaseDirectory); // Create fallback folder if it doesnt exist if (!(0, utils_1.isExist)(this.onboardbaseFallbackDirectory)) (0, fs_1.mkdirSync)(this.onboardbaseFallbackDirectory); // Create db folder if it doesnt exist if (!(0, utils_1.isExist)((0, path_1.join)(this.onboardbaseDirectory, "db"))) { (0, fs_1.mkdirSync)((0, path_1.join)(this.onboardbaseDirectory, "db")); } // Create versioning file if (!(0, utils_1.isExist)(this.onboardbaseVersioningDirectory)) (0, fs_1.writeFileSync)(this.onboardbaseVersioningDirectory, YAML.stringify({}), { encoding: "utf8", }); } // Creates the .onboardbase.yaml file createOnboardbaseConfigFile() { (0, fs_1.writeFileSync)(this.onboardbaseConfigFile, YAML.stringify({ scoped: {}, "version-check": {} })); } /** * * @param command string optional. It's used to exclude the * login command from throwing authentication error when ConfigManager * is called without being authenticated. * @returns void */ async init(command, iconfig) { var _a; this.onboardbaseConfigFile = (0, path_1.join)(iconfig === null || iconfig === void 0 ? void 0 : iconfig.configDir, "config.json"); const isExist = (path) => (0, fs_1.existsSync)(path); this.checkAndCreateOnboardbaseDirectories(); if (!isExist(this.onboardbaseConfigFile)) { // check to see if the user is using ENV vars const onboardbaseToken = process.env.ONBOARDBASE_TOKEN; const onboardbaseProject = process.env.ONBOARDBASE_PROJECT; const onboardbaseEnvironment = process.env.ONBOARDBASE_ENVIRONMENT; if (onboardbaseToken) { this.store = { project: onboardbaseProject, environment: onboardbaseEnvironment, token: onboardbaseToken, }; return; } this.createOnboardbaseConfigFile(); return; } const config = JSON.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); this.configManager = new ConfigManager(); const currentDirectory = process.cwd(); const directoryScopes = config.scoped; const projectScope = (_a = directoryScopes[currentDirectory]) !== null && _a !== void 0 ? _a : directoryScopes["/"]; if (!projectScope && command !== "Login") this.throwAuthenticationError(); let finalConfig = projectScope; const projectScopedConfigPath = this.projectConfigFile; if (isExist(projectScopedConfigPath)) { const projectConfig = this.getProjectConfig(); finalConfig = Object.assign(finalConfig !== null && finalConfig !== void 0 ? finalConfig : {}, projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.setup); } Object.keys(finalConfig).map((key) => { const configval = finalConfig[key]; finalConfig[key] = configval === "__REFPATH__" ? directoryScopes["/"][key] : finalConfig[key]; }); this.store = Object.assign(this.store, finalConfig); } isUserLoggedIn() { var _a; const isExist = (path) => (0, fs_1.existsSync)(path); this.checkAndCreateOnboardbaseDirectories(); if (!isExist(this.onboardbaseConfigFile)) { // check to see if the user is using ENV vars const onboardbaseToken = process.env.ONBOARDBASE_TOKEN; const onboardbaseProject = process.env.ONBOARDBASE_PROJECT; const onboardbaseEnvironment = process.env.ONBOARDBASE_ENVIRONMENT; if (onboardbaseToken !== undefined) { this.store = { project: onboardbaseProject, environment: onboardbaseEnvironment, token: onboardbaseToken, }; return; } this.createOnboardbaseConfigFile(); return; } const config = YAML.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); const currentDirectory = process.cwd(); const directoryScopes = config.scoped; const projectScope = (_a = directoryScopes[currentDirectory]) !== null && _a !== void 0 ? _a : directoryScopes["/"]; return Boolean(projectScope); } registerProcess(identifier, process) { this.runningProcess[identifier] = process; } closeRunningProcess() { try { // this.runningProcess.main?.kill("SIGINT"); // this.runningProcess.socket?.kill("SIGINT"); kill(this.runningProcess.main.pid); kill(this.runningProcess.socket.pid); } catch (error) { } } getConfigs() { let directoryScopes; if ((0, fs_1.existsSync)(this.onboardbaseConfigFile)) { const config = YAML.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); directoryScopes = config.scoped; } else directoryScopes = {}; return directoryScopes; } getScopedConfig(directory) { const directoryScopes = this.getConfigs(); return directoryScopes[directory !== null && directory !== void 0 ? directory : process.cwd()]; } getProjectConfig() { const localConfig = this.projectConfigFile; if ((0, utils_1.isExist)(localConfig)) { return YAML.parse((0, fs_1.readFileSync)(localConfig, { encoding: "utf8" })); } } getLocalConfig() { return this.store; } /** * This returns an encrypted version of the user's token * @returns */ getRawToken() { return this.store["token"]; } /** * This returns a decrypted version of the user's token * @returns */ async getToken() { const token = this.store["token"]; return token; } async isNewServiceToken() { return (await this.getToken()).length < 43 ? true : false; } getRsaKeys() { return this.rsaKeys; } storeRsaKeys(publicKey, privateKey) { this.rsaKeys.privateKey = privateKey; this.rsaKeys.publicKey = publicKey; } storeBackendPublicKey(publicKey) { this.rsaKeys.backendPublicKey = publicKey; // TODO: check if it is epheremal and needs to persist } async deleteToken({ scope }) { const config = YAML.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); let data = { scoped: {}, }; Object.keys(config.scoped).map((scopeConfig) => { if (scopeConfig !== scope) { data.scoped[scopeConfig] = config.scoped[scopeConfig]; } }); const newScope = YAML.stringify(data); (0, fs_1.writeFileSync)(this.onboardbaseConfigFile, newScope); } getDirectoriesThatRequiresPassword() { const config = YAML.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); const scoped = config.scoped; const directoriesThatNeedsPassword = []; Object.keys(scoped).map((scopedDirectoryKey) => { if (scoped[scopedDirectoryKey].requirePassword) directoriesThatNeedsPassword.push(scopedDirectoryKey); }); return directoriesThatNeedsPassword; } async updateLocalProjectSecrets(updatedSecrets) { const localEnvironmentFile = this.projectConfigFile; if ((0, utils_1.isExist)(localEnvironmentFile)) { try { const parsedContents = YAML.parse((0, fs_1.readFileSync)(localEnvironmentFile, "utf8")); const updatedData = Object.assign(Object.assign({}, parsedContents), { secrets: { local: updatedSecrets, } }); const stringifiedData = YAML.stringify(updatedData); (0, fs_1.writeFileSync)(this.projectConfigFile, stringifiedData); } catch (error) { } } } async updateDirectorySessionConfig({ scope, requirePasswordForCurrentSession, }) { const config = YAML.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); const scoped = config.scoped; const updatedConfig = { scoped: Object.assign(Object.assign({}, scoped), { [scope]: Object.assign(Object.assign({}, scoped[scope]), { requirePasswordForCurrentSession }) }), }; const newScope = YAML.stringify(updatedConfig); (0, fs_1.writeFileSync)(this.onboardbaseConfigFile, newScope); } async updateGlobalConfig({ scope = "/", token, encryptToken = true, apiHost, dashboardHost, authToken, requirePassword, password, requirePasswordForCurrentSession, }) { this.checkAndCreateOnboardbaseDirectories(); if (!(0, utils_1.isExist)(this.onboardbaseConfigFile)) this.createOnboardbaseConfigFile(); const config = YAML.parse((0, fs_1.readFileSync)(this.onboardbaseConfigFile, { encoding: "utf8" })); let finalData; // if there is an existing scoped config, update it if (config.scoped) { finalData = Object.assign(config, { scoped: Object.assign(config.scoped, { [scope]: Object.assign(Object.assign({}, (config.scoped[scope] || {})), { token, "api-host": apiHost !== null && apiHost !== void 0 ? apiHost : this.defaultApiHostBaseUrl, "dashboard-host": dashboardHost !== null && dashboardHost !== void 0 ? dashboardHost : this.defaultDashboardBaseUrl, requirePassword, password: await (0, utils_1.encryptSecrets)(password), requirePasswordForCurrentSession }), }), }); } // if there is no existing scoped config, create a new one else finalData = { scoped: { [scope]: { token, "api-host": apiHost !== null && apiHost !== void 0 ? apiHost : this.defaultApiHostBaseUrl, "dashboard-host": dashboardHost !== null && dashboardHost !== void 0 ? dashboardHost : this.defaultDashboardBaseUrl, requirePassword, password: await (0, utils_1.encryptSecrets)(password), requirePasswordForCurrentSession, }, }, }; /** * Session Mananegment for secure build is only available for unix * right now. Windows users will always have to re-enter their password * eveyrtime they want to use the run/build command */ if (requirePassword && (0, utils_1.isUnix)()) { /** * check to see if onboardbase exist in the user's RC file */ const shellRC = (0, utils_1.getShellRc)(); const existingData = (0, fs_1.readFileSync)(shellRC, "utf-8"); const existDataArray = existingData.split("\n"); const onboardbaseCommandExistinRCFile = existDataArray.includes("# onboardbase"); /** * only update the user's RC file again if the onboardbase command doesnt already * exist in the user's RC file */ if (!onboardbaseCommandExistinRCFile) { const command = `${existingData}\n\n# onboardbase\nonboardbase config:set --re-authenticate`; (0, fs_1.writeFileSync)(shellRC, command); } } const newScope = YAML.stringify(finalData); (0, fs_1.writeFileSync)(this.onboardbaseConfigFile, newScope); } async deleteOnboardbaseConfigs() { rimraf.sync(this.onboardbaseDirectory); console.log(chalk.green("onboardbase artifacts deleted successfully...")); process.nextTick(process.exit(1)); } getHttpInstance(url = null) { const allConfigs = this.getConfigs(); const timeoutThresholdTermination = this._getTimeThresholdForTerminationInMs(); const instance = axios_1.default.create({ baseURL: url !== null && url !== void 0 ? url : this.getApiHostBaseUrl(allConfigs), timeout: timeoutThresholdTermination, }); instance.interceptors.request.use((config) => { config.metadata = { startTime: new Date().getTime() }; return config; }); instance.interceptors.response.use((response) => { return response; }, (error) => { this._handleRequestTimeout(error, timeoutThresholdTermination); return Promise.reject(error); }); return instance; } _handleRequestTimeout(error, timeoutThresholdTermination) { const endTime = new Date().getTime(); const duration = endTime - error.config.metadata.startTime; if (duration > timeoutThresholdTermination) { console.error(chalk.red(`Request running too slow and is terminated. Check your internet connection, request time: ${duration / 1000}s.`)); console.log(`You can increase the timeout value in your .onboardbase.yml file. Check the docs for more info.`); process.exit(1); } } _getTimeThresholdForTerminationInMs() { var _a; let configTimeoutInSeconds = parseInt((_a = this === null || this === void 0 ? void 0 : this.store) === null || _a === void 0 ? void 0 : _a.timeout); if (isNaN(configTimeoutInSeconds)) { configTimeoutInSeconds = this._getDefaultTimeoutAndWarn(); } const timeout = configTimeoutInSeconds * 1000; return timeout; } _getDefaultTimeoutAndWarn() { var _a; const defaultTimeoutInSeconds = 3 * 60; // default is 3 minutes if ((_a = this === null || this === void 0 ? void 0 : this.store) === null || _a === void 0 ? void 0 : _a.timeout) { console.warn(chalk.yellow(`Invalid timeout value, defaulting to ${defaultTimeoutInSeconds} seconds`)); } return defaultTimeoutInSeconds; } updateProjectAndEnvironmentVersion(project, environment, version) { const existingVerisons = (0, fs_1.readFileSync)(this.onboardbaseVersioningDirectory, { encoding: "utf8", }); const parsedVersions = YAML.parse(existingVerisons); const payload = Object.assign({}, parsedVersions, { [project]: { [environment]: version }, }); const newScope = YAML.stringify(payload); (0, fs_1.writeFileSync)(this.onboardbaseVersioningDirectory, newScope); } getCurrentProjectAndEnvironmentVersion(project, environment) { var _a, _b; const projectVersions = (0, fs_1.readFileSync)(this.onboardbaseVersioningDirectory, { encoding: "utf8", }); const parsedVersions = YAML.parse(projectVersions); return Object.keys(parsedVersions).length && ((_a = parsedVersions === null || parsedVersions === void 0 ? void 0 : parsedVersions[project]) === null || _a === void 0 ? void 0 : _a[environment]) ? (_b = parsedVersions === null || parsedVersions === void 0 ? void 0 : parsedVersions[project]) === null || _b === void 0 ? void 0 : _b[environment] : 0; } getApiHostBaseUrl(allConfigs) { var _a; const apiHostBaseUrl = (_a = this._getFromConfig(allConfigs, "api-host")) !== null && _a !== void 0 ? _a : this.defaultApiHostBaseUrl; return apiHostBaseUrl; } getDashboardBaseUrl(allConfigs) { var _a; const dashboardBaseUrl = (_a = this._getFromConfig(allConfigs, "dashboard-host")) !== null && _a !== void 0 ? _a : this.defaultDashboardBaseUrl; return dashboardBaseUrl; } _getFromConfig(allConfigs, key, scope) { scope = scope !== null && scope !== void 0 ? scope : process.cwd(); const config = allConfigs[scope]; if (!config) return; return config[key]; } getDefaultDashboardBaseUrl() { return this.defaultDashboardBaseUrl; } getDefaultApiHostBaseUrl() { return this.defaultApiHostBaseUrl; } } exports.default = new ConfigManager();