@onboardbase/cli
Version:
[](https://www.npmjs.com/package/@onboardbase/cli) [](https://www.npmjs.com/package/@onboardbase/cli) [ • 21 kB
JavaScript
"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();