@interaktiv/mibuilder-core
Version:
Core libraries to interact with MiBuilder projects.
303 lines (248 loc) • 8.91 kB
JavaScript
;
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;