diff --git a/package-lock.json b/package-lock.json index 700eba81..c6cf8a00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,11 +93,6 @@ } } }, - "array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==" - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -210,17 +205,6 @@ "get-intrinsic": "^1.0.2" } }, - "command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "requires": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - } - }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", @@ -489,14 +473,6 @@ "unpipe": "~1.0.0" } }, - "find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "requires": { - "array-back": "^3.0.1" - } - }, "follow-redirects": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", @@ -665,11 +641,6 @@ } } }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -1018,11 +989,6 @@ "mime-types": "~2.1.24" } }, - "typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==" - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 631144b1..d6a34687 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "archiver": "^5.3.0", "axios": "^0.26.1", "bcryptjs": "^2.4.3", - "command-line-args": "^5.2.0", "date-and-time": "^2.3.1", "express": "^4.17.1", "express-fileupload": "^1.2.1", diff --git a/prod.js b/prod.js index 20ea004d..22f84234 100644 --- a/prod.js +++ b/prod.js @@ -6,7 +6,7 @@ const optionDefinitions = [ { name: 'source', alias: 's', type: String } ] -const commandLineArgs = require('command-line-args') +const commandLineArgs = require('./server/libs/commandLineArgs') const options = commandLineArgs(optionDefinitions) const Path = require('path') diff --git a/server/libs/commandLineArgs/index.js b/server/libs/commandLineArgs/index.js new file mode 100644 index 00000000..5d727dbb --- /dev/null +++ b/server/libs/commandLineArgs/index.js @@ -0,0 +1,1399 @@ +'use strict'; + +// +// modified for use in audiobookshelf (removed camelCase opt) +// Source: https://github.com/75lb/command-line-args +// + +/** + * Takes any input and guarantees an array back. + * + * - Converts array-like objects (e.g. `arguments`, `Set`) to a real array. + * - Converts `undefined` to an empty array. + * - Converts any another other, singular value (including `null`, objects and iterables other than `Set`) into an array containing that value. + * - Ignores input which is already an array. + * + * @module array-back + * @example + * > const arrayify = require('array-back') + * + * > arrayify(undefined) + * [] + * + * > arrayify(null) + * [ null ] + * + * > arrayify(0) + * [ 0 ] + * + * > arrayify([ 1, 2 ]) + * [ 1, 2 ] + * + * > arrayify(new Set([ 1, 2 ])) + * [ 1, 2 ] + * + * > function f(){ return arrayify(arguments); } + * > f(1,2,3) + * [ 1, 2, 3 ] + */ + +function isObject(input) { + return typeof input === 'object' && input !== null +} + +function isArrayLike(input) { + return isObject(input) && typeof input.length === 'number' +} + +/** + * @param {*} - The input value to convert to an array + * @returns {Array} + * @alias module:array-back + */ +function arrayify(input) { + if (Array.isArray(input)) { + return input + } + + if (input === undefined) { + return [] + } + + if (isArrayLike(input) || input instanceof Set) { + return Array.from(input) + } + + return [input] +} + +/** + * Takes any input and guarantees an array back. + * + * - converts array-like objects (e.g. `arguments`) to a real array + * - converts `undefined` to an empty array + * - converts any another other, singular value (including `null`) into an array containing that value + * - ignores input which is already an array + * + * @module array-back + * @example + * > const arrayify = require('array-back') + * + * > arrayify(undefined) + * [] + * + * > arrayify(null) + * [ null ] + * + * > arrayify(0) + * [ 0 ] + * + * > arrayify([ 1, 2 ]) + * [ 1, 2 ] + * + * > function f(){ return arrayify(arguments); } + * > f(1,2,3) + * [ 1, 2, 3 ] + */ + +function isObject$1(input) { + return typeof input === 'object' && input !== null +} + +function isArrayLike$1(input) { + return isObject$1(input) && typeof input.length === 'number' +} + +/** + * @param {*} - the input value to convert to an array + * @returns {Array} + * @alias module:array-back + */ +function arrayify$1(input) { + if (Array.isArray(input)) { + return input + } else { + if (input === undefined) { + return [] + } else if (isArrayLike$1(input)) { + return Array.prototype.slice.call(input) + } else { + return [input] + } + } +} + +/** + * Find and either replace or remove items in an array. + * + * @module find-replace + * @example + * > const findReplace = require('find-replace') + * > const numbers = [ 1, 2, 3] + * + * > findReplace(numbers, n => n === 2, 'two') + * [ 1, 'two', 3 ] + * + * > findReplace(numbers, n => n === 2, [ 'two', 'zwei' ]) + * [ 1, [ 'two', 'zwei' ], 3 ] + * + * > findReplace(numbers, n => n === 2, 'two', 'zwei') + * [ 1, 'two', 'zwei', 3 ] + * + * > findReplace(numbers, n => n === 2) // no replacement, so remove + * [ 1, 3 ] + */ + +/** + * @param {array} - The input array + * @param {testFn} - A predicate function which, if returning `true` causes the current item to be operated on. + * @param [replaceWith] {...any} - If specified, found values will be replaced with these values, else removed. + * @returns {array} + * @alias module:find-replace + */ +function findReplace(array, testFn) { + const found = []; + const replaceWiths = arrayify$1(arguments); + replaceWiths.splice(0, 2); + + arrayify$1(array).forEach((value, index) => { + let expanded = []; + replaceWiths.forEach(replaceWith => { + if (typeof replaceWith === 'function') { + expanded = expanded.concat(replaceWith(value)); + } else { + expanded.push(replaceWith); + } + }); + + if (testFn(value)) { + found.push({ + index: index, + replaceWithValue: expanded + }); + } + }); + + found.reverse().forEach(item => { + const spliceArgs = [item.index, 1].concat(item.replaceWithValue); + array.splice.apply(array, spliceArgs); + }); + + return array +} + +/** + * Some useful tools for working with `process.argv`. + * + * @module argv-tools + * @typicalName argvTools + * @example + * const argvTools = require('argv-tools') + */ + +/** + * Regular expressions for matching option formats. + * @static + */ +const re = { + short: /^-([^\d-])$/, + long: /^--(\S+)/, + combinedShort: /^-[^\d-]{2,}$/, + optEquals: /^(--\S+?)=(.*)/ +}; + +/** + * Array subclass encapsulating common operations on `process.argv`. + * @static + */ +class ArgvArray extends Array { + /** + * Clears the array has loads the supplied input. + * @param {string[]} argv - The argv list to load. Defaults to `process.argv`. + */ + load(argv) { + this.clear(); + if (argv && argv !== process.argv) { + argv = arrayify(argv); + } else { + /* if no argv supplied, assume we are parsing process.argv */ + argv = process.argv.slice(0); + const deleteCount = process.execArgv.some(isExecArg) ? 1 : 2; + argv.splice(0, deleteCount); + } + argv.forEach(arg => this.push(String(arg))); + } + + /** + * Clear the array. + */ + clear() { + this.length = 0; + } + + /** + * expand ``--option=value` style args. + */ + expandOptionEqualsNotation() { + if (this.some(arg => re.optEquals.test(arg))) { + const expandedArgs = []; + this.forEach(arg => { + const matches = arg.match(re.optEquals); + if (matches) { + expandedArgs.push(matches[1], matches[2]); + } else { + expandedArgs.push(arg); + } + }); + this.clear(); + this.load(expandedArgs); + } + } + + /** + * expand getopt-style combinedShort options. + */ + expandGetoptNotation() { + if (this.hasCombinedShortOptions()) { + findReplace(this, re.combinedShort, expandCombinedShortArg); + } + } + + /** + * Returns true if the array contains combined short options (e.g. `-ab`). + * @returns {boolean} + */ + hasCombinedShortOptions() { + return this.some(arg => re.combinedShort.test(arg)) + } + + static from(argv) { + const result = new this(); + result.load(argv); + return result + } +} + +/** + * Expand a combined short option. + * @param {string} - the string to expand, e.g. `-ab` + * @returns {string[]} + * @static + */ +function expandCombinedShortArg(arg) { + /* remove initial hypen */ + arg = arg.slice(1); + return arg.split('').map(letter => '-' + letter) +} + +/** + * Returns true if the supplied arg matches `--option=value` notation. + * @param {string} - the arg to test, e.g. `--one=something` + * @returns {boolean} + * @static + */ +function isOptionEqualsNotation(arg) { + return re.optEquals.test(arg) +} + +/** + * Returns true if the supplied arg is in either long (`--one`) or short (`-o`) format. + * @param {string} - the arg to test, e.g. `--one` + * @returns {boolean} + * @static + */ +function isOption(arg) { + return (re.short.test(arg) || re.long.test(arg)) && !re.optEquals.test(arg) +} + +/** + * Returns true if the supplied arg is in long (`--one`) format. + * @param {string} - the arg to test, e.g. `--one` + * @returns {boolean} + * @static + */ +function isLongOption(arg) { + return re.long.test(arg) && !isOptionEqualsNotation(arg) +} + +/** + * Returns the name from a long, short or `--options=value` arg. + * @param {string} - the arg to inspect, e.g. `--one` + * @returns {string} + * @static + */ +function getOptionName(arg) { + if (re.short.test(arg)) { + return arg.match(re.short)[1] + } else if (isLongOption(arg)) { + return arg.match(re.long)[1] + } else if (isOptionEqualsNotation(arg)) { + return arg.match(re.optEquals)[1].replace(/^--/, '') + } else { + return null + } +} + +function isValue(arg) { + return !(isOption(arg) || re.combinedShort.test(arg) || re.optEquals.test(arg)) +} + +function isExecArg(arg) { + return ['--eval', '-e'].indexOf(arg) > -1 || arg.startsWith('--eval=') +} + +/** + * For type-checking Javascript values. + * @module typical + * @typicalname t + * @example + * const t = require('typical') + */ + +/** + * Returns true if input is a number + * @param {*} - the input to test + * @returns {boolean} + * @static + * @example + * > t.isNumber(0) + * true + * > t.isNumber(1) + * true + * > t.isNumber(1.1) + * true + * > t.isNumber(0xff) + * true + * > t.isNumber(0644) + * true + * > t.isNumber(6.2e5) + * true + * > t.isNumber(NaN) + * false + * > t.isNumber(Infinity) + * false + */ +function isNumber(n) { + return !isNaN(parseFloat(n)) && isFinite(n) +} + +/** + * A plain object is a simple object literal, it is not an instance of a class. Returns true if the input `typeof` is `object` and directly decends from `Object`. + * + * @param {*} - the input to test + * @returns {boolean} + * @static + * @example + * > t.isPlainObject({ something: 'one' }) + * true + * > t.isPlainObject(new Date()) + * false + * > t.isPlainObject([ 0, 1 ]) + * false + * > t.isPlainObject(/test/) + * false + * > t.isPlainObject(1) + * false + * > t.isPlainObject('one') + * false + * > t.isPlainObject(null) + * false + * > t.isPlainObject((function * () {})()) + * false + * > t.isPlainObject(function * () {}) + * false + */ +function isPlainObject(input) { + return input !== null && typeof input === 'object' && input.constructor === Object +} + +/** + * An array-like value has all the properties of an array, but is not an array instance. Examples in the `arguments` object. Returns true if the input value is an object, not null and has a `length` property with a numeric value. + * + * @param {*} - the input to test + * @returns {boolean} + * @static + * @example + * function sum(x, y){ + * console.log(t.isArrayLike(arguments)) + * // prints `true` + * } + */ +function isArrayLike$2(input) { + return isObject$2(input) && typeof input.length === 'number' +} + +/** + * returns true if the typeof input is `'object'`, but not null! + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isObject$2(input) { + return typeof input === 'object' && input !== null +} + +/** + * Returns true if the input value is defined + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isDefined(input) { + return typeof input !== 'undefined' +} + +/** + * Returns true if the input value is a string + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isString(input) { + return typeof input === 'string' +} + +/** + * Returns true if the input value is a boolean + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isBoolean(input) { + return typeof input === 'boolean' +} + +/** + * Returns true if the input value is a function + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isFunction(input) { + return typeof input === 'function' +} + +/** + * Returns true if the input value is an es2015 `class`. + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isClass(input) { + if (isFunction(input)) { + return /^class /.test(Function.prototype.toString.call(input)) + } else { + return false + } +} + +/** + * Returns true if the input is a string, number, symbol, boolean, null or undefined value. + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isPrimitive(input) { + if (input === null) return true + switch (typeof input) { + case 'string': + case 'number': + case 'symbol': + case 'undefined': + case 'boolean': + return true + default: + return false + } +} + +/** + * Returns true if the input is a Promise. + * @param {*} - the input to test + * @returns {boolean} + * @static + */ +function isPromise(input) { + if (input) { + const isPromise = isDefined(Promise) && input instanceof Promise; + const isThenable = input.then && typeof input.then === 'function'; + return !!(isPromise || isThenable) + } else { + return false + } +} + +/** + * Returns true if the input is an iterable (`Map`, `Set`, `Array`, Generator etc.). + * @param {*} - the input to test + * @returns {boolean} + * @static + * @example + * > t.isIterable('string') + * true + * > t.isIterable(new Map()) + * true + * > t.isIterable([]) + * true + * > t.isIterable((function * () {})()) + * true + * > t.isIterable(Promise.resolve()) + * false + * > t.isIterable(Promise) + * false + * > t.isIterable(true) + * false + * > t.isIterable({}) + * false + * > t.isIterable(0) + * false + * > t.isIterable(1.1) + * false + * > t.isIterable(NaN) + * false + * > t.isIterable(Infinity) + * false + * > t.isIterable(function () {}) + * false + * > t.isIterable(Date) + * false + * > t.isIterable() + * false + * > t.isIterable({ then: function () {} }) + * false + */ +function isIterable(input) { + if (input === null || !isDefined(input)) { + return false + } else { + return ( + typeof input[Symbol.iterator] === 'function' || + typeof input[Symbol.asyncIterator] === 'function' + ) + } +} + +var t = { + isNumber, + isString, + isBoolean, + isPlainObject, + isArrayLike: isArrayLike$2, + isObject: isObject$2, + isDefined, + isFunction, + isClass, + isPrimitive, + isPromise, + isIterable +}; + +/** + * @module option-definition + */ + +/** + * Describes a command-line option. Additionally, if generating a usage guide with [command-line-usage](https://github.com/75lb/command-line-usage) you could optionally add `description` and `typeLabel` properties to each definition. + * + * @alias module:option-definition + * @typicalname option + */ +class OptionDefinition { + constructor(definition) { + /** + * The only required definition property is `name`, so the simplest working example is + * ```js + * const optionDefinitions = [ + * { name: 'file' }, + * { name: 'depth' } + * ] + * ``` + * + * Where a `type` property is not specified it will default to `String`. + * + * | # | argv input | commandLineArgs() output | + * | --- | -------------------- | ------------ | + * | 1 | `--file` | `{ file: null }` | + * | 2 | `--file lib.js` | `{ file: 'lib.js' }` | + * | 3 | `--depth 2` | `{ depth: '2' }` | + * + * Unicode option names and aliases are valid, for example: + * ```js + * const optionDefinitions = [ + * { name: 'один' }, + * { name: '两' }, + * { name: 'три', alias: 'т' } + * ] + * ``` + * @type {string} + */ + this.name = definition.name; + + /** + * The `type` value is a setter function (you receive the output from this), enabling you to be specific about the type and value received. + * + * The most common values used are `String` (the default), `Number` and `Boolean` but you can use a custom function, for example: + * + * ```js + * const fs = require('fs') + * + * class FileDetails { + * constructor (filename) { + * this.filename = filename + * this.exists = fs.existsSync(filename) + * } + * } + * + * const cli = commandLineArgs([ + * { name: 'file', type: filename => new FileDetails(filename) }, + * { name: 'depth', type: Number } + * ]) + * ``` + * + * | # | argv input | commandLineArgs() output | + * | --- | ----------------- | ------------ | + * | 1 | `--file asdf.txt` | `{ file: { filename: 'asdf.txt', exists: false } }` | + * + * The `--depth` option expects a `Number`. If no value was set, you will receive `null`. + * + * | # | argv input | commandLineArgs() output | + * | --- | ----------------- | ------------ | + * | 2 | `--depth` | `{ depth: null }` | + * | 3 | `--depth 2` | `{ depth: 2 }` | + * + * @type {function} + * @default String + */ + this.type = definition.type || String; + + /** + * getopt-style short option names. Can be any single character (unicode included) except a digit or hyphen. + * + * ```js + * const optionDefinitions = [ + * { name: 'hot', alias: 'h', type: Boolean }, + * { name: 'discount', alias: 'd', type: Boolean }, + * { name: 'courses', alias: 'c' , type: Number } + * ] + * ``` + * + * | # | argv input | commandLineArgs() output | + * | --- | ------------ | ------------ | + * | 1 | `-hcd` | `{ hot: true, courses: null, discount: true }` | + * | 2 | `-hdc 3` | `{ hot: true, discount: true, courses: 3 }` | + * + * @type {string} + */ + this.alias = definition.alias; + + /** + * Set this flag if the option takes a list of values. You will receive an array of values, each passed through the `type` function (if specified). + * + * ```js + * const optionDefinitions = [ + * { name: 'files', type: String, multiple: true } + * ] + * ``` + * + * Note, examples 1 and 3 below demonstrate "greedy" parsing which can be disabled by using `lazyMultiple`. + * + * | # | argv input | commandLineArgs() output | + * | --- | ------------ | ------------ | + * | 1 | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` | + * | 2 | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` | + * | 3 | `--files *` | `{ files: [ 'one.js', 'two.js' ] }` | + * + * @type {boolean} + */ + this.multiple = definition.multiple; + + /** + * Identical to `multiple` but with greedy parsing disabled. + * + * ```js + * const optionDefinitions = [ + * { name: 'files', lazyMultiple: true }, + * { name: 'verbose', alias: 'v', type: Boolean, lazyMultiple: true } + * ] + * ``` + * + * | # | argv input | commandLineArgs() output | + * | --- | ------------ | ------------ | + * | 1 | `--files one.js --files two.js` | `{ files: [ 'one.js', 'two.js' ] }` | + * | 2 | `-vvv` | `{ verbose: [ true, true, true ] }` | + * + * @type {boolean} + */ + this.lazyMultiple = definition.lazyMultiple; + + /** + * Any values unaccounted for by an option definition will be set on the `defaultOption`. This flag is typically set on the most commonly-used option to make for more concise usage (i.e. `$ example *.js` instead of `$ example --files *.js`). + * + * ```js + * const optionDefinitions = [ + * { name: 'files', multiple: true, defaultOption: true } + * ] + * ``` + * + * | # | argv input | commandLineArgs() output | + * | --- | ------------ | ------------ | + * | 1 | `--files one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` | + * | 2 | `one.js two.js` | `{ files: [ 'one.js', 'two.js' ] }` | + * | 3 | `*` | `{ files: [ 'one.js', 'two.js' ] }` | + * + * @type {boolean} + */ + this.defaultOption = definition.defaultOption; + + /** + * An initial value for the option. + * + * ```js + * const optionDefinitions = [ + * { name: 'files', multiple: true, defaultValue: [ 'one.js' ] }, + * { name: 'max', type: Number, defaultValue: 3 } + * ] + * ``` + * + * | # | argv input | commandLineArgs() output | + * | --- | ------------ | ------------ | + * | 1 | | `{ files: [ 'one.js' ], max: 3 }` | + * | 2 | `--files two.js` | `{ files: [ 'two.js' ], max: 3 }` | + * | 3 | `--max 4` | `{ files: [ 'one.js' ], max: 4 }` | + * + * @type {*} + */ + this.defaultValue = definition.defaultValue; + + /** + * When your app has a large amount of options it makes sense to organise them in groups. + * + * There are two automatic groups: `_all` (contains all options) and `_none` (contains options without a `group` specified in their definition). + * + * ```js + * const optionDefinitions = [ + * { name: 'verbose', group: 'standard' }, + * { name: 'help', group: [ 'standard', 'main' ] }, + * { name: 'compress', group: [ 'server', 'main' ] }, + * { name: 'static', group: 'server' }, + * { name: 'debug' } + * ] + * ``` + * + *
# | Command Line | commandLineArgs() output | + *
---|---|---|
1 | --verbose |
|
+ *
2 | --debug |
|
+ *
3 | --verbose --debug --compress |
|
+ *
4 | --compress |
|
+ *