{"id":20499670,"url":"https://github.com/apla/commop","last_synced_at":"2025-04-13T18:52:13.030Z","repository":{"id":36126684,"uuid":"40429801","full_name":"apla/commop","owner":"apla","description":"command line interface for nodejs cli apps","archived":false,"fork":false,"pushed_at":"2017-05-23T13:29:05.000Z","size":58,"stargazers_count":4,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-27T09:41:03.511Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/apla.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-09T08:10:42.000Z","updated_at":"2020-01-14T08:19:17.000Z","dependencies_parsed_at":"2022-09-18T00:51:13.369Z","dependency_job_id":null,"html_url":"https://github.com/apla/commop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apla%2Fcommop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apla%2Fcommop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apla%2Fcommop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/apla%2Fcommop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/apla","download_url":"https://codeload.github.com/apla/commop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248328166,"owners_count":21085262,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T18:18:02.881Z","updated_at":"2025-04-13T18:52:12.997Z","avatar_url":"https://github.com/apla.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CommOp\n\n[![Build Status](https://travis-ci.org/apla/commop.svg?branch=master)](https://travis-ci.org/apla/commop)\n[![NPM Version](http://img.shields.io/npm/v/commop.svg?style=flat)](https://www.npmjs.org/package/commop)\n\nSolution for complex cli interfaces.\n\n## Installation\n\n$ npm install -g commop\n\n### Writing another module\n\nThis is not an option parser module. If you writing small script, which handles one simple task,\nplease use another module, there is many option parsers: [commander](https://github.com/tj/commander.js), [dashdash](https://github.com/trentm/node-dashdash),\n[minimist](https://github.com/substack/minimist) or [yargs](https://github.com/bcoe/yargs).\n\nFor some of my tasks I had to write complicated cli interface. With global options,\noptions per command, command sets, localized messages, environment as fallback/override and so on.\nSo, I decided to write helper module which can use any option parser module for parsing argv and then\nbuild a cli I want.\n\n\n## Example\n\n```javascript\n{\n\t\"options\": {\n\t\t\"verbose\":  {\"type\": \"boolean\", \"global\": true, \"alias\": \"v\"},\n\t\t\"arduino\":  {\"type\": \"string\",  \"global\": true, \"alias\": \"A\"},\n\t\t\"include\":  {\"type\": \"string\",  \"alias\": \"I\"},\n\t\t\"define\":   {\"type\": \"string\",  \"alias\": \"D\"},\n\t\t\"port\":     {\"type\": \"string\",  \"alias\": \"p\"},\n\t\t\"baudrate\": {\"type\": \"number\",  \"alias\": \"r\"},\n\t},\n\t\"commands\": {\n\t\t\"compile\":  {\"options\": [\"inc\", \"define\"], \"run\": \"compile\"},\n\t\t\"upload\":   {\"options\": [\"port\", \"baudrate\"], \"run\": \"upload\"},\n\t\t\"platform\": {\n\t\t\t\"sub\": {\n\t\t\t\t\"add\":    {\"run\": \"addPlatform\"},\n\t\t\t\t\"remove\": {\"run\": \"removePlatform\"}\n\t\t\t}\n\t\t}\n\t}\n}\n```\n\n```javascript\n\nvar commop = require ('commop');\nvar config = require ('./commop.json');\n\nvar launcher = new commop (config);\n\nfunction compile (cmd, next) {\n\tif (cmd.options.inc) {\n\t\t// TODO: make something with inc\n\t}\n\t// code to compile\n}\n\nmodule.exports.compile = compile;\n\nlauncher.start (); // process.argv, module.exports\n\n```\n\nI had to create cli for arduino supporting commands like `compile`, `upload` and `platform`.\nEach task should have `verbose` and `arduino` options.\nAnd `compile` and `upload` commands should have `sketch` and `board` options.\nAfterall `compile` must be configurable via `-D`, includes via `-I` options.\nUpload must support `port` and `baudrate` options.\n`platform` is actually set of commands,\nlike `platform add \u003c\u003e` and `platform remove \u003c\u003e`.\n\n### Option parsing\n\nI've tried a lot of option parsing modules, and found out that it is matter of preference which module to use.\nFor my own tasks (almost) any option parser can handle job. But some needs [advanced validation](https://github.com/trentm/node-dashdash#option-specs),\nin some cases [no requirements](https://github.com/substack/minimist) is a must, and some [loves pirates](https://github.com/bcoe/yargs).\n\n`commop` uses bundled `minimist` module and supports `yargs` and `dashdash` interfaces.\n\n## Interface\n\nThere is no fancy interface to add options. Module takes [configuration](#configuration) and\nlaunches tasks tied to that configuration.\n\n```javascript\n\nvar commop = require ('commop');\nvar config = require ('./commop.json');\n\nvar launcher = new commop (cmdConfig);\n\n// getting command configuration from argv\nvar cmd;\n\n// now you can command configuration\ncmd = launcher.findCommand ();\n// or\ncmd = launcher.findCommand (process.argv);\n// or\ncmd = launcher.findCommand (process.argv);\n\n/*\ncmd: {\n\tconfig: \u003ccommand config from configuration object\u003e\n\tbranch: [ 'library' ], // actual command branch\n\toptions: { verbose: false, debug: false }, // options, accessible by that command\n\tfailedOptions: {}, // if option is required and missed or some options conflicts\n\terrors: [] // another errors\n}\n*/\n\n// launch tasks associated with that command\nlauncher.start ();\n\n// launch tasks associated with that command\nlauncher.start (process.argv, require.main, function (cmd, data) {\n\t// every task is completed\n});\n\n```\n\n## Features\n\n### Command handler(s)\n\n#### Using list of tasks with `run` key\n\nEach command must have a handler, described by `run` key in [command configuration](#configuration-for-command).\nIf handler is an array, then tasks launched one after one. Task laso can be a promise or function,\nwhich return a promise.\n\n```javascript\n/**\n* task function\n* @param {Object}   cmd  command object\n* @param {Object}   data shared object between tasks in current handler\n* @param {Function} next call next if done\n*/\nfunction task (cmd, data, next) {\n\n}\n\n```\n\nCommand objects usually contains `config` key with associated configuration structure,\n`branch` key with list of parsed command names, `positional` with positional parameters,\n`options` — list of applicable options, also `failedOptions` and `errors`\n\nData should be modified and returned via `next` callback or promise's resolve.\nNext task will be launched regardless of return status of previous task.\nIf your task rely on data from previous command, assert data section.\n\nThose tasks can be object methods, you just have to provide an origin as parameter to the `start` call.\nIf origin is not provided, `require.main` is used instead (it is your main module exports).\n\n```javascript\n\nvar theProgram = new Program ();\n\nlauncher.start (null, theProgram, function (cmd, data) {\n});\n\n```\n\n#### Using external script with `script` key\n\nIf you defined `script` key for command, this script will run. You can pass\noptions as environment variables. Script's `stdout` and `stderr` you'll get via\n`data.scriptStderr` and `data.scriptStdout`. If command configuration contains\n`anyway` key, then script error will be written to the `data.scriptError` and\ncallback will be called.\n\n```javascript\n\nvar testConfig2 = {\n\toptions: {\n\t\tBBB: {type: \"string\"},\n\t\tDDD: {type: \"string\"}\n\t},\n\tcommands: {\n\t\tnode: {\n\t\t\tscript: nodePath + \" -e 'console.log (process.argv)' AAA=${BBB} CCC=${DDD}\",\n\t\t\toptions: [\"BBB\", \"DDD\"]\n\t\t}\n\t}\n};\n\nvar co = new CommOp (testConfig2);\n\nco.start (null, null, function (cmd, data) {\n\tconsole.log (data.scriptStderr, data.scriptStdout)\n});\n\n```\n\nAlso, if you need to launch different scripts on different OS'es,\nuse next format:\n\n```javascript\n\nscript: {\n\twin32:  nodePath + \" -e 'console.log (process.argv)' AAA=%BBB% CCC=%DDD%\",\n\tdefault: nodePath + \" -e 'console.log (process.argv)' AAA=${BBB} CCC=${DDD}\"\n}\n\n```\n\n### Usage/help localization\n\nEach message is localizable,for more information please refer `test/04-l10n.js`\n\n### Option sharing\n\nOption can be global or shared between specified commands.\n\n### Implied, conflicting and required options\n\nExamples at `test/02-options.js`\n\n### Usage generator\n\nIf your commands and options have properly defined descriptions,\n`commop` will generate usage automatically. By default, if there is no command specified,\nusage will be displayed automatically. But you can override this behaviour.\n\n```javascript\n\n// usage is displayed automatically unless showUsage is false\nlauncher.showUsage = false;\n\n// usage will be displayed automatically if there is no commands in argv\nlauncher.start ([], null, function (cmd, data) {\n\t// we cannot find any commands\n\tif (\"usage\" in cmd) {\n\t\t// do something like this:\n\t\tif (cmd.branch[0] === 'xxx') {\n\t\t\tlauncher.helpForCommand ([\"xxx\"]);\n\t\t}\n\t}\n\n});\n\n// or, you can generate usage\n\nvar usageString = launcher.usage();\n\n```\n\n### Help generator for command\n\nCommand help displayed automatically if you added a `help` keyword before actual command.\nAlso, you can provide your own help handler by setting `run` key for a `help` command in config.\n\n```javascript\n\n// command help will be displayed automatically if you prefixed command with help keyword\nlauncher.start ([\"help\", \"cmd\"], null, function (cmd, data) {\n\tif (\"usage\" in cmd) {\n\t}\n});\n\n// or, you can generate command help programmatically\n\nvar helpString = launcher.helpForCommand ([\"cmd\"]);\n\n```\n\n## Configuration\n\nConfiguration is a javascript object. Options and commands configurations under\n`options` and `commands` keys.\n\n### Configuration for option\n\n```javascript\n\"options\": {\n\t\"verbose\":  {\n\t\t\"type\": \"boolean\",\n\t\t\"global\": true,\n\t\t\"alias\": \"v\",\n\t\t\"description\": \"be verbose\",\n\t\t\"default\": false,\n\t\t\"env\": \"VERBOSE\"\n\t},\n\t\"arduino\":  {\"type\": \"string\",  \"global\": true, \"alias\": \"A\"},\n\t\"include\":  {\"type\": \"string\",  \"alias\": \"I\"},\n\t\"define\":   {\"type\": \"string\",  \"alias\": \"D\"},\n\t\"port\":     {\"type\": \"string\",  \"alias\": \"p\"},\n\t\"baudrate\": {\"type\": \"number\",  \"alias\": \"r\"},\n}\n```\n\nConfiguration keys:\n\n * `type` handling depends on option parsing module\n\n * `global` options available for all commands\n\n * Single `alias` or list of aliases\n\n * `description` needed for usage/help generation\n\n * `default` value\n\n * `env` keys to use for that option\n\n\n### Configuration for command\n\n```javascript\n\n\"commands\": {\n\t\"compile\":  {\"options\": [\"inc\", \"define\"], \"run\": \"compile\"},\n\t\"upload\":   {\"options\": [\"port\", \"baudrate\"], \"run\": \"upload\"},\n\t\"platform\": {\n\t\t\"sub\": {\n\t\t\t\"add\":    {\"run\": \"addPlatform\"},\n\t\t\t\"remove\": {\"run\": \"removePlatform\"}\n\t\t}\n\t}\n}\n\n```\n\nConfiguration keys:\n\n * `run` task or task set to run command\n\n * `sub` subcommands\n\n * `description` needed for usage/help generation\n\n * `options` is applicable options (`implies` is WIP):\n\n```javascript\n\n// simplest way to use:\noptions: [\"port\", \"baudrate\"]\n\n// complete:\noptions: {\n\t\"port\": {\"required\": true, \"conflicts\": \"board\"},\n\t\"board\": null,\n\t\"baudrate\": {\"implies\": \"port\"}\n}\n\n```\n\n##### Note\n\nImplied/conflicting options works quite badly with booleans.\nUsing boolean options you will allways get value, either\ntrue or false even if that option is not present.\n\n### Additional flags\n\n * `envMode` can be `override` or `fallback`. In fallback mode every missing option\n will be filled from environment variable. In override mode environment values\n takes over precedence over argv options.\n\n * `ignoreUnknownCommands` allows to ignore unknown commands\n\n### See also\n\nhttps://github.com/freeformsystems/cli-command complex, many deps, don't have option relationship, events, +sections, +pod\n\n\n\n## License\n\nMIT\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapla%2Fcommop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fapla%2Fcommop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fapla%2Fcommop/lists"}