├── .editorconfig ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .iron-node.js ├── .nclirc ├── .npmrc ├── .travis.yml ├── API.md ├── COMMANDLINE-ARGUMENTS.md ├── LICENSE ├── README.md ├── examples └── example.js ├── help-demo.png ├── lib ├── error.js └── index.js ├── package-lock.json ├── package.json └── test ├── example.spec.js └── node-cli.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true, 4 | "node": true 5 | }, 6 | "rules":{ 7 | "no-trailing-spaces" : "off", 8 | "no-multiple-empty-lines":"off", 9 | "space-before-function-paren" : "off", 10 | "keyword-spacing" : "off", 11 | "indent" : "off", 12 | "quotes": ["warn", "double", "avoid-escape"], 13 | "eol-last" : "off", 14 | "space-before-blocks" : "off", 15 | "no-mixed-spaces-and-tabs" : "warn", 16 | "key-spacing" : "off", 17 | "comma-spacing" : "off", 18 | "no-multi-spaces" : "off", 19 | "padded-blocks" : "off", 20 | "spaced-comment": "off", 21 | "no-extra-boolean-cast" : "off", 22 | "max-statements" : ["warn", { "max": 20 }], 23 | "max-params":["error", 3], 24 | "max-nested-callbacks":["error", { "max": 3 }], 25 | "max-depth":["error", 4], 26 | "no-negated-condition":"off", 27 | "no-implicit-coercion":"off", 28 | "no-unused-vars":"warn", 29 | "no-useless-escape" : "off", 30 | "space-in-parens" : "off", 31 | "semi-spacing": "off", 32 | "no-useless-concat": "off", 33 | "max-lines": ["warn", 330], 34 | "no-lonely-if" :"off", 35 | "space-infix-ops" : "warn", 36 | "camelcase" : "off", 37 | "comma-dangle" : "warn", 38 | "no-new-func":"off", 39 | "no-prototype-builtins":"off", 40 | "no-new":"warn" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # DNX 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | 50 | *_i.c 51 | *_p.c 52 | *_i.h 53 | *.ilk 54 | *.meta 55 | *.obj 56 | *.pch 57 | *.pdb 58 | *.pgc 59 | *.pgd 60 | *.rsp 61 | *.sbr 62 | *.tlb 63 | *.tli 64 | *.tlh 65 | *.tmp 66 | *.tmp_proj 67 | *.log 68 | *.vspscc 69 | *.vssscc 70 | .builds 71 | *.pidb 72 | *.svclog 73 | *.scc 74 | 75 | # Chutzpah Test files 76 | _Chutzpah* 77 | 78 | # Visual C++ cache files 79 | ipch/ 80 | *.aps 81 | *.ncb 82 | *.opendb 83 | *.opensdf 84 | *.sdf 85 | *.cachefile 86 | *.VC.db 87 | *.VC.VC.opendb 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | *.sap 94 | 95 | # TFS 2012 Local Workspace 96 | $tf/ 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | *.DotSettings.user 105 | 106 | # JustCode is a .NET coding add-in 107 | .JustCode 108 | 109 | # TeamCity is a build add-in 110 | _TeamCity* 111 | 112 | # DotCover is a Code Coverage Tool 113 | *.dotCover 114 | 115 | # NCrunch 116 | _NCrunch_* 117 | .*crunch*.local.xml 118 | nCrunchTemp_* 119 | 120 | # MightyMoose 121 | *.mm.* 122 | AutoTest.Net/ 123 | 124 | # Web workbench (sass) 125 | .sass-cache/ 126 | 127 | # Installshield output folder 128 | [Ee]xpress/ 129 | 130 | # DocProject is a documentation generator add-in 131 | DocProject/buildhelp/ 132 | DocProject/Help/*.HxT 133 | DocProject/Help/*.HxC 134 | DocProject/Help/*.hhc 135 | DocProject/Help/*.hhk 136 | DocProject/Help/*.hhp 137 | DocProject/Help/Html2 138 | DocProject/Help/html 139 | 140 | # Click-Once directory 141 | publish/ 142 | 143 | # Publish Web Output 144 | *.[Pp]ublish.xml 145 | *.azurePubxml 146 | # TODO: Comment the next line if you want to checkin your web deploy settings 147 | # but database connection strings (with potential passwords) will be unencrypted 148 | *.pubxml 149 | *.publishproj 150 | 151 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 152 | # checkin your Azure Web App publish settings, but sensitive information contained 153 | # in these scripts will be unencrypted 154 | PublishScripts/ 155 | 156 | # NuGet Packages 157 | *.nupkg 158 | # The packages folder can be ignored because of Package Restore 159 | **/packages/* 160 | # except build/, which is used as an MSBuild target. 161 | !**/packages/build/ 162 | # Uncomment if necessary however generally it will be regenerated when needed 163 | #!**/packages/repositories.config 164 | # NuGet v3's project.json files produces more ignoreable files 165 | *.nuget.props 166 | *.nuget.targets 167 | 168 | # Microsoft Azure Build Output 169 | csx/ 170 | *.build.csdef 171 | 172 | # Microsoft Azure Emulator 173 | ecf/ 174 | rcf/ 175 | 176 | # Windows Store app package directories and files 177 | AppPackages/ 178 | BundleArtifacts/ 179 | Package.StoreAssociation.xml 180 | _pkginfo.txt 181 | 182 | # Visual Studio cache files 183 | # files ending in .cache can be ignored 184 | *.[Cc]ache 185 | # but keep track of directories ending in .cache 186 | !*.[Cc]ache/ 187 | 188 | # Others 189 | ClientBin/ 190 | ~$* 191 | *~ 192 | *.dbmdl 193 | *.dbproj.schemaview 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc 263 | /npm-debug.*.* 264 | /build 265 | /coverage 266 | /output 267 | /.vscode/cSpell.json 268 | /coverage 269 | /.nyc_output 270 | /.vscode 271 | -------------------------------------------------------------------------------- /.iron-node.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var settings = { 3 | "nodeModule" : { 4 | // "scriptInjection" : "debugger;", // Prepend some custom javascript code to your code. 5 | "arguments" : [ // Add additional arguments. Node will pass undefined to these parms. This is usefull to mimic a native browser environment. 6 | "window", "document", "self", "navigator", 7 | ] 8 | }, 9 | "v8": { 10 | "flags" : [ // DEFAULT=[]; https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md 11 | // "--harmony-arrow-functions" 12 | ] 13 | }, 14 | "app": { 15 | "native+" : false, // DEFAULT=FALSE; extends require to search native modules respecting the current v8 engine version. 16 | "autoAddWorkSpace" : false, // DEFAULT=TRUE; disables the autoAddWorkSpace behavior. 17 | "openDevToolsDetached" : true, // DEFAULT=FALSE; opens the dev tools windows detached in an own window. 18 | "hideMainWindow" : true, // DEFAULT=FALSE; hides the main window to show dev tools only. 19 | }, 20 | "workSpaceDirectory" : function(argv) { // determines the workspace directory for specific commandline applications. 21 | var result = ""; 22 | if (argv[2]){ 23 | result = path.dirname(argv[2]); 24 | var startupScriptName = path.basename(argv[2]).toLowerCase(); 25 | 26 | switch(startupScriptName) { 27 | case "_mocha": 28 | result = process.cwd(); 29 | break; 30 | default: 31 | result = path.resolve(result); 32 | break; 33 | } 34 | } 35 | 36 | return result; 37 | } 38 | }; 39 | 40 | module.exports = settings; 41 | -------------------------------------------------------------------------------- /.nclirc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test" 3 | } 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | message=":arrow_up: %s" -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v8 4 | - v7 5 | - v6 6 | - v5 7 | - v4 8 | after_success: 9 | - npm run coverage && npm run coveralls -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | 4 | 5 | 6 | 7 | * * * 8 | 9 | ## Class: CLI 10 | 11 | 12 | **.prettyError**: `PrettyError` , Instance of [https://github.com/AriaMinaei/pretty-error](https://github.com/AriaMinaei/pretty-error) . 13 | **.argv**: `object` , Parsed result of [https://github.com/substack/minimist](https://github.com/substack/minimist) . 14 | **.color**: `Chalk` , Instance of [https://github.com/chalk/chalk](https://github.com/chalk/chalk) . 15 | **.Error**: `CLI.Error` , Constructor to create a new instance of custom n-cli Error . 16 | **.minimist**: `Minimist` , Instance of [https://github.com/substack/minimist](https://github.com/substack/minimist) . 17 | **.config**: `user-appdata` , Instance of ```user-appdata``` [https://github.com/s-a/user-appdata](https://github.com/s-a/user-appdata) 18 | ### CLI.resolvePath(fileOrDir) 19 | 20 | Resolve a path 21 | 22 | **Parameters** 23 | 24 | **fileOrDir**: `string`, Filename or directory 25 | 26 | **Returns**: `string`, Absolute directory name relative to the current working directory of process. 27 | 28 | ### CLI.showHelp() 29 | 30 | Output help to console. The ncli tries to find help.txt in your project folder and outputs it to console. 31 | 32 | 33 | ### CLI.showVersion() 34 | 35 | Output version to console. 36 | 37 | 38 | ### CLI.findup(basedir, filename, json) 39 | 40 | Find a file in parent directories. 41 | 42 | **Parameters** 43 | 44 | **basedir**: `string`, In most cases the current directory of process. 45 | 46 | **filename**: `string`, Filename to find. 47 | 48 | **json**: `boolean`, parse text to JSON object if true. 49 | 50 | **Returns**: `string`, Absolute directory to given ```filename```. 51 | 52 | ### CLI.programInfo() 53 | 54 | Output program information based on ```package.json``` to console. 55 | 56 | 57 | ### CLI.on(commandName, commandFunction) 58 | 59 | Catch a commandline switch case. The commandFunction runs in context of CLI. 60 | 61 | **Parameters** 62 | 63 | **commandName**: `string`, A command you want to process. For example ```push``` or ```commit```. 64 | 65 | **commandFunction**: `function`, event handler function in context of CLI. 66 | 67 | 68 | ### CLI.runcom(commandFunction) 69 | 70 | Executes if a runcom file was found. 71 | 72 | **Parameters** 73 | 74 | **commandFunction**: `function`, Event handler function. 75 | 76 | 77 | **Example**: 78 | ```js 79 | var cli = new CLI({runcom:".nclirc", handleUncaughtException:true, silent:false }); cli.runcom(function(rc){ console.log(rc); }); // yields -> // { // dir: 'c:\\git\\n-cli', // filename: '.nclirc', // fullpath: 'c:\\git\\n-cli\\.nclirc', // settings: contents-of-rc-file // } 80 | ``` 81 | 82 | ### CLI.silent() 83 | 84 | Determines if console app is running in silent mode. Silent mode activates automaticaly when ```process.env.NODE_ENV === "test"```. 85 | 86 | 87 | ### CLI.log() 88 | 89 | Like console.log put implements util.inspect. 90 | 91 | 92 | ### CLI.error() 93 | 94 | Like console.error put implements util.inspect. 95 | 96 | 97 | ### CLI.stdout() 98 | 99 | Wrapper for process.stdout.write 100 | 101 | 102 | ### CLI.stderr() 103 | 104 | Wrapper for process.stderr.write 105 | 106 | 107 | 108 | 109 | * * * 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /COMMANDLINE-ARGUMENTS.md: -------------------------------------------------------------------------------- 1 | # Help 2 | If no parameters are given the app searches a file called ```.youcoolapprc```. Content should look like the following example: 3 | 4 | ```js 5 | // file contents of .youcoolapprc -> 6 | { 7 | setting1 : true, 8 | setting2 : false, 9 | targetfolder : "./out/" 10 | } 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```sh 16 | youcoolapp [--targetfolder ./out]; 17 | ``` 18 | 19 | ## Parameters 20 | |Name|Description| 21 | |----|-----------| 22 | |--init|create a runcom file| 23 | |--outputfolder|Targetfolder for all files.| 24 | |help, --help, /? |Show this help.| 25 | |version, --version, -v|Show version.| -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 s-a (https://github.com/s-a) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # n-cli [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage Status](https://coveralls.io/repos/github/s-a/n-cli/badge.svg?branch=master)](https://coveralls.io/github/s-a/n-cli?branch=master) [![Donate](http://s-a.github.io/donate/donate.svg)](http://s-a.github.io/donate/) 2 | 3 | > a usefull toolset for node commandline applications 4 | 5 | ## Installation 6 | 7 | ```sh 8 | $ npm install --save n-cli 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```js 14 | #!/usr/bin/env node 15 | 16 | "use strict"; 17 | 18 | var Cli = new require("n-cli"); 19 | var cli = new Cli({ 20 | silent: false, 21 | handleUncaughtException : true, // beautifies error output to console 22 | handledRejectionPromiseError : true, // beautifyies error output to console 23 | runcom : ".myapprc" 24 | }); 25 | 26 | cli.on("unicorn", function(){ 27 | this.argv.notNull("rainbow"); 28 | this.log(this); 29 | }); 30 | 31 | cli.runcom(function(rc){ 32 | this.log(rc); 33 | }); 34 | ``` 35 | 36 | ```sh 37 | $ your-client-app unicorn --rainbow forever; 38 | 39 | # yields : 40 | { 41 | prettyError: 42 | PrettyError { }, 43 | init: 44 | { appname: 'node-cli-example-app-1', 45 | handleUncaughtException: true }, 46 | argv: { _: [ 'unicorn' ], rainbow: 'e', notNull: [Function: bound ] }, 47 | config: 48 | { settings: {}, 49 | appFolder: 'c:\\git\\n-cli\\examples', 50 | appPackageFilename: 'c:\\git\\n-cli\\examples\\package.json', 51 | appPackage: {}, 52 | appName: 'node-cli-example-app-1', 53 | dataFolder: 'C:\\Users\\User\\AppData\\Roaming\\node-cli-example-app-1', 54 | filename: 'C:\\Users\\User\\AppData\\Roaming\\node-cli-example-app-1\\config.json' }, 55 | color: 56 | Chalk { }, 57 | Error: [Function: NodeCliError], 58 | minimist: [Function] 59 | } 60 | ``` 61 | 62 | ```sh 63 | $ your-client-app unicorn --rainbow ; 64 | 65 | # yields : missing-parameter-value missing value for parameter rainbow. 66 | ``` 67 | 68 | ## Build in functions 69 | 70 | ncli adds automatically some methods to your commandline application. 71 | 72 | ```sh 73 | # output your version number 74 | $ your-client-app -v; 75 | ``` 76 | 77 | Output [COMMANDLINE-ARGUMENTS.md](COMMANDLINE-ARGUMENTS.md) in your projects root folder 78 | 79 | ```sh 80 | $ your-client-app help; 81 | ``` 82 | 83 | [![Help Demo][help-demo-image]][help-demo-image] 84 | 85 | ## API 86 | 87 | - [API description](API.md) 88 | 89 | ## Demo 90 | 91 | 92 | 93 | ## License 94 | 95 | MIT © [s-a](https://github.com/s-a) 96 | 97 | [npm-image]: https://badge.fury.io/js/n-cli.svg 98 | 99 | [npm-url]: https://npmjs.org/package/n-cli 100 | 101 | [travis-image]: https://travis-ci.org/s-a/n-cli.svg?branch=master 102 | 103 | [travis-url]: https://travis-ci.org/s-a/n-cli 104 | 105 | [daviddm-image]: https://david-dm.org/s-a/n-cli.svg?theme=shields.io 106 | 107 | [daviddm-url]: https://david-dm.org/s-a/n-cli 108 | 109 | [coveralls-image]: https://coveralls.io/repos/s-a/n-cli/badge.svg 110 | 111 | [coveralls-url]: https://coveralls.io/r/s-a/n-cli 112 | 113 | [help-demo-image]: help-demo.png 114 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | "use strict"; 4 | 5 | function MyCommandLineApp(argv){ 6 | this.argv = argv || process.argv.slice(2); 7 | return this; 8 | } 9 | 10 | MyCommandLineApp.prototype.start = function(){ 11 | var CLI = require("../lib"); 12 | var cli = new CLI({ 13 | handleUncaughtException : true, 14 | argv : this.argv 15 | }); 16 | 17 | cli.on("yolo", function(){ 18 | this.argv.notNull("test"); 19 | this.log(this.argv); 20 | }); 21 | 22 | cli.on("unicorn", function(){ 23 | this.argv.notNull("rainbow"); 24 | this.log("this"); 25 | }); 26 | }; 27 | 28 | if (require.main === module) { 29 | new MyCommandLineApp().start(); 30 | } else { 31 | module.exports = MyCommandLineApp; 32 | } -------------------------------------------------------------------------------- /help-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s-a/n-cli/891ab90d1f1c2fa1bcde7ea6b138c90efca03455/help-demo.png -------------------------------------------------------------------------------- /lib/error.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var chalk = require("chalk"); 4 | 5 | function NodeCliError(name, message) { 6 | this.message = name; 7 | this.description = (message || ""); 8 | return this; 9 | } 10 | 11 | NodeCliError.prototype.log = function (output) { 12 | if (output) { 13 | output(this.message, this.desciption); 14 | } else { 15 | console.error(chalk.bgRed.white.bold(this.message), chalk.white(this.desciption)); 16 | } 17 | }; 18 | 19 | module.exports = NodeCliError; 20 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-lines */ 2 | "use strict"; 3 | 4 | 5 | var minimist = require("minimist"); 6 | var E = require("./error.js"); 7 | var fs = require("fs"); 8 | var path = require("path"); 9 | var chalk = require("chalk"); 10 | var util = require("util"); 11 | var Config = require("user-appdata"); 12 | var PrettyError = require("pretty-error"); 13 | 14 | var argvNotNull = function (key) { 15 | if (this[key] === undefined) { 16 | throw new E("missing-parameter", key) 17 | } 18 | if (this[key] === true) { 19 | throw new E("missing-parameter-value", "missing value for parameter " + key + ".") 20 | } 21 | return this[key]; 22 | } 23 | 24 | /** 25 | * @param {object} config - configuration. 26 | * @example 27 | * var cli = new Cli({ 28 | * silent: false, 29 | * handleUncaughtException : true 30 | * }); 31 | * @constructor 32 | */ 33 | // eslint-disable-next-line max-statements 34 | var CLI = function (init) { 35 | if (init === undefined) { 36 | throw new E("api-expect-config-argument"); 37 | } 38 | /** 39 | * Instance of [https://github.com/AriaMinaei/pretty-error](https://github.com/AriaMinaei/pretty-error) . 40 | * @member {PrettyError} .prettyError 41 | */ 42 | this.prettyError = new PrettyError(); 43 | this.prettyError.skipNodeFiles(); 44 | this.prettyError.skipPath("bootstrap_node.js"); 45 | this.prettyError.skipPath("internal/module.js"); 46 | this.prettyError.skipPackage("bluebird"); 47 | this.init = init || {}; 48 | 49 | /** 50 | * Parsed result of [https://github.com/substack/minimist](https://github.com/substack/minimist) . 51 | * @member {object} .argv 52 | */ 53 | this.argv = minimist(this.init.argv || process.argv.slice(2)); 54 | this.argv.notNull = argvNotNull.bind(this.argv); 55 | 56 | /** 57 | * Instance of [https://github.com/chalk/chalk](https://github.com/chalk/chalk) . 58 | * @member {Chalk} .color 59 | */ 60 | this.color = chalk; 61 | 62 | /** 63 | * Constructor to create a new instance of custom n-cli Error . 64 | * @member {CLI.Error} .Error 65 | */ 66 | this.Error = E; 67 | 68 | /** 69 | * Instance of [https://github.com/substack/minimist](https://github.com/substack/minimist) . 70 | * @member {Minimist} .minimist 71 | */ 72 | this.minimist = minimist; 73 | 74 | this.done = false; 75 | 76 | if (init.handleUncaughtException === true) { 77 | this.handleUncaughtException(); 78 | } 79 | 80 | if (init.handledRejectionPromiseError === true) { 81 | this.handledRejectionPromiseError(); 82 | } 83 | 84 | var basePath = path.dirname(module.parent.filename); 85 | if (init.pkg) { 86 | this.packageJson = { 87 | settings: init.pkg 88 | } 89 | } else { 90 | this.packageJson = this.findup(basePath, "package.json", true); 91 | if (this.packageJson === null) { 92 | throw new E("parent-package-json-not-found", "tried " + basePath + " and parent folders."); 93 | } 94 | } 95 | 96 | if (this.argv.version === true || this.argv.v === true || (this.argv._[0] || "").toLowerCase() === "version") { 97 | this.showVersion(); 98 | } else { 99 | if (init.programInfoOnStartup !== false) { 100 | this.programInfo(); 101 | } 102 | if (this.argv._.indexOf("/?") !== -1 || this.argv.help || (this.argv._[0] || "").toLowerCase() === "help") { 103 | this.help = this.findup(basePath, "COMMANDLINE-ARGUMENTS.md"); 104 | if (this.help !== null) { 105 | this.help = this.help.settings; 106 | this.showHelp(); 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Instance of ```user-appdata``` [https://github.com/s-a/user-appdata](https://github.com/s-a/user-appdata) 113 | * @member {user-appdata} .config 114 | */ 115 | this.config = new Config({ 116 | appname: this.packageJson.settings.name, 117 | defaultSettings: init.defaultSettings || {} 118 | }) 119 | 120 | 121 | return this; 122 | } 123 | 124 | 125 | /** 126 | * Resolve a path 127 | * @param {string} fileOrDir Filename or directory 128 | * @return {string} Absolute directory name relative to the current working directory of process. 129 | */ 130 | CLI.prototype.resolvePath = function (fileOrDir) { 131 | var result = fileOrDir; 132 | if (!path.isAbsolute(fileOrDir)) { 133 | result = path.resolve(path.join(process.cwd(), fileOrDir)); 134 | } 135 | return result; 136 | }; 137 | 138 | /** 139 | * Output help to console. The ncli tries to find help.txt in your project folder and outputs it to console. 140 | * @method showHelp 141 | */ 142 | CLI.prototype.showHelp = function () { 143 | this.stdout("\n"); 144 | var marked = require("marked").marked; 145 | var TerminalRenderer = require("marked-terminal").default; 146 | 147 | marked.setOptions({ 148 | // Define custom renderer 149 | renderer: new TerminalRenderer() 150 | }); 151 | var result = marked(this.help); 152 | this.stdout(result); 153 | this.done = true; 154 | }; 155 | 156 | /** 157 | * Output version to console. 158 | * @method showVersion 159 | */ 160 | CLI.prototype.showVersion = function () { 161 | this.stdout(this.packageJson.settings.version); 162 | this.done = true; 163 | }; 164 | 165 | /** 166 | * Find a file in parent directories. 167 | * @method findup 168 | * @param {string} basedir In most cases the current directory of process. 169 | * @param {string} filename Filename to find. 170 | * @param {boolean} json parse text to JSON object if true. 171 | * @return {string} Absolute directory to given ```filename```. 172 | */ 173 | CLI.prototype.findup = function (basedir, filename, json) { 174 | var result = null; 175 | if (fs.existsSync(path.join(basedir, filename))) { 176 | result = { 177 | dir: basedir, 178 | filename: filename, 179 | fullpath: path.join(basedir, filename), 180 | settings: fs.readFileSync(path.join(basedir, filename)).toString() 181 | }; 182 | if (json === true) { 183 | try { 184 | result.settings = JSON.parse(result.settings); 185 | } catch (e) { 186 | throw new this.Error(e + "\n" + result.fullpath); 187 | } 188 | } 189 | } else { 190 | var newdir = path.join(basedir, ".."); 191 | if (newdir !== basedir) { 192 | result = this.findup(newdir, filename, json); 193 | } 194 | } 195 | 196 | return result; 197 | }; 198 | 199 | /** 200 | * Output program information based on ```package.json``` to console. 201 | * @method programInfo 202 | */ 203 | CLI.prototype.programInfo = function () { 204 | this.stdout(this.color.white(this.packageJson.settings.name + " " + this.packageJson.settings.version) + " " + this.color.grey(this.packageJson.settings.license + " " + this.packageJson.settings.homepage) + "\n"); 205 | }; 206 | 207 | CLI.prototype.renderError = function (error) { 208 | if (error.constructor.name === "NodeCliError") { 209 | this.stderr(this.color.bgRed.white.bold(error.message) + " " + this.color.white(error.description) || ""); 210 | } else { 211 | var renderedError = this.prettyError.render(error); 212 | console.error(renderedError); 213 | } 214 | }; 215 | 216 | CLI.prototype.handleUncaughtException = function () { 217 | process.on("uncaughtException", this.renderError.bind(this)); 218 | }; 219 | 220 | CLI.prototype.handledRejectionPromiseError = function () { 221 | var self = this; 222 | process.on("unhandledRejection", function (reason /*, p*/ ) { 223 | self.renderError.bind(self)(reason) 224 | }); 225 | }; 226 | 227 | /** 228 | * Catch a commandline switch case. The commandFunction runs in context of CLI. 229 | * @method on 230 | * @param {string} [commandName] A command you want to process. For example ```push``` or ```commit```. 231 | * @param {function} commandFunction event handler function in context of CLI. 232 | */ 233 | CLI.prototype.on = function (commandName, commandFunction) { 234 | if (!this.done) { 235 | if (typeof (commandName) === "function") { 236 | commandName.bind(this)(); 237 | } else { 238 | if (this.argv._ && this.argv._[0] === commandName) { 239 | if (typeof (commandFunction) !== "function") { 240 | throw new E("expected-type-is-function", commandFunction); 241 | } 242 | commandFunction.bind(this)(); 243 | } 244 | } 245 | } 246 | }; 247 | 248 | /** 249 | * Executes if a runcom file was found. 250 | * @param {function} commandFunction Event handler function. 251 | * @example 252 | * var cli = new CLI({runcom:".nclirc", handleUncaughtException:true, silent:false }); 253 | * cli.runcom(function(rc){ 254 | * console.log(rc); 255 | * }); 256 | * // yields -> 257 | * // { 258 | * // dir: 'c:\\git\\n-cli', 259 | * // filename: '.nclirc', 260 | * // fullpath: 'c:\\git\\n-cli\\.nclirc', 261 | * // settings: contents-of-rc-file 262 | * // } 263 | * @method runcom 264 | */ 265 | CLI.prototype.runcom = function (fn) { 266 | if (!this.done) { 267 | if (typeof (fn) !== "function") { 268 | throw new E("expected-type-is-function", fn); 269 | } 270 | if (this.init.runcom !== undefined) { 271 | var rc = null; 272 | if (path.isAbsolute(this.init.runcom)) { 273 | rc = { 274 | dir: path.dirname(this.init.runcom), 275 | filename: path.basename(this.init.runcom), 276 | fullpath: this.init.runcom, 277 | settings: JSON.parse(fs.readFileSync(this.init.runcom).toString()) 278 | }; 279 | } else { 280 | rc = this.findup(process.cwd(), this.init.runcom, true); 281 | } 282 | if (rc) { 283 | var cwd = this.resolvePath(rc.dir); 284 | process.chdir(cwd); 285 | } 286 | fn.bind(this)(rc); 287 | }; 288 | } 289 | }; 290 | 291 | /** 292 | * Determines if console app is running in silent mode. Silent mode activates automaticaly when ```process.env.NODE_ENV === "test"```. 293 | * @method silent 294 | */ 295 | CLI.prototype.silent = function () { 296 | return (this.init.silent === undefined ? (process.env.NODE_ENV === "test") : this.init.silent) || (this.argv.silent === true); 297 | }; 298 | 299 | /** 300 | * Like console.log put implements util.inspect. 301 | * @method log 302 | */ 303 | CLI.prototype.log = function () { 304 | this.outputbuffer = null; 305 | if (!this.silent()) { 306 | var args = []; 307 | for (var i = 0; i < arguments.length; i++) { 308 | args.push(util.inspect(arguments[i], /*showHidden=*/ false, /*depth=*/ this.init.inspectDepth || 4, /*colorize=*/ true)); 309 | } 310 | console.log.apply(console.log, args); 311 | } 312 | this.outputbuffer = arguments; 313 | return this; 314 | }; 315 | 316 | /** 317 | * Like console.error put implements util.inspect. 318 | * @method error 319 | */ 320 | CLI.prototype.error = function () { 321 | this.outputbuffer = null; 322 | if (!this.silent()) { 323 | var args = []; 324 | for (var i = 0; i < arguments.length; i++) { 325 | args.push(util.inspect(arguments[i], /*showHidden=*/ false, /*depth=*/ 10, /*colorize=*/ true)); 326 | } 327 | console.error.apply(console.error, args); 328 | } 329 | this.outputbuffer = arguments; 330 | return this; 331 | }; 332 | 333 | /** 334 | * Wrapper for process.stdout.write 335 | * @method stdout 336 | */ 337 | CLI.prototype.stdout = function () { 338 | this.outputbuffer = null; 339 | if (!this.silent()) { 340 | process.stdout.write(arguments[0]); 341 | } 342 | this.outputbuffer = arguments[0]; 343 | return this; 344 | }; 345 | 346 | /** 347 | * Wrapper for process.stderr.write 348 | * @method stderr 349 | */ 350 | CLI.prototype.stderr = function () { 351 | this.outputbuffer = null; 352 | if (!this.silent()) { 353 | process.stderr.write(arguments[0]); 354 | } 355 | this.outputbuffer = arguments[0]; 356 | return this; 357 | }; 358 | 359 | 360 | 361 | module.exports = CLI; 362 | 363 | 364 | /** 365 | * @title n-cli 366 | * @copyright (c) 2016 Stephan Ahlf 367 | * @license MIT 368 | * @author Stephan Ahlf 369 | */ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "n-cli", 3 | "version": "1.2.21", 4 | "description": "a usefull toolset for node commandline applications", 5 | "homepage": "", 6 | "author": { 7 | "name": "s-a", 8 | "email": "stephan.ahlf@googlemail.com", 9 | "url": "https://github.com/s-a" 10 | }, 11 | "files": [ 12 | "lib" 13 | ], 14 | "main": "lib/index.js", 15 | "keywords": [ 16 | "terminal", 17 | "commandline", 18 | "user", 19 | "interface", 20 | "tools" 21 | ], 22 | "devDependencies": { 23 | "coveralls": "^3.1.1", 24 | "eslint": "^6.8.0", 25 | "mocha": "^10.6.0", 26 | "nyc": "^17.0.0", 27 | "should": "^13.2.3" 28 | }, 29 | "repository": "https://github.com/s-a/n-cli.git", 30 | "scripts": { 31 | "docs:lib": "jsdox lib/index.js && cp ./output/index.md ./API.md", 32 | "docs": "npm run docs:lib", 33 | "lcov-file": "node node_modules/nyc/bin/nyc.js report --reporter=lcov", 34 | "coverage": "node node_modules/nyc/bin/nyc.js --reporter=html --reporter=text mocha && npm run lcov-file", 35 | "coveralls": "cat ./coverage/lcov.info | node node_modules/coveralls/bin/coveralls.js", 36 | "eslint": "node node_modules/eslint/bin/eslint.js ./lib", 37 | "debug": "iron-node node_modules/mocha/bin/_mocha", 38 | "prepublish": "npm test", 39 | "bump": "npm test && npm version patch && git push && git push --tags && npm publish", 40 | "mocha": "node node_modules/mocha/bin/_mocha", 41 | "test": "npm run eslint && npm run coverage" 42 | }, 43 | "license": "MIT", 44 | "dependencies": { 45 | "chalk": "^4.1.2", 46 | "marked": "^13.0.2", 47 | "marked-terminal": "^7.1.0", 48 | "minimist": "^1.2.8", 49 | "pretty-error": "^4.0.0", 50 | "user-appdata": "^0.1.2" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/example.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | process.env.NODE_ENV = "test"; 4 | 5 | var CLI = require("../lib"); 6 | var should = require("should"); 7 | var CommandLineApp = require("./../examples/example.js"); 8 | 9 | 10 | describe("example commandLineApp", function () { 11 | 12 | it("should start", function () { 13 | var commandLineApp = new CommandLineApp(); 14 | commandLineApp.start(); 15 | }); 16 | 17 | 18 | it("should throw missing-parameter", function () { 19 | should(function(){ 20 | var commandLineApp = new CommandLineApp(["unicorn"]); 21 | commandLineApp.start(); 22 | }).throw("missing-parameter"); 23 | }); 24 | 25 | 26 | it("should throw missing-parameter-value", function () { 27 | should(function(){ 28 | var commandLineApp = new CommandLineApp(["unicorn", "--rainbow"]); 29 | commandLineApp.start(); 30 | }).throw("missing-parameter-value"); 31 | }); 32 | 33 | 34 | it("should run unicorn fine", function () { 35 | var commandLineApp = new CommandLineApp(["unicorn", "--rainbow", "1a"]); 36 | commandLineApp.start(); 37 | }); 38 | 39 | 40 | it("should run yolo fine", function () { 41 | var commandLineApp = new CommandLineApp(["yolo", "--test", "1a"]); 42 | commandLineApp.start(); 43 | }); 44 | 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /test/node-cli.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | process.env.NODE_ENV = "test"; 4 | var silent = true; 5 | var CLI = require("../lib"); 6 | var should = require("should"); 7 | var NodeCliError = require("./../lib/error.js"); 8 | var appname = "node-cli-test-spec"; 9 | 10 | describe("node command line interface", function () { 11 | process.env.NODE_ENV = "test"; 12 | it("should throw api-expect-config-argument", function () { 13 | should(function(){ 14 | var cli = new CLI(); 15 | cli.on("ls", "doh!"); 16 | }).throw("api-expect-config-argument"); 17 | }); 18 | 19 | it("should throw expected-type-is-function", function () { 20 | should(function(){ 21 | var cli = new CLI({appname: appname, argv: ["ls"]}); 22 | cli.on("ls", "doh!"); 23 | }).throw("expected-type-is-function"); 24 | }); 25 | 26 | it("should run a program", function (done) { 27 | var cli = new CLI({appname: appname, argv: ["dir", "--test", "5"]}); 28 | cli.on("dir", function(){ 29 | cli.argv.notNull("test"); 30 | done(); 31 | }); 32 | }); 33 | 34 | it("should throw missing-parameter", function () { 35 | var cli = new CLI({appname: appname, argv: ["dir"]}); 36 | should(function(){ 37 | cli.argv.notNull("test"); 38 | }).throw("missing-parameter"); 39 | }); 40 | 41 | it("should throw missing-parameter-value", function () { 42 | var cli = new CLI({appname: appname, argv: ["dir", "--test"]}); 43 | should(function(){ 44 | cli.argv.notNull("test"); 45 | }).throw("missing-parameter-value"); 46 | }); 47 | 48 | it("should identify test environment", function () { 49 | var cli = new CLI({appname: appname, argv: ["dir"]}); 50 | cli.silent(true).should.equal(true); 51 | cli.silent().should.equal(true); 52 | }); 53 | 54 | it("should stdout text to console", function () { 55 | var cli = new CLI({silent:silent, appname: appname, argv: ["dir"]}); 56 | cli.silent(false); 57 | cli.stdout("cool?").outputbuffer.should.equal("cool?"); 58 | }); 59 | 60 | it("should stderr text to console", function () { 61 | var cli = new CLI({silent:silent, appname: appname, argv: ["dir"]}); 62 | cli.silent(false); 63 | cli.stderr("err?").outputbuffer.should.equal("err?"); 64 | }); 65 | 66 | it("should log grummel? to console", function () { 67 | var cli = new CLI({silent:silent, appname: appname, argv: ["dir"]}); 68 | cli.log({text:"grummel?"}).outputbuffer[0].should.deepEqual({text: "grummel?"}); 69 | }); 70 | 71 | it("should resolve path", function () { 72 | var cli = new CLI({silent:silent, appname: appname, argv: ["dir"]}); 73 | cli.log(cli.resolvePath(".")); 74 | }); 75 | 76 | it("should log done? to console", function () { 77 | var cli = new CLI({silent:silent, appname: appname, argv: ["dir"]}); 78 | cli.log({text:"done?"}).outputbuffer[0].should.deepEqual({text: "done?"}); 79 | }); 80 | 81 | it("should throw yolo-test-error", function () { 82 | var cli = new CLI({handleUncaughtException:true, silent:silent, appname: appname, argv: ["dir"]}); 83 | try { 84 | throw new NodeCliError("yolo-test-error", "!doh"); 85 | } catch (err) { 86 | if (err instanceof NodeCliError) { 87 | err.log(); 88 | err.log(cli.error.bind(cli)); 89 | cli.renderError(err) 90 | } 91 | } 92 | }); 93 | 94 | 95 | 96 | it("should not find runcom file", function () { 97 | var cli = new CLI({runcom:".n-clirc", handleUncaughtException:false, silent:silent, appname: appname, argv: ["dir"]}); 98 | cli.runcom(function(rc){ 99 | should.not.exist(rc); 100 | }); 101 | }); 102 | 103 | it("should find runcom file", function () { 104 | var cli = new CLI({runcom:".nclirc", handleUncaughtException:false, silent:silent, appname: appname, argv: ["dir"]}); 105 | cli.runcom(function(rc){ 106 | should.exist(rc); 107 | should.exist(rc.dir); 108 | should.exist(rc.filename); 109 | should.exist(rc.fullpath); 110 | should.exist(rc.settings.name); 111 | rc.settings.name.should.equal("test"); 112 | }); 113 | }); 114 | 115 | it("should show help", function () { 116 | process.env.NODE_ENV = "test"; 117 | var cli = new CLI({handleUncaughtException:false, silent:silent, appname: appname, argv: ["--help"]}); 118 | }); 119 | 120 | it("should show version", function () { 121 | process.env.NODE_ENV = "test"; 122 | var cli = new CLI({handleUncaughtException:false, silent:silent, appname: appname, argv: ["-v"]}); 123 | }); 124 | 125 | }); 126 | --------------------------------------------------------------------------------