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

333 lines (332 loc) 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigManager = exports.PROJECT_CONFIG_FILENAME = exports.REFPATH_VALUE = void 0; const fs_1 = require("fs"); const os_1 = require("os"); const path_1 = require("path"); const YAML = require("yaml"); const safelyGetFileContent_1 = require("../common/utils/safelyGetFileContent"); const errors_1 = require("../common/errors"); const chalk = require("chalk"); const fs = require("fs"); const util = require("util"); const defaults_1 = require("../common/defaults"); exports.REFPATH_VALUE = `__REFPATH__`; const OLD_GLOBAL_CONFIG_YAML = "/.onboardbase/.onboardbase.yaml"; exports.PROJECT_CONFIG_FILENAME = ".onboardbase.yaml"; const GLOBAL_CONFIG_FILENAME = "config.json"; const DEFAULT_GLOBAL_CONFIGURATION = { "version-check": {}, scoped: {} }; const HOME_DIR = (0, os_1.homedir)(); class ConfigManager { constructor() { this.OLD_GLOBAL_CONFIG_DIR = `${HOME_DIR}/.onboardbase/`; this._CURRENT_DIR_SCOPE = process.cwd(); } destroy() { (0, fs_1.unlinkSync)(this._getGlobalConfigPath()); } getConfigDir() { return this.GLOBAL_CONFIG_DIR; } getGlobalConfig() { return this.globalConfig; } getCacheDir() { return this.GLOBAL_CACHE_DIR; } getOldConfigDir() { return this.OLD_GLOBAL_CONFIG_DIR; } _getCurrentProjectConfigFilePath() { return (0, path_1.join)(process.cwd(), exports.PROJECT_CONFIG_FILENAME); } _getCurrentProjectConfigFileContent() { const filePath = this._getCurrentProjectConfigFilePath(); return (0, safelyGetFileContent_1.safelyGetFileContent)(filePath); } /** * Use this function to pull out the details from the .onboardbase.yaml file * into memory * * The file is expected to be in the current directory where the command is * executed e.g: * $ ~/work/path: onboardbase run env * * A `.onboardbase.yaml` file is expected to be found at * ~/work/path/.onboardbase.yaml * */ _loadCurrentProjectConfig() { const configContent = this._getCurrentProjectConfigFileContent(); return configContent ? YAML.parse(configContent) : {}; } _getOldGlobalConfigPath() { return (0, path_1.join)(HOME_DIR, OLD_GLOBAL_CONFIG_YAML); } /** * * @returns */ _readOldGlobalConfigFromDisk() { try { const filePath = this._getOldGlobalConfigPath(); const fileContent = (0, fs_1.readFileSync)(filePath); (0, fs_1.writeFileSync)(`${filePath}.bak`, fileContent.toString()); return fileContent.toString("utf-8"); } catch (error) { if (error.code !== "ENOENT") { throw errors_1.BadConfigError.from(error.message); } } } _getGlobalConfigPath() { (0, fs_1.mkdirSync)(this.GLOBAL_CONFIG_DIR, { recursive: true }); return (0, path_1.join)(this.GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_FILENAME); } _writeGlobalConfigToDisk(content) { try { const filePath = this._getGlobalConfigPath(); (0, fs_1.writeFileSync)(filePath, content); } catch (error) { return errors_1.BadConfigError.from("unable to syncronise config to disk"); } } _migrateDeprecatedConfigFormat() { if ((0, fs_1.existsSync)(this._getGlobalConfigPath())) return; const oldConfigContent = this._readOldGlobalConfigFromDisk(); if (!oldConfigContent) return; const parsedConent = YAML.parse(oldConfigContent); this._writeGlobalConfigToDisk(JSON.stringify(parsedConent)); // unlinkSync(this._getOldGlobalConfigPath()); // TODO: enable after complete migration } _readGlobalConfigFromDisk(haltOnErr = true) { try { const filePath = this._getGlobalConfigPath(); const content = (0, fs_1.readFileSync)(filePath); return content.toString(); } catch (error) { const instr = `You might need to run ${chalk.bold(chalk.red(`onboardbase login`))} to start setup process`; console.warn(`Could not find global config`); console.info(haltOnErr ? instr : ""); if (haltOnErr) throw errors_1.BadConfigError.from("unable to read global config file from disk"); return ""; } } _checkIfGlobalConfigExists() { return (0, fs_1.existsSync)(this._getGlobalConfigPath()); } _loadGlobalConfig(haltOnErr = true) { this._migrateDeprecatedConfigFormat(); const content = this._readGlobalConfigFromDisk(haltOnErr); if (!content) return DEFAULT_GLOBAL_CONFIGURATION; const parseConfig = JSON.parse(content); return parseConfig; } /** * Merges the current working directory (cwd) configuration with the root configuration. * If a key exists in the root configuration but not in the cwd configuration, it is set to REFPATH_VALUE. * @param cwdConfig The current working directory configuration. * @returns The merged configuration. */ _mergeCWDConfigWithRootConfig(cwdConfig) { // default to empty object if the config is falsy(null | undefined) const rootConfig = this.getFromGlobal(`scoped./`) || {}; // get the key differences const keyNeedle = ["token", "api-host", "dashboard-host", "auth-result"]; keyNeedle.forEach((needle) => { // set REFPATH on keys that exist on rootConfig but not in cwdConfig if (!cwdConfig[needle] && rootConfig[needle]) { cwdConfig[needle] = exports.REFPATH_VALUE; } }); cwdConfig.merged = true; return cwdConfig; } _bootstrapGlobalConfig() { // assumes that global config is already in memory. const cwdConfigPath = `scoped.${process.cwd()}`; // get the cwd config const cwdConfig = this.getFromGlobal(cwdConfigPath) || {}; // return if the cwd config is already merged if (cwdConfig.merged) return cwdConfig; // merge the root config with the global config this._mergeCWDConfigWithRootConfig(cwdConfig); // set the cwd config as the merged config this.setGlobal(cwdConfigPath, cwdConfig); } setScopeConfig(key, value) { const _key = `scoped.${this._CURRENT_DIR_SCOPE}.${key}`; return this.setGlobal(_key, value); } /** * Sets a configuration for the current scope. * @param subPath - Optional sub-path within the current scope. * @returns The configuration for the current scope. */ getScopeConfig(subPath) { return this.getFromGlobal(`scoped.${this._CURRENT_DIR_SCOPE}${subPath ? `.${subPath}` : ""}`); } getProjectConfigStat() { return util.promisify(fs.stat)(`${process.cwd()}/${exports.PROJECT_CONFIG_FILENAME}`); } loadAllConfig(cmdConfig, haltOnErr = true) { this.loadPartialConfig(cmdConfig, haltOnErr); this.projectConfig = this._loadCurrentProjectConfig(); this._bootstrapGlobalConfig(); } // TODO: this method might need to be merged with `loadAllConfig` - but it's currently needed for the `login` command loadPartialConfig(cmdConfig, haltOnErr = true) { this.GLOBAL_CONFIG_DIR = cmdConfig.configDir; this.GLOBAL_CACHE_DIR = cmdConfig.cacheDir; this.globalConfig = this._loadGlobalConfig(haltOnErr); this._setHostURLs(); } _setHostURLs() { ConfigManager.CURRENT_SCOPE_API_HOST = this.getScopeConfig("api-host") || defaults_1.DEFAULT_API_HOST; ConfigManager.CURRENT_SCOPE_DASHBOARD_HOST = this.getScopeConfig("api-host") || defaults_1.DEFAULT_DASHBOARD_HOST; } _stringifyCurrentProjectConfig() { return YAML.stringify(this.projectConfig); } _writeCurrentProjectToDisk(content) { try { const filePath = this._getCurrentProjectConfigFilePath(); (0, fs_1.writeFileSync)(filePath, content); } catch (error) { return ""; } } /** * Involves reading local and global config objects then storing them to disk * Both config objects have their different storage paths * See their implementation for more details */ async persistConfigs() { this._persistCurrentProjectConfig(); const stat = await this.getProjectConfigStat(); this.setScopeConfig("projectConfigLastModified", stat.mtimeMs); this._persistGlobalConfig(); } _persistCurrentProjectConfig() { const content = this._stringifyCurrentProjectConfig(); content.trim() && this._writeCurrentProjectToDisk(content); } _persistGlobalConfig() { const globalConfigStr = JSON.stringify(this.globalConfig); this._writeGlobalConfigToDisk(globalConfigStr); } _get(path, obj) { return path.split(".").reduce((acc, part) => acc && acc[part], obj); } _generateRootConfigKeyFromKey(key) { const splitKey = key.split("."); const baseKey = splitKey[splitKey.length - 1]; return `scoped./.${baseKey}`; } /** * Get a value from the global config * @param scopedKey: the format is `scoped.${scope_path}.${key}` * @example`scoped./.token` - get the token from the root config * @example `scoped./path/to/project/.token` - get the token from scoped config to a project path * @returns */ getFromGlobal(scopedKey) { const value = this._get(scopedKey, this.globalConfig); if (value === exports.REFPATH_VALUE) { // convert to root config scopedKey const rootConfigKey = this._generateRootConfigKeyFromKey(scopedKey); return this.getFromGlobal(rootConfigKey); } return value; } /** * Get a value from the root config. The methood is a wrapper around `getFromGlobal` * @param key the key to get from the root config * @returns */ getFromRootConfig(key) { return this.getFromGlobal(`scoped./.${key}`); } getFromProject(key) { return this._get(key, this.projectConfig); } getFromProcessEnv(key) { return process.env[key]; } setGlobal(key, val) { return this._set(key, val, this.globalConfig); } setDefaultGlobalConfig(data) { const { scope, content } = data; const config = this.globalConfig; if (config === null || config === void 0 ? void 0 : config.scoped) { this.globalConfig = Object.assign(config, { scoped: Object.assign(config.scoped, { [scope]: content, }), }); } else { this.globalConfig = { scoped: { [scope]: content, }, "version-check": {}, }; } this._persistGlobalConfig(); } setProject(key, val) { return this._set(key, val, this.projectConfig); } /** * Sets the value at the specified path in the given source object. * If the path does not exist, it creates the necessary nested objects along the way. * * @param path - The path to set the value at, using dot notation. * @param data - The value to set at the specified path. * @param source - The source object to set the value in. * @returns The modified source object. */ _set(path, data, source) { const splitPath = path.split("."); // key.key1.ney.asdfasdf => 1 return splitPath.reduce((acc, part, ind) => { var _a; if (ind === splitPath.length - 1) { acc[part] = data; } else { acc[part] = (_a = acc[part]) !== null && _a !== void 0 ? _a : {}; } return acc[part]; }, source); } setRSAKeys(data) { this.RSA_KEYS = data; } getRSAKeys() { return this.RSA_KEYS; } setAndPersistGlobalConfig(key, val) { this.setGlobal(key, val); this.persistConfigs(); } getEnvPrefix(userPrefix) { const prefix = userPrefix !== null && userPrefix !== void 0 ? userPrefix : this.getFromProject("setup.prefix"); return prefix; } } exports.ConfigManager = ConfigManager; ConfigManager.CURRENT_SCOPE_API_HOST = defaults_1.DEFAULT_API_HOST; ConfigManager.CURRENT_SCOPE_DASHBOARD_HOST = defaults_1.DEFAULT_DASHBOARD_HOST;