UNPKG

@interaktiv/mibuilder-core

Version:

Core libraries to interact with MiBuilder projects.

303 lines (248 loc) 8.91 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigFile = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _path = _interopRequireDefault(require("path")); var _dxl = require("@interaktiv/dxl"); var _types = require("@interaktiv/types"); var _mibuilderError = require("../mibuilder-error"); var _constants = require("./constants"); var _configStore = require("./config-store"); var _resolveConfigPath = require("./resolve-config-path"); function getHomeDir(os) { return process.env.HOME || this.windows && this.windowsHome() || os.homedir() || os.tmpdir(); } /** * Represents a json config file used to manage settings and state. Global * config files are stored in the home directory hidden state folder * (.mibuilder) and local config files are stored in the project path, either * in the hidden state folder or wherever specified. * * **Do not directly construct instances of this class -- use {@link ConfigFile.create} instead.** * * ``` * class MyConfig extends ConfigFile { * public static getFileName(): string { * return 'myConfigFilename.json'; * } * } * * const myConfig = await MyConfig.create({ * isGlobal: true * }); * * myConfig.set('mykey', 'myvalue'); * * await myConfig.write(); * ``` */ class ConfigFile extends _configStore.BaseConfigStore { /** * Returns the config's filename. */ static getFileName() { // Can not have abstract static methods, so throw a runtime error. throw new _mibuilderError.MiBuilderError('Unknown filename for config file.'); } /** * Returns the default options for the config file. * * @param {Boolean} isGlobal If the file should be stored globally or locally * @param {String} filename The name of the config file * @return {Object} The default options */ // eslint-disable-next-line default-param-last static getDefaultOptions(isGlobal = false, filename) { return { isGlobal, isState: true, filename: filename || this.getFileName() }; } static getDefaultContents(config = {}) { const { home: homeDir = getHomeDir(require('os')) } = config; const sshDir = _path.default.join(homeDir, '.ssh'); // INFO: Changes here need to be reflected in the `./config.js` module see // the `CONFIG_GROUP` constant and the àllowedProperties` prop return { app: { workspace: _path.default.resolve(homeDir, 'Documents', 'Appcelerator_Studio_Workspace') }, template: { sshPrivateKeyPath: _path.default.resolve(sshDir, 'bitbucket_miapps'), sshPublicKeyPath: _path.default.resolve(sshDir, 'bitbucket_miapps.pub') }, api: { getContentEndpoint: 'https://mi-app.deubner-api.de/getContent/%s' }, android: { googlePlayJsonKeyPath: _path.default.resolve(_constants.MIBUILDER_PROJECT_JSON_DIR, 'google-play-service-account-key.json') }, match: { gitUrl: 'https://x-token-auth:%{access_token}@bitbucket.org/deubner-apps/certificates' }, flint: { gitUrl: 'https://x-token-auth:%{access_token}@bitbucket.org/deubner-apps/keystores' } }; } /** * Helper used to determined what the local and global folder point to. * Returns the file path of the root folder. * * @param {Boolean} isGlobal True if the config should be global. False for local. * @return {Promise<String>} A promise that resolves with the global or project (local) dir */ static resolveRootDir(isGlobal) { if ((0, _types.isBoolean)(isGlobal) === false) { throw new _mibuilderError.MiBuilderError('isGlobal must be a boolean', 'InvalidTypeForIsGlobal'); } if (isGlobal) return Promise.resolve(getHomeDir(require('os'))); return (0, _resolveConfigPath.resolveConfigPath)('', process.cwd()); } /** * Determines if the config file is read/write accessible. Returns `true` if * the user has capabilities specified by perm. * * **See** {@link https://nodejs.org/dist/latest/docs/api/fs.html#fs_fs_access_path_mode_callback} * * @param {number} perm The permission. * @return {Boolean} Returns true if the use has read/write access specified by perm */ async access(perm) { try { await (0, _dxl.access)(this.getPath(), perm); return true; } catch (err) { return false; } } /** * Read the config file and set the config contents. Returns the config * contents of the config file. * * **Throws** *{@link MiBuilderError}{ name: 'UnexpectedJsonFileFormat' }* There was a problem reading or parsing the file. * * @param {Boolean} [throwOnNotFound=false] Optionally indicate if a throw should occur on file read. * @return {Object} The contents */ async read(throwOnNotFound = false) { try { const obj = await (0, _dxl.readJsonMap)(this.getPath()); this.setContentsFromObject(obj); return this.getContents(); } catch (err) { if (err.code === 'ENOENT') { if (throwOnNotFound === false) { this.setContents(); return this.getContents(); } } throw err; } } /** * Write the config file with new contents. If no new contents are provided * it will write the existing config contents that were set from * {@link ConfigFile.read}, or an empty file if {@link ConfigFile.read} was * not called. * * @param {Object} newContents The new contents of the file. * @return {Object} The content */ async write(newContents) { if (newContents != null) { this.setContents(newContents); } await (0, _dxl.mkdirp)(_path.default.dirname(this.getPath())); await (0, _dxl.outputJson)(this.getPath(), this.toObject()); return this.getContents(); } /** * Check to see if the config file exists. Returns `true` if the config file * exists and has access, false otherwise. * * @return {Promise} A promise resolved with the access stat */ exists() { return this.access(_dxl.constants.R_OK); } /** * Get the stats of the file. Returns the stats of the file. * * {@link stat} * @return {Promise} A promise resolves with stat */ stat() { return (0, _dxl.stat)(this.getPath()); } /** * Delete the config file if it exists. Returns `true` if the file was * deleted, `false` otherwise. * * {@link unlink} */ async unlink() { const exists = await this.exists(); if (exists) { return (0, _dxl.unlink)(this.getPath()); } throw new _mibuilderError.MiBuilderError(`Target file doesn't exist. Path: ${this.getPath()}`, 'TargetFileNotFound'); } /** * Returns the path to the config file. * * @return {String} The path to the file */ getPath() { return this.path; } /** * Returns `true` if this config is using the global path, `false` otherwise. * * @return {Boolean} Returns true if this config is global */ isGlobal() { return !!this.options.isGlobal; } /** * Used to initialize asynchronous components. * * **Throws** *`Error`{ code: 'ENOENT' }* If the * {@link ConfigFile.getFilename} file is not found when * options.throwOnNotFound is true. */ async init() { const statics = this.constructor; let defaultOptions = {}; try { defaultOptions = statics.getDefaultOptions(); } catch (e) { /* Some implementations don't let you call default options */ } // Merge default and passed in options this.options = (0, _extends2.default)({}, defaultOptions, this.options); if (!this.options.filename) { throw new _mibuilderError.MiBuilderError('The ConfigOptions filename parameter is invalid.', 'InvalidParameter'); } const isGlobal = (0, _types.isBoolean)(this.options.isGlobal) && this.options.isGlobal; const isState = (0, _types.isBoolean)(this.options.isState) && this.options.isState; // Don't let users store config files in homedir without being in the // state folder. let rootDir = this.options.rootDir ? this.options.rootDir : await ConfigFile.resolveRootDir(!!this.options.isGlobal); if (isGlobal || isState) { rootDir = _path.default.join(rootDir, _constants.CONFIG_STATE_FOLDER); } this.path = _path.default.join(rootDir, this.options.filePath ? this.options.filePath : '', this.options.filename); const contents = await this.read(this.options.throwOnNotFound); let defaultContents = {}; try { defaultContents = statics.getDefaultContents(); await this.write((0, _extends2.default)({}, defaultContents, contents)); } catch (err) {// Some implementations don't let you call default contents } } } exports.ConfigFile = ConfigFile;