Repository: DiegoZoracKy/magicli Branch: master Commit: 7c50b8e49dd0 Files: 25 Total size: 27.3 KB Directory structure: gitextract_ynsia_i4/ ├── .gitignore ├── .jshintrc ├── .travis.yml ├── README.md ├── bin/ │ └── cli.js ├── lib/ │ └── magicli.js ├── package.json └── tests/ ├── cli.test.js ├── main.test.js └── test-modules/ ├── function-simple-concat/ │ ├── bin/ │ │ └── cli.js │ ├── lib/ │ │ └── index.js │ ├── package.json │ └── specs.js ├── general-test-module/ │ ├── bin/ │ │ └── cli.js │ ├── lib/ │ │ └── index.js │ ├── package.json │ └── specs.js ├── general-test-module-async/ │ ├── bin/ │ │ └── cli.js │ ├── lib/ │ │ └── index.js │ ├── package.json │ └── specs.js └── object-flat/ ├── bin/ │ └── cli.js ├── lib/ │ └── index.js ├── package.json └── specs.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules/ _private/ package-lock.json ================================================ FILE: .jshintrc ================================================ { "esversion": 6, "node": true } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - 8 - 10 ================================================ FILE: README.md ================================================ # MagiCLI [![Build Status](https://api.travis-ci.org/DiegoZoracKy/magicli.svg)](https://travis-ci.org/DiegoZoracKy/magicli) [![npm](https://img.shields.io/npm/v/magicli.svg)]() [![npm](https://img.shields.io/npm/l/magicli.svg)]() Automagically generates command-line interfaces (CLI), for any module. Just `require('magicli')();` and your module is ready to be executed via CLI. The main goal is to have any module prepared to be executed via CLI (installed globally with `-g`, or by using **npx**): To see why I believe you should plug it on your module, even if you don't need a CLI (it probably will serve someone on the community), read here: [Introducing MagiCLI: Automagically generates a command-line interface (CLI) for any module ](https://hackernoon.com/introducing-magicli-automagically-generates-a-command-line-interface-cli-for-any-module-49543e50f86d) **It can be installed globally, in order to *execute* any module or .js file via CLI.** ## Goals * Minimal setup (*one line*) * Automatic options names based on functions parameters * Out of the box support to async functions (`Promises`, or any *thenable* lib) * A specific help section for each nested property (*"subcommands"*) * *Name*, *Description* and *Version* extracted from package.json * Simple API to hook into the execution flow (*stdin*, *before*, *after*) * Cover all possible cases of module.exports (*Function*, *Object* with nested properties, Destructuring parameters) * Provide a CLI to be used to execute any given module or .js file via CLI ## Usage (the most simple and minimal way) * `npm install magicli` * Add the property **bin** to your package.json containing the value **./bin/magicli.js** * Create the file **./bin/magicli.js** with the following content: ```javascript #!/usr/bin/env node require('magicli')(); ``` **Done!** Install your module with `-g`, or use it via **[npx](http://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner)**, and run it with `--help` to see the result. The `--version` option will show the same value found at *package.json*. In the same way you can just run `node ./bin/magicli.js --help` to test it quickly, without installing it. Let's suppose that **your-module** exports the function: ```javascript module.exports = function(param1, param2) { return param1 + param2; } ``` When calling it via CLI, with `--help`, you will get: ```bash Description: Same description found at package.json Usage: $ your-module [options] Options: --param1 --param2 ``` The program will be expecting options with the same name as the parameters declared at the exported function, and it doesn't need to follow the same order. Example: `$ your-module --param2="K" --param1="Z"` would result in: `ZK`. Important: MagiCLI requires the module in order to analyse it, and provide the command-line interface for it. Keep that in mind in case your module does something just by being required. ## Usage via CLI In order to **execute** any module or .js file via CLI, install it globally: ```bash $ npm install magicli -g ``` Then just pass in as the first argument, the path to a module or a .js file. Examples: * `$ magicli . --help` * `$ magicli ./path/to-some-module --help` * `$ magicli ./path/to-a-file.js --help` Or use it via **[npx](http://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner)** without the need to install it. Let's suppose that you have a simple .js file as this one: ```javascript module.exports = { sum: (n1, n2) => n1 + n2, ec: { ho: str => `${str} !!!` } } ``` Just execute **magicli** on it, as `$ magicli ./path/to-the-file-above.js --help` and you will get: ```bash Commands: sum ec-ho ``` `$ magicli ./path/to-the-file-above.js sum --help` will give you: ```bash Usage: $ sum [options] Options: --n1 --n2 ``` and `$ magicli ./path/to-the-file-above.js sum --n1=4 --n2=2` will result in `6` ### How it works MagiCLI is capable of handling many styles of `exports`, like: * Functions * Object Literal * Nested properties * Class with static methods And also any kind of parameters declaration (*Destructuring Parameters*, *Rest Parameters*). If **your-module** were like this: ```javascript // An Arrow function with Destructuring assignment and Default values const mainMethod = ([p1, [p2]] = ['p1Default', ['p2Default']], { p3 = 'p3Default' } = {}) => `${p1}-${p2}-${p3}`; // Object Literal containing a nested method module.exports = { mainMethod, nested: { method: param => `nested method param value is: "${param}` } }; ``` `$ your-module --help` would result in: ```bash Description: Same description found at package.json Usage: $ your-module Commands: mainMethod nested-method ``` `$ your-module mainMethod --help` would be: ```bash Usage: $ your-module mainMethod [options] Options: --p1 --p2 --p3 ``` `$ your-module nested-method --help` returns: ```bash Usage: $ your-module nested-method [options] Options: --param ``` Calling *mainMethod* without any parameter: `$ your-module mainMethod` results in: ` p1Default-p2Default-p3Default` While defining the parameter for *nested-method*: `$ your-module mainMethod nested-method --param=paramValue` would return: ` nested method param value is: "paramValue"` Note: Nested methods/properties will be turned into commands separated by `-`, and it can be configurable via options (`subcommandDelimiter`). ## Usage Options `magicli({ commands = {}, validateRequiredParameters = false, help = {}, version = {}, pipe = {}, enumerability = 'enumerable', subcommandDelimiter = '-'})` Options are provided to add more information about commands and its options, and also to support a better control of a command execution flow, without the need to change the source code of the module itself (for example, to `JSON.stringify` an `Object Literal` that is returned). ### enumerability By default, only the enumerable nested properties will be considered. The possible values are: `'enumerable'` (default), `'nonenumerable'` or `'all'`. ### validateRequiredParameters MagiCLI can validate the required parameters for a command and show the help in case some of them are missing. The default value is `false`. ### help **help.option** To define a different option name to show the help section. For example, if `'modulehelp'` is chosen, `--modulehelp` must be used instead of `--help` to show the help section. **help.stripAnsi** Set to `true` to strip all ansi escape codes (colors, underline, etc.) and output just a raw text. ### version **version.option** To define a different option name to show the version. For example, if `'moduleversion'` is chosen, `--moduleversion` must be used instead of `--version` to show the version number. ### pipe (stdin, before and after) The pipeline of a command execution is: **stdin** (command.pipe.stdin || magicliOptions.pipe.stdin) => **magicliOptions.pipe.before** => **command.pipe.before** => **command.action** (the method in case) => **command.pipe.after** => **magicliOptions.pipe.after** => **stdout** Where each of these steps can be handled if needed. As it can be defined on *commands* option, for each command, **pipe** can also be defined in *options* to implement a common handler for all commands. The expected properties are: **pipe.stdin** `(stdinValue, args, positionalArgs, argsAfterEndOfOptions)` Useful to get a value from *stdin* and set it to one of the expected *args*. **pipe.before** `(args, positionalArgs, argsAfterEndOfOptions)` To transform the data being input, before it is passed in to the main command action. **pipe.after** `(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)` Note: **stdin** and **before** must always return *args*, and **after** must always return *result*, as these values will be passed in for the next function in the pipeline. ### commands The options are effortlessly extracted from the parameters names, however it is possible to give more information about a command and its options, and also give instructions to the options parser. **commands** expects an `Object Literal` where each key is the command name. It would be the module's name for the main function that is exported, and the command's name as it is shown at the *Commands:* section of `--help`. For example: ```javascript commands: { 'mainmodulename': {}, 'some-nested-method': {} } ``` For each command the following properties can be configurable: #### options Is an *Array* of *Objects*, where each contains: **name** (*required*) The name of the parameter that will be described **required** To tell if the parameter is required. **description** To give hints or explain what the option is about. **type** To define how the parser should treat the option (Array, Object, String, Number, etc.). Check [yargs-parser](https://github.com/yargs/yargs-parser) for instructions about *type*, as it is the engine being used to parse the options. **alias** To define an alias for the option. #### pipe (stdin, before and after) The pipeline of a command execution is: **stdin** (command.pipe.stdin || magicliOptions.pipe.stdin) => **magicliOptions.pipe.before** => **command.pipe.before** => **command.action** (the method in case) => **command.pipe.after** => **magicliOptions.pipe.after** => **stdout** Where each of these steps can be handled if needed. As it can be defined on *options* to implement a common handler for all commands, **pipe** can also be defined for each command. **pipe.stdin** `(stdinValue, args, positionalArgs, argsAfterEndOfOptions)` Useful to get a value from *stdin* and set it to one of the expected *args*. **pipe.before** `(args, positionalArgs, argsAfterEndOfOptions)` To transform the data being input, before it is passed in to the main command action. **pipe.after** `(result, parsedArgs, positionalArgs, argsAfterEndOfOptions)` Note: **stdin** and **before** must always return *args*, and **after** must always return *result*, as these values will be passed in for the next function in the pipeline. If needed, a more thorough guide about this section can be found at [cliss](https://github.com/DiegoZoracKy/cliss) (as this is the module under the hood to handle that) A full featured use of the module would look like: ```javascript magicli({ commands, enumerability, subcommandDelimiter, validateRequiredParameters, help: { option, stripAnsi }, version: { option }, pipe: { stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => {}, before: (args, positionalArgs, argsAfterEndOfOptions) => {}, after: (result, parsedArgs, positionalArgs, argsAfterEndOfOptions) => {} } }); ``` ## Example To better explain with an example, let's get the following module and configure it with MagiCLI to: * Define **p1** as `String` (*mainMethod*) * Write a description for **p2** (*mainMethod*) * Define **p3** as required (*mainMethod*) * Get **p2** from stdin (*mainMethod*) * Use **before** (command) to upper case **param** (*nested-method*) * Use **after** (command) to JSON.stringify the result of (*nested-method*) * Use **after** (options) to decorate all outputs (*nested-method*) **module** ("main" property of package.json) ```javascript 'use strict'; module.exports = { mainMethod: (p1, p2, { p3 = 'p3Default' } = {}) => `${p1}-${p2}-${p3}`, nested: { method: param => { // Example of a Promise being handled return new Promise((resolve, reject) => { setTimeout(() => { resolve({ param }); }, 2000); }); } } }; ``` **magicli.js** ("bin" property of package.json) ```javascript #!/usr/bin/env node require('../magicli')({ commands: { 'mainMethod': { options: [{ name: 'p1', description: 'Number will be converted to String', type: 'String' }, { name: 'p2', description: 'This parameter can be defined via stdin' }, { name: 'p3', required: true }], pipe: { stdin: (stdinValue, args, positionalArgs, argsAfterEndOfOptions) => { args.p2 = stdinValue; return args; } } }, 'nested-method': { options: [{ name: 'param', description: 'Wait for it...' }], pipe: { before: (args, positionalArgs, argsAfterEndOfOptions) => { if (args.param) { args.param = args.param.toUpperCase(); } return args; }, after: JSON.stringify } } }, pipe: { after: (result, positionalArgs, argsAfterEndOfOptions) => `======\n${result}\n======` } }); ``` ## Tests There is another repository called [MagiCLI Test Machine](https://github.com/DiegoZoracKy/magicli-test-machine), where many real published modules are being successfully tested. As the idea is to keep increasing the number of real modules tested, it made more sense to maintain a separated repository for that, instead of being constantly increasing the size of MagiCLI itself over time. I ask you to contribute with the growing numbers of those tests by adding your own module there via a pull request. If you find some case that isn't being handled properly, please open an *issue* or feel free to create a PR ;) ================================================ FILE: bin/cli.js ================================================ #!/usr/bin/env node 'use strict' const { name, description, version } = require('../package.json'); const path = require('path'); const fs = require('fs'); if (!process.argv[2]) { console.error('The first argument must be the path to a module or to a .js file'); return; } const modulePath = path.resolve(process.argv[2]); if (!fs.existsSync(modulePath)) { console.error('The first argument must be a valid path to a module or to a .js file'); return; } process.argv = [process.argv[0], ...process.argv.slice(2)]; require('../')({ modulePath }); ================================================ FILE: lib/magicli.js ================================================ #!/usr/bin/env node 'use strict'; const fs = require('fs'); const path = require('path'); const forEachProperty = require('for-each-property'); const inspectProperty = require('inspect-property'); const cliss = require('cliss'); const findUp = require('find-up'); function requireMainModule(currentPath) { let packageJson = path.resolve(path.join(currentPath, '/package.json')); if (!fs.existsSync(packageJson)) { packageJson = findUp.sync('package.json', { cwd: packageJson }); } const modulePath = path.dirname(packageJson); return { moduleRef: require(modulePath), packageJson: require(packageJson) }; } function requireJsFile(currentPath) { return { moduleRef: require(currentPath), packageJson: {} }; } function magicli({ commands = {}, validateRequiredParameters = false, help = {}, version = {}, pipe = {}, enumerability = 'enumerable', subcommandDelimiter = '-', modulePath } = {}) { const { moduleRef, packageJson } = modulePath && path.extname(modulePath) === '.js' ? requireJsFile(modulePath) : requireMainModule(modulePath || require.main.filename); const moduleName = packageJson.name; const moduleVersion = packageJson.version; const moduleDescription = packageJson.description; const moduleApi = inspectProperty(moduleRef, null, { enumerability, delimiter: subcommandDelimiter, inspectProperties: true }); // CLIss cliSpec const cliSpec = { name: moduleName, version: moduleVersion, description: moduleDescription }; // Main command if (moduleApi.type === 'function') { cliSpec.action = moduleApi.functionInspection.fn; if (commands[moduleName]) { Object.assign(cliSpec, commands[moduleName]); } } // Subcommands forEachProperty(moduleApi.properties, (value, key) => { if (value.type !== 'function' || value.isClass) { return; } cliSpec.commands = cliSpec.commands || []; const subcommand = { name: key, action: value.functionInspection.fn }; if (commands[subcommand.name]) { Object.assign(subcommand, commands[subcommand.name]); } cliSpec.commands.push(subcommand); }); cliss(cliSpec, { options: { validateRequiredParameters }, help, version, pipe }); } module.exports = magicli; ================================================ FILE: package.json ================================================ { "name": "magicli", "version": "0.2.1", "description": "Automagically generates command-line interfaces (CLI) for any module. Expected options and help sections are created automatically based on parameters names, with support to async. It can be installed globally, in order to *execute* any module, or .js file, via CLI.", "main": "lib/magicli.js", "bin": "bin/cli.js", "scripts": { "test": "mocha ./tests", "test-main": "mocha ./tests/main.test.js", "test-cli": "mocha ./tests/cli.test.js" }, "author": { "name": "Diego ZoracKy", "email": "diego.zoracky@gmail.com", "url": "https://github.com/DiegoZoracKy/" }, "repository": { "type": "git", "url": "https://github.com/DiegoZoracKy/magicli" }, "keywords": [ "bin", "cli", "async", "simple", "command-line", "interface" ], "license": "MIT", "dependencies": { "cliss": "0.0.8", "find-up": "^2.1.0", "for-each-property": "0.0.4", "inspect-property": "0.0.7" }, "devDependencies": { "mocha": "^4.0.1" } } ================================================ FILE: tests/cli.test.js ================================================ 'use strict'; const assert = require('assert'); const path = require('path'); const fs = require('fs'); const { exec } = require('child_process'); const testsPath = path.resolve(__dirname, './test-modules'); const tests = [ 'function-simple-concat', 'object-flat' ]; tests.forEach(test => { describe(`CLI :: ${test}`, function() { const modulePath = path.resolve(testsPath, test); const moduleCliPath = path.resolve(__dirname, '../bin/cli.js') + ' ' + modulePath; const specs = require(path.resolve(modulePath, 'specs.js')); specs.forEach(spec => { const { input, output, description, stdin } = spec; it(description || input, function() { return execCli(moduleCliPath, input, stdin).then(result => assert.equal(result, output)); }); }); }); }); function execCli(moduleCliPath, args, stdin = '') { return new Promise((resolve, reject) => { const cmd = `${stdin} node ${moduleCliPath} ${args}`; exec(cmd, (err, stdout, stderr) => { if (err || stderr) { return reject(err || stderr); } resolve(stdout.replace(/ +$/gm, '').replace(/\n$/, '')); }).stdin.end(); }); } ================================================ FILE: tests/main.test.js ================================================ 'use strict'; const assert = require('assert'); const path = require('path'); const fs = require('fs'); const { exec } = require('child_process'); const testsPath = path.resolve(__dirname, './test-modules'); const tests = fs.readdirSync(testsPath); tests.forEach(test => { describe(test, function() { const modulePath = path.resolve(testsPath, test); const packageJson = require(path.resolve(modulePath, 'package.json')); const moduleCliPath = path.resolve(modulePath, packageJson.bin); const specs = require(path.resolve(modulePath, 'specs.js')); specs.forEach(spec => { const { input, output, description, stdin } = spec; it(description || input, function() { return execCli(moduleCliPath, input, stdin).then(result => assert.equal(result, output)); }); }); }); }); function execCli(moduleCliPath, args, stdin = '') { return new Promise((resolve, reject) => { const cmd = `${stdin} node ${moduleCliPath} ${args}`; exec(cmd, (err, stdout, stderr) => { if (err || stderr) { return reject(err || stderr); } resolve(stdout.replace(/ +$/gm, '').replace(/\n$/, '')); }).stdin.end(); }); } ================================================ FILE: tests/test-modules/function-simple-concat/bin/cli.js ================================================ #!/usr/bin/env node 'use strict'; require('../../../../')(); ================================================ FILE: tests/test-modules/function-simple-concat/lib/index.js ================================================ `use strict`; module.exports = (p1, p2) => `${p1} ${p2}`; ================================================ FILE: tests/test-modules/function-simple-concat/package.json ================================================ { "name": "function-simple-concat", "version": "1.0.0", "private": true, "description": "Test function-simple-concat", "main": "lib/index.js", "bin": "bin/cli.js" } ================================================ FILE: tests/test-modules/function-simple-concat/specs.js ================================================ 'use strict'; const tests = [{ description: 'Version --version', input: '--version', output: `1.0.0` }, { description: 'Help --help', input: '--help', output: ` Description: Test function-simple-concat Usage: $ function-simple-concat [options] Options: --p1 --p2 ` }, { description: '--p1=P1 --p2=P2', input: '--p1=P1 --p2=2', output: 'P1 2' }]; module.exports = tests; ================================================ FILE: tests/test-modules/general-test-module/bin/cli.js ================================================ #!/usr/bin/env node 'use strict'; require('../../../../')({ commands: { 'a-b-c-d-e-f': { options: [{ name: 'f1', required: true }], pipe: { after: JSON.stringify } }, 'general-test-module': { pipe: { stdin: (stdinValue, args) => { args.param2 = stdinValue; return args; }, after: JSON.stringify } } }, validateRequiredParameters: true, enumerability: 'all', help: { option: 'modulehelp', stripAnsi: true }, version: { option: 'moduleversion' }, pipe: { after: result => `${result}\n=========` } }); ================================================ FILE: tests/test-modules/general-test-module/lib/index.js ================================================ 'use strict'; const main = function(param1, param2) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`o/ ${param1} ${param2}`); }, 1500); }); }; main.methodA = function(paramA1, paramA2) { return `${paramA1}=${paramA2}`; }; main.methodB = function() { return `${paramB1}-${paramB2}`; }; Object.defineProperty(main, 'methodNonEnumerable', { value: function(paramC1, paramC2) { return `${paramC1}-${paramC2}`; } }); main.a = { b: (b1, b2) => `main.a.b: ${b1} ${b2}` }; main.a.b.c = (c1, c2) => `main.a.b.c: ${c1} ${c2}`; main.a.b.c['d-e'] = {}; main.a.b.c['d-e'].f = ({f1}, [[f2 = 'F2Default']] = [[]]) => ({ f1, f2 }); module.exports = main; ================================================ FILE: tests/test-modules/general-test-module/package.json ================================================ { "name": "general-test-module", "version": "0.0.1", "private": true, "description": "general-test-module description", "main": "./lib/index.js", "bin": "./bin/cli.js" } ================================================ FILE: tests/test-modules/general-test-module/specs.js ================================================ 'use strict'; const tests = [{ description: 'Version with different option --moduleversion', input: '--moduleversion', output: `0.0.1` }, { description: 'Help with different option --modulehelp + help.stripAnsi = true + non-enumerable method', input: '--modulehelp', output: ` Description: general-test-module description Usage: $ general-test-module [options] $ general-test-module [command] Options: --param1 --param2 Commands: methodA methodB methodNonEnumerable a-b a-b-c a-b-c-d-e-f ` },{ description: 'Async + STDIN', stdin: 'echo "PARAM2" | ', input: '--param1=111', output: `"o/ 111 PARAM2" =========` }, { description: 'Method non-enumerable --modulehelp', input: 'methodNonEnumerable --modulehelp', output: ` Usage: $ general-test-module methodNonEnumerable [options] Options: --paramC1 --paramC2 ` }, { description: 'Method non-enumerable call', input: 'methodNonEnumerable --paramC1=val1 --paramC2=val2', output: `val1-val2 =========` }, { description: 'Nested method with no required options', input: 'a-b-c --modulehelp', output: ` Usage: $ general-test-module a-b-c [options] Options: --c1 --c2 ` }, { description: 'Nested method with required options', input: 'a-b-c-d-e-f --modulehelp', output: ` Usage: $ general-test-module a-b-c-d-e-f Options: --f1 Required --f2 ` }, { description: 'Nested method without one required options', input: 'a-b-c-d-e-f --f2=2', output: ` Usage: $ general-test-module a-b-c-d-e-f Options: --f1 Required --f2 ` }, { description: 'Nested method passing only the required option', input: 'a-b-c-d-e-f --f1=2', output: `{"f1":2,"f2":"F2Default"} =========` }]; module.exports = tests; ================================================ FILE: tests/test-modules/general-test-module-async/bin/cli.js ================================================ #!/usr/bin/env node 'use strict'; require('../../../../')({ commands: { 'a-b-c-d-e-f': { options: [{ name: 'f1', required: true }], pipe: { after: JSON.stringify } }, 'general-test-module-async': { pipe: { stdin: (stdinValue, args) => { args.param2 = stdinValue; return args; }, after: JSON.stringify } } }, validateRequiredParameters: true, enumerability: 'all', help: { option: 'modulehelp', stripAnsi: true }, version: { option: 'moduleversion' }, pipe: { after: result => `${result}\n=========` } }); ================================================ FILE: tests/test-modules/general-test-module-async/lib/index.js ================================================ 'use strict'; const main = async function(param1, param2) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(`o/ ${param1} ${param2}`); }, 1500); }); }; main.methodA = async function(paramA1, paramA2) { return `${paramA1}=${paramA2}`; }; main.methodB = async function() { return `${paramB1}-${paramB2}`; }; Object.defineProperty(main, 'methodNonEnumerable', { value: async function(paramC1, paramC2) { return `${paramC1}-${paramC2}`; } }); main.a = { b: async (b1, b2) => `main.a.b: ${b1} ${b2}` }; main.a.b.c = async (c1, c2) => `main.a.b.c: ${c1} ${c2}`; main.a.b.c['d-e'] = {}; main.a.b.c['d-e'].f = async ({f1}, [[f2 = 'F2Default']] = [[]]) => ({ f1, f2 }); module.exports = main; ================================================ FILE: tests/test-modules/general-test-module-async/package.json ================================================ { "name": "general-test-module-async", "version": "0.0.1", "private": true, "description": "general-test-module-async description", "main": "./lib/index.js", "bin": "./bin/cli.js" } ================================================ FILE: tests/test-modules/general-test-module-async/specs.js ================================================ 'use strict'; const tests = [{ description: 'Version with different option --moduleversion', input: '--moduleversion', output: `0.0.1` }, { description: 'Help with different option --modulehelp + help.stripAnsi = true + non-enumerable method', input: '--modulehelp', output: ` Description: general-test-module-async description Usage: $ general-test-module-async [options] $ general-test-module-async [command] Options: --param1 --param2 Commands: methodA methodB methodNonEnumerable a-b a-b-c a-b-c-d-e-f ` },{ description: 'Async + STDIN', stdin: 'echo "PARAM2" | ', input: '--param1=111', output: `"o/ 111 PARAM2" =========` }, { description: 'Method non-enumerable --modulehelp', input: 'methodNonEnumerable --modulehelp', output: ` Usage: $ general-test-module-async methodNonEnumerable [options] Options: --paramC1 --paramC2 ` }, { description: 'Method non-enumerable call', input: 'methodNonEnumerable --paramC1=val1 --paramC2=val2', output: `val1-val2 =========` }, { description: 'Nested method with no required options', input: 'a-b-c --modulehelp', output: ` Usage: $ general-test-module-async a-b-c [options] Options: --c1 --c2 ` }, { description: 'Nested method with required options', input: 'a-b-c-d-e-f --modulehelp', output: ` Usage: $ general-test-module-async a-b-c-d-e-f Options: --f1 Required --f2 ` }, { description: 'Nested method without one required options', input: 'a-b-c-d-e-f --f2=2', output: ` Usage: $ general-test-module-async a-b-c-d-e-f Options: --f1 Required --f2 ` }, { description: 'Nested method passing only the required option', input: 'a-b-c-d-e-f --f1=2', output: `{"f1":2,"f2":"F2Default"} =========` }]; module.exports = tests; ================================================ FILE: tests/test-modules/object-flat/bin/cli.js ================================================ #!/usr/bin/env node 'use strict'; require('../../../../')(); ================================================ FILE: tests/test-modules/object-flat/lib/index.js ================================================ `use strict`; module.exports = { methodA: function(param1, param2) { return `${param1}-${param2}`; }, methodB: (param1, param2) => `${param1-param2}` } ================================================ FILE: tests/test-modules/object-flat/package.json ================================================ { "name": "object-flat", "version": "0.0.0", "private": true, "description": "Test object-flat", "main": "./lib/index.js", "bin": "./bin/cli.js" } ================================================ FILE: tests/test-modules/object-flat/specs.js ================================================ 'use strict'; const tests = [{ description: 'Version --version', input: '--version', output: `0.0.0` },{ description: 'Help --help', input: '--help', output: ` Description: Test object-flat Usage: $ object-flat Commands: methodA methodB ` }, { description: 'methodA --help', input: 'methodA --help', output: ` Usage: $ object-flat methodA [options] Options: --param1 --param2 ` }, { description: 'methodB --help', input: 'methodB --help', output: ` Usage: $ object-flat methodB [options] Options: --param1 --param2 ` }, { description: 'methodA --param2="Z" --param1="K"', input: 'methodA --param2="Z" --param1="K"', output: `K-Z` }, { description: 'methodB --param1=3 --param2=2', input: 'methodB --param1=3 --param2=2', output: `1` }]; module.exports = tests;