const readFileSync = require('fs').readFileSync;
const path = require('path');
const mri = require('mri');
/**
* @module module:crx3/lib/configuration
*/
/**
* Create Configuration object and set its properties.
*
* @alias module:crx3/lib/configuration
* @param {object} [options]
* @return {module:crx3/lib/configuration.Configuration}
*/
module.exports = function createConfiguration (options) {
var config = new Configuration();
if (options && typeof options === 'object') {
config.setFromOptions(options);
}
return config;
};
/**
* @private
*/
const NUMBER_OF_IGNORED_CLI_ARGS = 2;
/**
* @private
*/
const CWD = process.env.PWD || process.cwd();
/**
* @private
*/
const DEFAULT_CRX_PATH = path.join(CWD, 'web-extension.crx');
/**
* @private
*/
const DEFAULT_ZIP_PATH = path.join(CWD, 'web-extension.zip');
/**
* @private
*/
const DEFAULT_KEY_PATH = path.join(CWD, 'web-extension.pem');
/**
* @private
*/
const DEFAULT_XML_PATH = path.join(CWD, 'web-extension.xml');
/**
* @example
* const config = require('crx/lib/configuration');
* var options = config();
*
* @name module:crx3/lib/configuration.Configuration
* @class
*/
function Configuration () {
/**
* Name used for files, if they are not specified otherwise.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#name
*/
this.name = '';
/**
* Path name of output CRX file.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#crxPath
* @default './web-extension.crx'
*/
this.crxPath = DEFAULT_CRX_PATH;
/**
* Optional path name of output ZIP file.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#zipPath
* @default ''
*/
this.zipPath = '';
/**
* Private key to be used for signing CRX file.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#keyPath
* @default ''
*/
this.keyPath = '';
/**
* Optional path name of output Update Manifest XML file.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#xmlPath
* @default ''
*/
this.xmlPath = '';
/**
* Optional version name to be written into Upate Manifest file.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#appVersion
* @default undefined
*/
this.appVersion = undefined; // eslint-disable-line no-undefined
/**
* Optional extension file URL name to be written into Upate Manifest file.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#crxURL
* @default undefined
*/
this.crxURL = undefined; // eslint-disable-line no-undefined
/**
* Optional minimum supported browser version name, e.g., '70.0.0'.
*
* @type {string}
* @name module:crx3/lib/configuration.Configuration#browserVersion
* @default undefined
*/
this.browserVersion = undefined; // eslint-disable-line no-undefined
/**
* List of paths to include.
*
* @type {string[]}
* @name module:crx3/lib/configuration.Configuration#srcPaths
* @default []
*/
this.srcPaths = [];
/**
* File creation time (UNIX timestamp) to be used for files and directories stored in the ZIP file.
* Leave unchanged to keep values as found in the file system.
* **WARNING**: This is supported only when creating new ZIP file!
*
* @type {number}
* @name module:crx3/lib/configuration.Configuration#forceDateTime
* @default 0
*/
this.forceDateTime = 0;
}
/**
* Parse command line arguments and apply them as options.
*
* @return {Configuration} this
*/
Configuration.prototype.setFromArgv = function setFromArgv () {
const argv = mri(process.argv.slice(NUMBER_OF_IGNORED_CLI_ARGS), {
alias: {
crxPath: ['o', 'crx'],
zipPath: ['z', 'zip'],
keyPath: ['p', 'key'],
xmlPath: ['x', 'xml']
}
});
if (Array.isArray(argv._) && argv._.length > 0) {
this.srcPaths = argv._.slice();
}
return this.setFromOptions(argv);
};
/**
* Pick properties from target options object if their name matches supported option.
*
* @param {object} options
* @return {Configuration} this
*/
Configuration.prototype.setFromOptions = function setFromOptions (options = {}) {
Object.keys(this).forEach(key => {
this[key] = Reflect.has(options, key) ? options[key] : this[key];
});
return this.sanitize();
};
/**
* Make sure that option values are of correct type, converting them when needed.
*
* return {Configuration} `this`
*/
Configuration.prototype.sanitize = function sanitize () {
if (!this.name) {
if (this.srcPaths.length === 1 && path.extname(this.srcPaths[0]) === '') {
this.name = path.basename(this.srcPaths[0]);
}
else if (this.srcPaths.length > 0) {
var manifest = this.srcPaths.filter(p => path.basename(p) === 'manifest.json');
if (manifest.length === 1) {
this.name = path.basename(path.dirname(manifest[0]));
}
}
}
this.sanitizePath('crxPath', 'crx', DEFAULT_CRX_PATH);
this.sanitizePath('zipPath', 'zip', DEFAULT_ZIP_PATH);
this.sanitizePath('keyPath', 'pem', DEFAULT_KEY_PATH);
this.sanitizePath('xmlPath', 'xml', DEFAULT_XML_PATH);
this.crxPath = path.resolve(CWD, this.crxPath);
return this;
};
/**
* Set path name based on this.name or default value.
* But only if property is already non-empty.
* Otherwise leave it empty.
*
* @private
* @param {string} property property name, e.g., 'zipPath'
* @param {string} extension file extension, e.g., 'zip'
* @param {string} fallback value to use by default, e.g., '${process.env.PWD}/web-extension.zip'
*/
Configuration.prototype.sanitizePath = function sanitizePath (property, extension, fallback) {
if (this[property] === true || this[property] === fallback) {
this[property] = this.name ? `${this.name}.${extension}` : fallback;
}
if (this[property]) {
this[property] = path.resolve(CWD, this[property]);
}
};
/**
* Return a "help text", suitable to output in CLI environment.
*
* @return {string}
*/
Configuration.prototype.helpText = function helpText () {
const name = path.basename(require.main.filename);
const readmePath = path.join(path.dirname(__dirname), 'README.md');
const readme = readFileSync(readmePath, 'utf8');
const readmeCLI = readme.match(/[#]+ Usage \(CLI\)\n(?<usage>[\w\W]+?)\n+[#]+/);
const usage = (readmeCLI && readmeCLI.groups.usage)
|| `Read ${readmePath} for more information.`;
return `Usage: ${name} [-o [path]] [-k [path]] [-z [path]] [-x [path]] directoryOrManifest
${usage}`;
};