From 35404b8c4626799d293ae3e6e935a2e54d614300 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 11 Nov 2019 22:34:27 +1300 Subject: [PATCH 01/22] Fix brackets in CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 82be55b1b..5358ba4ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed -* display help when requested, even if there are missing required options [(#1091)] +* display help when requested, even if there are missing required options ([#1091]) ## [4.0.0] (2019-11-02) From 11abe21c636cc5dbd7fa9a5789f8e7cf36acd021 Mon Sep 17 00:00:00 2001 From: Christian d'Heureuse Date: Thu, 14 Nov 2019 02:29:40 +0100 Subject: [PATCH 02/22] Remove trailing blanks from wrapped help text --- index.js | 2 +- tests/helpwrap.test.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index ce38da26b..173cac0af 100644 --- a/index.js +++ b/index.js @@ -1429,7 +1429,7 @@ function wrap(str, width, indent) { if (line.slice(-1) === '\n') { line = line.slice(0, line.length - 1); } - return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line; + return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line.trimRight(); }).join('\n'); } diff --git a/tests/helpwrap.test.js b/tests/helpwrap.test.js index 9eb9dd6aa..1cfbd55df 100644 --- a/tests/helpwrap.test.js +++ b/tests/helpwrap.test.js @@ -13,8 +13,8 @@ test('when long option description then wrap and indent', () => { `Usage: [options] Options: - -x -extra-long-option-switch kjsahdkajshkahd kajhsd akhds kashd kajhs dkha - dkh aksd ka dkha kdh kasd ka kahs dkh sdkh + -x -extra-long-option-switch kjsahdkajshkahd kajhsd akhds kashd kajhs dkha + dkh aksd ka dkha kdh kasd ka kahs dkh sdkh askdh aksd kashdk ahsd kahs dkha skdh -h, --help output usage information `; @@ -34,7 +34,7 @@ test('when long option description and default then wrap and indent', () => { `Usage: [options] Options: - -x -extra-long-option kjsahdkajshkahd kajhsd akhds (default: "aaa + -x -extra-long-option kjsahdkajshkahd kajhsd akhds (default: "aaa bbb ccc ddd eee fff ggg") -h, --help output usage information `; @@ -59,7 +59,7 @@ Options: -h, --help output usage information Commands: - alpha Lorem mollit quis dolor ex do eu quis ad insa + alpha Lorem mollit quis dolor ex do eu quis ad insa a commodo esse. `; From 28a9a1d1d3cb820f2b01049c8897495456d31437 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 23 Nov 2019 09:20:19 +1300 Subject: [PATCH 03/22] Update dependencies --- package-lock.json | 410 ++++++++++++++++++++++++++++++++++------------ package.json | 12 +- 2 files changed, 314 insertions(+), 108 deletions(-) diff --git a/package-lock.json b/package-lock.json index 58621c7ca..f432c7412 100644 --- a/package-lock.json +++ b/package-lock.json @@ -433,20 +433,14 @@ } }, "@types/jest": { - "version": "24.0.18", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.18.tgz", - "integrity": "sha512-jcDDXdjTcrQzdN06+TSVsPPqxvsZA/5QkYfIZlq1JMw7FdP5AZylbOc+6B/cuDurctRe+MziUMtQ3xQdrbjqyQ==", + "version": "24.0.23", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.23.tgz", + "integrity": "sha512-L7MBvwfNpe7yVPTXLn32df/EK+AMBFAFvZrRuArGs7npEWnlziUXK+5GMIUTI4NIuwok3XibsjXCs5HxviYXjg==", "dev": true, "requires": { - "@types/jest-diff": "*" + "jest-diff": "^24.3.0" } }, - "@types/jest-diff": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", - "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", - "dev": true - }, "@types/json-schema": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", @@ -454,9 +448,9 @@ "dev": true }, "@types/node": { - "version": "12.7.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.8.tgz", - "integrity": "sha512-FMdVn84tJJdV+xe+53sYiZS4R5yn1mAIxfj+DVoNiQjTYz1+OYmjwEZr1ev9nU0axXwda0QDbYl06QHanRVH3A==", + "version": "12.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.11.tgz", + "integrity": "sha512-O+x6uIpa6oMNTkPuHDa9MhMMehlxLAd5QcOvKRjAFsBVpeFWTOPnXbDvILvFgFFZfQ1xh1EZi1FbXxUix+zpsQ==", "dev": true }, "@types/stack-utils": { @@ -552,9 +546,9 @@ } }, "acorn-jsx": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", - "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, "acorn-walk": { @@ -1002,12 +996,12 @@ } }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, "cli-width": { @@ -1081,10 +1075,9 @@ } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", - "dev": true, + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "optional": true }, "component-emitter": { @@ -1433,9 +1426,9 @@ } }, "eslint": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.4.0.tgz", - "integrity": "sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.7.0.tgz", + "integrity": "sha512-dQpj+PaHKHfXHQ2Imcw5d853PTvkUGbHk/MR68KQUl98EgKDCdh4vLRH1ZxhqeQjQFJeg8fgN0UwmNhN3l8dDQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -1445,19 +1438,19 @@ "debug": "^4.0.1", "doctrine": "^3.0.0", "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.2", + "eslint-utils": "^1.4.3", "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.1", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", - "globals": "^11.7.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.4.1", + "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", @@ -1466,7 +1459,7 @@ "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", "semver": "^6.1.2", @@ -1475,6 +1468,46 @@ "table": "^5.2.3", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "globals": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, "eslint-config-standard": { @@ -1760,9 +1793,9 @@ } }, "eslint-plugin-jest": { - "version": "22.17.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.17.0.tgz", - "integrity": "sha512-WT4DP4RoGBhIQjv+5D0FM20fAdAUstfYAf/mkufLNTojsfgzc5/IYW22cIg/Q4QBavAZsROQlqppiWDpFZDS8Q==", + "version": "22.21.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.21.0.tgz", + "integrity": "sha512-OaqnSS7uBgcGiqXUiEnjoqxPNKvR4JWG5mSRkzVoR6+vDwlqqp11beeql1hYs0HTbdhiwrxWLxbX0Vx7roG3Ew==", "dev": true, "requires": { "@typescript-eslint/experimental-utils": "^1.13.0" @@ -1856,13 +1889,13 @@ "dev": true }, "espree": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz", - "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", "dev": true, "requires": { - "acorn": "^7.0.0", - "acorn-jsx": "^5.0.2", + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", "eslint-visitor-keys": "^1.1.0" } }, @@ -2130,9 +2163,9 @@ } }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -2878,10 +2911,9 @@ "dev": true }, "handlebars": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.3.tgz", - "integrity": "sha512-VupOxR91xcGojfINrzMqrvlyYbBs39sXIrWa7YdaQWeBudOlvKEGvCczMfJPgnuwHE/zyH1M6J+IUP6cgDVyxg==", - "dev": true, + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "requires": { "neo-async": "^2.6.0", "optimist": "^0.6.1", @@ -3000,9 +3032,9 @@ "dev": true }, "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -3042,24 +3074,41 @@ "dev": true }, "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.0.tgz", + "integrity": "sha512-rSdC7zelHdRQFkWnhsMu2+2SO41mpv2oF2zy4tMhmiLWkcKbOAs87fWAJhVXttKVwhdZvymvnuM95EyEXg2/tQ==", "dev": true, "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" + }, + "dependencies": { + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } } }, "invariant": { @@ -4099,9 +4148,9 @@ } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, "minimatch": { @@ -4116,8 +4165,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "mixin-deep": { "version": "1.3.2", @@ -4156,9 +4204,9 @@ "dev": true }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, "nan": { @@ -4196,8 +4244,7 @@ "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, "nice-try": { "version": "1.0.5", @@ -4423,19 +4470,18 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "^2.1.0" } }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -4444,8 +4490,7 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" } } }, @@ -4930,12 +4975,12 @@ "dev": true }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, @@ -5252,8 +5297,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "source-map-resolve": { "version": "0.5.2", @@ -5369,6 +5413,144 @@ "eslint-plugin-react": "~7.14.2", "eslint-plugin-standard": "~4.0.0", "standard-engine": "^12.0.0" + }, + "dependencies": { + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "eslint": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.4.0.tgz", + "integrity": "sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.2", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.4.1", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + } } }, "standard-engine": { @@ -5440,22 +5622,41 @@ } }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" }, "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.0" } } } @@ -5668,9 +5869,9 @@ } }, "ts-node": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.4.1.tgz", - "integrity": "sha512-5LpRN+mTiCs7lI5EtbXmF/HfMeCjzt7DH9CZwtkr6SywStrNQC723wG+aOWFiLNn7zT3kD/RnFqi3ZUfr4l5Qw==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.2.tgz", + "integrity": "sha512-W1DK/a6BGoV/D4x/SXXm6TSQx6q3blECUzd5TN+j56YEMX3yPVMpHsICLedUw3DvGF3aTQ8hfdR9AKMaHjIi+A==", "dev": true, "requires": { "arg": "^4.1.0", @@ -5717,19 +5918,18 @@ "dev": true }, "typescript": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz", - "integrity": "sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", - "dev": true, + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", + "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", "optional": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" } }, @@ -5920,6 +6120,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/package.json b/package.json index bdfb60ea5..fccff0ad8 100644 --- a/package.json +++ b/package.json @@ -26,14 +26,14 @@ ], "dependencies": {}, "devDependencies": { - "@types/jest": "^24.0.18", - "@types/node": "^12.7.5", - "eslint": "^6.4.0", - "eslint-plugin-jest": "^22.17.0", + "@types/jest": "^24.0.23", + "@types/node": "^12.12.11", + "eslint": "^6.7.0", + "eslint-plugin-jest": "^22.21.0", "jest": "^24.8.0", "standard": "^14.3.1", - "ts-node": "^8.4.1", - "typescript": "^3.6.3" + "ts-node": "^8.5.2", + "typescript": "^3.7.2" }, "typings": "typings/index.d.ts", "engines": { From df8d9defe648d7dce4697ffec4ce55f1e23214c2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 24 Nov 2019 20:11:11 +1300 Subject: [PATCH 04/22] Do not rely on "this" in listener (as per other listeners) --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 173cac0af..b30c696f5 100644 --- a/index.js +++ b/index.js @@ -1041,9 +1041,10 @@ Command.prototype.version = function(str, flags, description) { var versionOption = new Option(flags, description); this._versionOptionName = versionOption.long.substr(2) || 'version'; this.options.push(versionOption); + var self = this; this.on('option:' + this._versionOptionName, function() { process.stdout.write(str + '\n'); - this._exit(0, 'commander.version', str); + self._exit(0, 'commander.version', str); }); return this; }; From 09c4ab4aea7deb7fe800610ffe518eb38a24d392 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 24 Nov 2019 20:23:04 +1300 Subject: [PATCH 05/22] Extend timeout to hopefully reduce test failures --- tests/command.executableSubcommand.lookup.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/command.executableSubcommand.lookup.test.js b/tests/command.executableSubcommand.lookup.test.js index 9f1db6ed2..50c2cfe1f 100644 --- a/tests/command.executableSubcommand.lookup.test.js +++ b/tests/command.executableSubcommand.lookup.test.js @@ -89,6 +89,7 @@ testOrSkipOnWindows('when subcommand file is double symlink then lookup succeeds }); test('when subcommand suffix is .ts then lookup succeeds', (done) => { + jest.setTimeout(20000); // Extend timeout for GitHub Actions const binLinkTs = path.join(__dirname, 'fixtures-ts', 'pm.ts'); childProcess.execFile('node', ['-r', 'ts-node/register', binLinkTs, 'install'], function(_error, stdout, stderr) { expect(stdout).toBe('install\n'); From 7004c0ae346f703f078b6213dc2e37235b32caf8 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 24 Nov 2019 20:55:24 +1300 Subject: [PATCH 06/22] Reword supported node versions --- Readme.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index b23d01f2e..34fc1eb7a 100644 --- a/Readme.md +++ b/Readme.md @@ -648,7 +648,8 @@ More Demos can be found in the [examples](https://github.com/tj/commander.js/tre ## Support -Commander is supported on Node 8 and above. (Commander is likely to still work on older versions of Node, but is not tested below Node 8.) +Commander 4.x is supported on Node 8 and above, and is likely to work with Node 6 but not tested. +(For versions of Node below Node 6, use Commander 3.x or 2.x.) The main forum for free and community support is the project [Issues](https://github.com/tj/commander.js/issues) on GitHub. From bc16bd2523a103537ba20e7719a2eabbc4f8028c Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 7 Dec 2019 12:57:44 +1300 Subject: [PATCH 07/22] Change to six months support, rather than n-1 --- SECURITY.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index ea2977345..2cadbb7a6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,11 +2,14 @@ ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| 4.x | :white_check_mark: | -| 3.x | :white_check_mark: | -| < 3 | :x: | +Old versions receive security updates for six months. + +| Version | Supported | +| ------- | ------------------------------------------ | +| 4.x | :white_check_mark: | +| 3.x | :white_check_mark: support ends 2020-05-01 | +| 2.x | :white_check_mark: support ends 2020-02-03 | +| < 2 | :x: | ## Reporting a Vulnerability From 3740834b47101ed530941080bf9b24349f5920da Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 7 Dec 2019 13:30:17 +1300 Subject: [PATCH 08/22] Separate out changelog for old versions --- CHANGELOG.md | 143 +------------------------------------- changelogs/CHANGELOG-0.md | 88 +++++++++++++++++++++++ changelogs/CHANGELOG-1.md | 56 +++++++++++++++ 3 files changed, 147 insertions(+), 140 deletions(-) create mode 100644 changelogs/CHANGELOG-0.md create mode 100644 changelogs/CHANGELOG-1.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5358ba4ce..f86cc4332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -355,147 +355,10 @@ program * remove input methods (.prompt, .confirm, etc) -## 1.3.2 / 2013-07-18 +## Older versions -* add support for sub-commands to co-exist with the original command - -## 1.3.1 / 2013-07-18 - -* add quick .runningCommand hack so you can opt-out of other logic when running a sub command - -## 1.3.0 / 2013-07-09 - -* add EACCES error handling -* fix sub-command --help - -## 1.2.0 / 2013-06-13 - -* allow "-" hyphen as an option argument -* support for RegExp coercion - -## 1.1.1 / 2012-11-20 - -* add more sub-command padding -* fix .usage() when args are present. Closes #106 - -## 1.1.0 / 2012-11-16 - -* add git-style executable subcommand support. Closes #94 - -## 1.0.5 / 2012-10-09 - -* fix `--name` clobbering. Closes #92 -* fix examples/help. Closes #89 - -## 1.0.4 / 2012-09-03 - -* add `outputHelp()` method. - -## 1.0.3 / 2012-08-30 - -* remove invalid .version() defaulting - -## 1.0.2 / 2012-08-24 - -* add `--foo=bar` support [arv] -* fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus] - -## 1.0.1 / 2012-08-03 - -* fix issue #56 -* fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode()) - -## 1.0.0 / 2012-07-05 - -* add support for optional option descriptions -* add defaulting of `.version()` to package.json's version - -## 0.6.1 / 2012-06-01 - -* Added: append (yes or no) on confirmation -* Added: allow node.js v0.7.x - -## 0.6.0 / 2012-04-10 - -* Added `.prompt(obj, callback)` support. Closes #49 -* Added default support to .choose(). Closes #41 -* Fixed the choice example - -## 0.5.1 / 2011-12-20 - -* Fixed `password()` for recent nodes. Closes #36 - -## 0.5.0 / 2011-12-04 - -* Added sub-command option support [itay] - -## 0.4.3 / 2011-12-04 - -* Fixed custom help ordering. Closes #32 - -## 0.4.2 / 2011-11-24 - -* Added travis support -* Fixed: line-buffered input automatically trimmed. Closes #31 - -## 0.4.1 / 2011-11-18 - -* Removed listening for "close" on --help - -## 0.4.0 / 2011-11-15 - -* Added support for `--`. Closes #24 - -## 0.3.3 / 2011-11-14 - -* Fixed: wait for close event when writing help info [Jerry Hamlet] - -## 0.3.2 / 2011-11-01 - -* Fixed long flag definitions with values [felixge] - -## 0.3.1 / 2011-10-31 - -* Changed `--version` short flag to `-V` from `-v` -* Changed `.version()` so it's configurable [felixge] - -## 0.3.0 / 2011-10-31 - -* Added support for long flags only. Closes #18 - -## 0.2.1 / 2011-10-24 - -* "node": ">= 0.4.x < 0.7.0". Closes #20 - -## 0.2.0 / 2011-09-26 - -* Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs] - -## 0.1.0 / 2011-08-24 - -* Added support for custom `--help` output - -## 0.0.5 / 2011-08-18 - -* Changed: when the user enters nothing prompt for password again -* Fixed issue with passwords beginning with numbers [NuckChorris] - -## 0.0.4 / 2011-08-15 - -* Fixed `Commander#args` - -## 0.0.3 / 2011-08-15 - -* Added default option value support - -## 0.0.2 / 2011-08-15 - -* Added mask support to `Command#password(str[, mask], fn)` -* Added `Command#password(str, fn)` - -## 0.0.1 / 2010-01-03 - -* Initial release +* [1.x](./changelogs/CHANGELOG-1.md) +* [0.x](./changelogs/CHANGELOG-0.md) [#599]: https://github.com/tj/commander.js/issues/599 [#611]: https://github.com/tj/commander.js/issues/611 diff --git a/changelogs/CHANGELOG-0.md b/changelogs/CHANGELOG-0.md new file mode 100644 index 000000000..07defca8d --- /dev/null +++ b/changelogs/CHANGELOG-0.md @@ -0,0 +1,88 @@ +# Changelog for 0.x + +## 0.6.1 / 2012-06-01 + +* Added: append (yes or no) on confirmation +* Added: allow node.js v0.7.x + +## 0.6.0 / 2012-04-10 + +* Added `.prompt(obj, callback)` support. Closes #49 +* Added default support to .choose(). Closes #41 +* Fixed the choice example + +## 0.5.1 / 2011-12-20 + +* Fixed `password()` for recent nodes. Closes #36 + +## 0.5.0 / 2011-12-04 + +* Added sub-command option support [itay] + +## 0.4.3 / 2011-12-04 + +* Fixed custom help ordering. Closes #32 + +## 0.4.2 / 2011-11-24 + +* Added travis support +* Fixed: line-buffered input automatically trimmed. Closes #31 + +## 0.4.1 / 2011-11-18 + +* Removed listening for "close" on --help + +## 0.4.0 / 2011-11-15 + +* Added support for `--`. Closes #24 + +## 0.3.3 / 2011-11-14 + +* Fixed: wait for close event when writing help info [Jerry Hamlet] + +## 0.3.2 / 2011-11-01 + +* Fixed long flag definitions with values [felixge] + +## 0.3.1 / 2011-10-31 + +* Changed `--version` short flag to `-V` from `-v` +* Changed `.version()` so it's configurable [felixge] + +## 0.3.0 / 2011-10-31 + +* Added support for long flags only. Closes #18 + +## 0.2.1 / 2011-10-24 + +* "node": ">= 0.4.x < 0.7.0". Closes #20 + +## 0.2.0 / 2011-09-26 + +* Allow for defaults that are not just boolean. Default peassignment only occurs for --no-*, optional, and required arguments. [Jim Isaacs] + +## 0.1.0 / 2011-08-24 + +* Added support for custom `--help` output + +## 0.0.5 / 2011-08-18 + +* Changed: when the user enters nothing prompt for password again +* Fixed issue with passwords beginning with numbers [NuckChorris] + +## 0.0.4 / 2011-08-15 + +* Fixed `Commander#args` + +## 0.0.3 / 2011-08-15 + +* Added default option value support + +## 0.0.2 / 2011-08-15 + +* Added mask support to `Command#password(str[, mask], fn)` +* Added `Command#password(str, fn)` + +## 0.0.1 / 2010-01-03 + +* Initial release diff --git a/changelogs/CHANGELOG-1.md b/changelogs/CHANGELOG-1.md new file mode 100644 index 000000000..1eb7ac32e --- /dev/null +++ b/changelogs/CHANGELOG-1.md @@ -0,0 +1,56 @@ +# Changelog for 1.x + +## 1.3.2 / 2013-07-18 + +* add support for sub-commands to co-exist with the original command + +## 1.3.1 / 2013-07-18 + +* add quick .runningCommand hack so you can opt-out of other logic when running a sub command + +## 1.3.0 / 2013-07-09 + +* add EACCES error handling +* fix sub-command --help + +## 1.2.0 / 2013-06-13 + +* allow "-" hyphen as an option argument +* support for RegExp coercion + +## 1.1.1 / 2012-11-20 + +* add more sub-command padding +* fix .usage() when args are present. Closes #106 + +## 1.1.0 / 2012-11-16 + +* add git-style executable subcommand support. Closes #94 + +## 1.0.5 / 2012-10-09 + +* fix `--name` clobbering. Closes #92 +* fix examples/help. Closes #89 + +## 1.0.4 / 2012-09-03 + +* add `outputHelp()` method. + +## 1.0.3 / 2012-08-30 + +* remove invalid .version() defaulting + +## 1.0.2 / 2012-08-24 + +* add `--foo=bar` support [arv] +* fix password on node 0.8.8. Make backward compatible with 0.6 [focusaurus] + +## 1.0.1 / 2012-08-03 + +* fix issue #56 +* fix tty.setRawMode(mode) was moved to tty.ReadStream#setRawMode() (i.e. process.stdin.setRawMode()) + +## 1.0.0 / 2012-07-05 + +* add support for optional option descriptions +* add defaulting of `.version()` to package.json's version From 3baa16fafbcb748980b4e98871126feee330d9e3 Mon Sep 17 00:00:00 2001 From: Samuel Bodin <1637651+bodinsamuel@users.noreply.github.com> Date: Sat, 7 Dec 2019 19:10:31 +0100 Subject: [PATCH 09/22] doc: typo (#1113) --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index b30c696f5..d29201432 100644 --- a/index.js +++ b/index.js @@ -151,7 +151,7 @@ function Command(name) { * // Command implemented using separate executable file (description is second parameter to `.command`) * program * .command('start ', 'start named service') - * .command('stop [service]', 'stop named serice, or all if no name supplied'); + * .command('stop [service]', 'stop named service, or all if no name supplied'); * * @param {string} nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable) From f119fc78eb9de82b9edd6526cf24945e1ae35fdd Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 8 Dec 2019 22:49:32 +1300 Subject: [PATCH 10/22] Remove redundant @param types, and tidy. --- typings/index.d.ts | 71 ++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 44 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index a2fc6a989..92c639ce4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,7 +1,5 @@ -// Type definitions for commander 2.11 -// Project: https://github.com/visionmedia/commander.js -// Definitions by: Alan Agius , Marcelo Dezem , vvakame , Jules Randolph -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped +// Type definitions for commander +// Original definitions by: Alan Agius , Marcelo Dezem , vvakame , Jules Randolph /// @@ -39,7 +37,6 @@ declare namespace commander { * which will print the version number when passed. * * You can optionally supply the flags and description to override the defaults. - * */ version(str: string, flags?: string, description?: string): Command; @@ -87,8 +84,7 @@ declare namespace commander { /** * Define argument syntax for the top-level command. * - * @param {string} desc - * @returns {Command} for chaining + * @returns Command for chaining */ arguments(desc: string): Command; @@ -97,8 +93,7 @@ declare namespace commander { * * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`. * - * @param {string[]} args - * @returns {Command} for chaining + * @returns Command for chaining */ parseExpectedArgs(args: string[]): Command; @@ -118,8 +113,7 @@ declare namespace commander { * // output help here * }); * - * @param {(...args: any[]) => void} fn - * @returns {Command} for chaining + * @returns Command for chaining */ action(fn: (...args: any[]) => void): Command; @@ -163,11 +157,7 @@ declare namespace commander { * // optional argument * program.option('-c, --cheese [type]', 'add cheese [marble]'); * - * @param {string} flags - * @param {string} [description] - * @param {((arg1: any, arg2: any) => void) | RegExp} [fn] function or default - * @param {*} [defaultValue] - * @returns {Command} for chaining + * @returns Command for chaining */ option(flags: string, description?: string, fn?: ((arg1: any, arg2: any) => void) | RegExp, defaultValue?: any): Command; option(flags: string, description?: string, defaultValue?: any): Command; @@ -184,74 +174,69 @@ declare namespace commander { /** * Allow unknown options on the command line. * - * @param {boolean} [arg] if `true` or omitted, no error will be thrown for unknown options. - * @returns {Command} for chaining + * @param [arg] if `true` or omitted, no error will be thrown for unknown options. + * @returns Command for chaining */ allowUnknownOption(arg?: boolean): Command; /** * Parse `argv`, settings options and invoking commands when defined. * - * @param {string[]} argv * @returns {Command} for chaining */ parse(argv: string[]): Command; /** * Parse options from `argv` returning `argv` void of these options. - * - * @param {string[]} argv - * @returns {ParseOptionsResult} */ parseOptions(argv: string[]): commander.ParseOptionsResult; /** * Return an object containing options as key-value pairs - * - * @returns {{[key: string]: any}} */ opts(): { [key: string]: any }; /** - * Set the description to `str`. - * - * @param {string} str - * @param {{[argName: string]: string}} argsDescription - * @return {(Command | string)} + * Set the description. + * + * @returns Command for chaining */ description(str: string, argsDescription?: {[argName: string]: string}): Command; + /** + * Get the description. + */ description(): string; /** * Set an alias for the command. - * - * @param {string} alias - * @return {(Command | string)} + * + * @returns Command for chaining */ alias(alias: string): Command; + /** + * Get alias for the command. + */ alias(): string; /** - * Set or get the command usage. - * - * @param {string} str - * @return {(Command | string)} + * Set the command usage. + * + * @returns Command for chaining */ usage(str: string): Command; + /** + * Get the command usage. + */ usage(): string; /** * Set the name of the command. - * - * @param {string} str - * @return {Command} + * + * @returns Command for chaining */ name(str: string): Command; - /** * Get the name of the command. - * - * @return {string} */ name(): string; @@ -260,8 +245,6 @@ declare namespace commander { * * When listener(s) are available for the helpLongFlag * those callbacks are invoked. - * - * @param {(str: string) => string} [cb] */ outputHelp(cb?: (str: string) => string): void; From df6284cde850198dde1c874494906d7e26160376 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 9 Dec 2019 21:13:29 +1300 Subject: [PATCH 11/22] Add current changes to CHANGELOG --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f86cc4332..5f2c76189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. +## [4.0.2] (date goes here) + +### Fixed + +- Remove trailing blanks from wrapped help text ([#1096]) + +### Changed + +- update dependencies +- extend security coverage for Commander 2.x to 2020-02-03 +- improvements to README +- improvements to TypeScript definition documentation +- move old versions out of main CHANGELOG + ## [4.0.1] (2019-11-12) ### Fixed @@ -389,6 +403,7 @@ program [#1071]: https://github.com/tj/commander.js/pull/1071 [#1081]: https://github.com/tj/commander.js/pull/1081 [#1091]: https://github.com/tj/commander.js/pull/1091 +[#1096]: https://github.com/tj/commander.js/pull/1096 [Unreleased]: https://github.com/tj/commander.js/compare/master...develop [4.0.1]: https://github.com/tj/commander.js/compare/v4.0.0..v4.0.1 From 81c6e285ba303ccd27f31a6435cbba2b17b17497 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 11 Dec 2019 16:45:02 +1300 Subject: [PATCH 12/22] Opt-in behaviour to avoid name pollution (#1102) * Add object to hold option values separately from properties on command. Return directly from .opts(). * Add configureCommand, and support for passing options rather than command to action handler * Restore original opts() implementation when using old configuration * Use either/or new/old option storage, not both * Turn version test on again, old behaviour restored * Add tests for configureCommand, and fix bugs * Expand .opts tests to include modern configuration * Add TypeScript and inline documentation for configureCommand * Switch from modern:boolean to combo:string * Rework new behaviour with matching named routines. * Add example files for storeOptionsAsProperties * Add usage error, and make value default to true for new routines (so simpler call for that case) * Simpify description * Add section on avoiding option name clashes * Do not use else after a return --- .eslintignore | 2 + .eslintrc.json | 3 +- Readme.md | 40 +++++++ examples/storeOptionsAsProperties-action.js | 40 +++++++ examples/storeOptionsAsProperties-opts.js | 38 +++++++ examples/storeOptionsAsProperties-problem.js | 35 ++++++ index.js | 113 ++++++++++++++++--- tests/commander.configureCommand.test.js | 87 ++++++++++++++ tests/options.opts.test.js | 80 ++++++++----- typings/commander-tests.ts | 3 + typings/index.d.ts | 17 +++ 11 files changed, 408 insertions(+), 50 deletions(-) create mode 100644 .eslintignore create mode 100644 examples/storeOptionsAsProperties-action.js create mode 100644 examples/storeOptionsAsProperties-opts.js create mode 100644 examples/storeOptionsAsProperties-problem.js create mode 100644 tests/commander.configureCommand.test.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..c9227f805 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +# We have not configured eslint for TypeScript so avoid bogus error messages in the test file and typings file. +**/*.ts diff --git a/.eslintrc.json b/.eslintrc.json index 383d25115..5ac1412cf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,7 @@ "rules": { "one-var": "off", "semi": ["error", "always"], - "space-before-function-paren": ["error", "never"] + "space-before-function-paren": ["error", "never"], + "no-else-return": ["error", { "allowElseIf": false }] } } diff --git a/Readme.md b/Readme.md index 34fc1eb7a..afe042d5e 100644 --- a/Readme.md +++ b/Readme.md @@ -31,6 +31,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [.help(cb)](#helpcb) - [Custom event listeners](#custom-event-listeners) - [Bits and pieces](#bits-and-pieces) + - [Avoiding option name clashes](#avoiding-option-name-clashes) - [TypeScript](#typescript) - [Node options such as `--harmony`](#node-options-such-as---harmony) - [Node debugging](#node-debugging) @@ -70,6 +71,8 @@ Options are defined with the `.option()` method, also serving as documentation f The options can be accessed as properties on the Command object. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc. Multiple short flags may be combined as a single arg, for example `-abc` is equivalent to `-a -b -c`. +See also optional new behaviour to [avoid name clashes](#avoiding-option-name-clashes). + ### Common option types, boolean and value The two most used option types are a boolean flag, and an option which takes a value (declared using angle brackets). Both are `undefined` unless specified on command line. @@ -550,6 +553,43 @@ program.on('command:*', function () { ## Bits and pieces +### Avoiding option name clashes + +The original and default behaviour is that the option values are stored +as properties on the program, and the action handler is passed a +command object with the options values stored as properties. +This is very convenient to code, but the downside is possible clashes with +existing properties of Command. + +There are two new routines to change the behaviour, and the default behaviour may change in the future: + +- `storeOptionsAsProperties`: whether to store option values as properties on command object, or store separately (specify false) and access using `.opts()` +- `passCommandToAction`: whether to pass command to action handler, +or just the options (specify false) + +```js +// file: ./examples/storeOptionsAsProperties.action.js +program + .storeOptionsAsProperties(false) + .passCommandToAction(false); + +program + .name('my-program-name') + .option('-n,--name '); + +program + .command('show') + .option('-a,--action ') + .action((options) => { + console.log(options.action); + }); + +program.parse(process.argv); + +const programOptions = program.opts(); +console.log(programOptions.name); +``` + ### TypeScript The Commander package includes its TypeScript Definition file, but also requires the node types which you need to install yourself. e.g. diff --git a/examples/storeOptionsAsProperties-action.js b/examples/storeOptionsAsProperties-action.js new file mode 100644 index 000000000..0e01122e5 --- /dev/null +++ b/examples/storeOptionsAsProperties-action.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +// To avoid possible name clashes, you can change the default behaviour +// of storing the options values as properties on the command object. +// In addition, you can pass just the options to the action handler +// instead of a commmand object. This allows simpler code, and is more consistent +// with the previous behaviour so less code changes from old code. +// +// Example output: +// +// $ node storeOptionsAsProperties-action.js show +// undefined +// undefined +// +// $ node storeOptionsAsProperties-action.js --name foo show --action jump +// jump +// foo + +const commander = require('../'); +const program = new commander.Command(); + +program + .storeOptionsAsProperties(false) // <--- change behaviour + .passCommandToAction(false); // <--- change behaviour + +program + .name('my-program-name') + .option('-n,--name '); + +program + .command('show') + .option('-a,--action ') + .action((options) => { // <--- passed options, not Command + console.log(options.action); // <--- matches old code + }); + +program.parse(process.argv); + +const programOptions = program.opts(); // <--- use opts to access option values +console.log(programOptions.name); diff --git a/examples/storeOptionsAsProperties-opts.js b/examples/storeOptionsAsProperties-opts.js new file mode 100644 index 000000000..f86c0dcc0 --- /dev/null +++ b/examples/storeOptionsAsProperties-opts.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +// To avoid possible name clashes, you can change the default behaviour +// of storing the options values as properties on the command object. +// You access the option values using the .opts() function. +// +// Example output: +// +// $ node storeOptionsAsProperties-opts.js show +// undefined +// undefined +// +// $ node storeOptionsAsProperties-opts.js --name foo show --action jump +// jump +// foo + +const commander = require('../'); +const program = new commander.Command(); + +program + .storeOptionsAsProperties(false); // <--- change behaviour + +program + .name('my-program-name') + .option('-n,--name '); + +program + .command('show') + .option('-a,--action ') + .action((cmd) => { + const options = cmd.opts(); // <--- use opts to access option values + console.log(options.action); + }); + +program.parse(process.argv); + +const programOptions = program.opts(); // <--- use opts to access option values +console.log(programOptions.name); diff --git a/examples/storeOptionsAsProperties-problem.js b/examples/storeOptionsAsProperties-problem.js new file mode 100644 index 000000000..01110d0cc --- /dev/null +++ b/examples/storeOptionsAsProperties-problem.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +// The original and default behaviour is that the option values are stored +// as properties on the program (Command). The action handler is passed a +// command object (Command) with the options values also stored as properties. +// This is very convenient to code, but the downside is possible clashes with +// existing properties of Command. +// +// Example output, note the issues in the first call: +// +// $ node storeOptionsAsProperties-problem.js show +// [Function] +// [Function] +// +// $ node storeOptionsAsProperties-problem.js --name foo show --action jump +// jump +// foo + +const commander = require('../'); +const program = new commander.Command(); + +program + .name('my-program-name') + .option('-n,--name '); // Oops, clash with .name() + +program + .command('show') + .option('-a,--action ') // Oops, clash with .action() + .action((cmd) => { + console.log(cmd.action); + }); + +program.parse(process.argv); + +console.log(program.name); diff --git a/index.js b/index.js index d29201432..918d04a2b 100644 --- a/index.js +++ b/index.js @@ -126,6 +126,9 @@ function Command(name) { this._allowUnknownOption = false; this._args = []; this._name = name || ''; + this._optionValues = {}; + this._storeOptionsAsProperties = true; // backwards compatible by default + this._passCommandToAction = true; // backwards compatible by default this._helpFlags = '-h, --help'; this._helpDescription = 'output usage information'; @@ -183,6 +186,8 @@ Command.prototype.command = function(nameAndArgs, actionOptsOrExecDesc, execOpts cmd._helpShortFlag = this._helpShortFlag; cmd._helpLongFlag = this._helpLongFlag; cmd._exitCallback = this._exitCallback; + cmd._storeOptionsAsProperties = this._storeOptionsAsProperties; + cmd._passCommandToAction = this._passCommandToAction; cmd._executableFile = opts.executableFile; // Custom name for executable file this.commands.push(cmd); @@ -351,7 +356,11 @@ Command.prototype.action = function(fn) { // The .action callback takes an extra parameter which is the command itself. var expectedArgsCount = self._args.length; var actionArgs = args.slice(0, expectedArgsCount); - actionArgs[expectedArgsCount] = self; + if (self._passCommandToAction) { + actionArgs[expectedArgsCount] = self; + } else { + actionArgs[expectedArgsCount] = self.opts(); + } // Add the extra arguments so available too. if (args.length > expectedArgsCount) { actionArgs.push(args.slice(expectedArgsCount)); @@ -405,12 +414,12 @@ Command.prototype._optionEx = function(config, flags, description, fn, defaultVa if (option.negate || option.optional || option.required || typeof defaultValue === 'boolean') { // when --no-foo we make sure default is true, unless a --foo option is already defined if (option.negate) { - var opts = self.opts(); - defaultValue = Object.prototype.hasOwnProperty.call(opts, name) ? opts[name] : true; + const positiveLongFlag = option.long.replace(/^--no-/, '--'); + defaultValue = self.optionFor(positiveLongFlag) ? self._getOptionValue(name) : true; } // preassign only if we have a default if (defaultValue !== undefined) { - self[name] = defaultValue; + self._setOptionValue(name, defaultValue); option.defaultValue = defaultValue; } } @@ -423,22 +432,22 @@ Command.prototype._optionEx = function(config, flags, description, fn, defaultVa this.on('option:' + oname, function(val) { // coercion if (val !== null && fn) { - val = fn(val, self[name] === undefined ? defaultValue : self[name]); + val = fn(val, self._getOptionValue(name) === undefined ? defaultValue : self._getOptionValue(name)); } // unassigned or boolean value - if (typeof self[name] === 'boolean' || typeof self[name] === 'undefined') { + if (typeof self._getOptionValue(name) === 'boolean' || typeof self._getOptionValue(name) === 'undefined') { // if no value, negate false, and we have a default, then use it! if (val == null) { - self[name] = option.negate + self._setOptionValue(name, option.negate ? false - : defaultValue || true; + : defaultValue || true); } else { - self[name] = val; + self._setOptionValue(name, val); } } else if (val !== null) { // reassign - self[name] = option.negate ? false : val; + self._setOptionValue(name, option.negate ? false : val); } }); @@ -531,6 +540,69 @@ Command.prototype.allowUnknownOption = function(arg) { return this; }; +/** + * Whether to store option values as properties on command object, + * or store separately (specify false). In both cases the option values can be accessed using .opts(). + * + * @param {boolean} value + * @return {Command} Command for chaining + * @api public + */ + +Command.prototype.storeOptionsAsProperties = function(value) { + this._storeOptionsAsProperties = (value === undefined) || value; + if (this.options.length) { + // This is for programmer, not end user. + console.error('Commander usage error: call storeOptionsAsProperties before adding options'); + } + return this; +}; + +/** + * Whether to pass command to action handler, + * or just the options (specify false). + * + * @param {boolean} value + * @return {Command} Command for chaining + * @api public + */ + +Command.prototype.passCommandToAction = function(value) { + this._passCommandToAction = (value === undefined) || value; + return this; +}; + +/** + * Store option value + * + * @param {String} key + * @param {Object} value + * @api private + */ + +Command.prototype._setOptionValue = function(key, value) { + if (this._storeOptionsAsProperties) { + this[key] = value; + } else { + this._optionValues[key] = value; + } +}; + +/** + * Retrieve option value + * + * @param {String} key + * @return {Object} value + * @api private + */ + +Command.prototype._getOptionValue = function(key) { + if (this._storeOptionsAsProperties) { + return this[key]; + } + return this._optionValues[key]; +}; + /** * Parse `argv`, settings options and invoking commands when defined. * @@ -843,7 +915,7 @@ Command.prototype._checkForMissingMandatoryOptions = function() { // Walk up hierarchy so can call from action handler after checking for displaying help. for (var cmd = this; cmd; cmd = cmd.parent) { cmd.options.forEach((anOption) => { - if (anOption.mandatory && (cmd[anOption.attributeName()] === undefined)) { + if (anOption.mandatory && (cmd._getOptionValue(anOption.attributeName()) === undefined)) { cmd.missingMandatoryOptionValue(anOption); } }); @@ -936,14 +1008,19 @@ Command.prototype.parseOptions = function(argv) { * @api public */ Command.prototype.opts = function() { - var result = {}, - len = this.options.length; - - for (var i = 0; i < len; i++) { - var key = this.options[i].attributeName(); - result[key] = key === this._versionOptionName ? this._version : this[key]; + if (this._storeOptionsAsProperties) { + // Preserve original behaviour so backwards compatible when still using properties + var result = {}, + len = this.options.length; + + for (var i = 0; i < len; i++) { + var key = this.options[i].attributeName(); + result[key] = key === this._versionOptionName ? this._version : this[key]; + } + return result; } - return result; + + return this._optionValues; }; /** diff --git a/tests/commander.configureCommand.test.js b/tests/commander.configureCommand.test.js new file mode 100644 index 000000000..69ad7bcb4 --- /dev/null +++ b/tests/commander.configureCommand.test.js @@ -0,0 +1,87 @@ +const commander = require('../'); + +// Mostly testing direct on program, limited check that (sub)command working same. + +// Default behaviours + +test('when default then options stored on command', () => { + const program = new commander.Command(); + program + .option('--foo ', 'description'); + program.parse(['node', 'test', '--foo', 'bar']); + expect(program.foo).toBe('bar'); +}); + +test('when default then command passed to action', () => { + const program = new commander.Command(); + const callback = jest.fn(); + program + .arguments('') + .action(callback); + program.parse(['node', 'test', 'value']); + expect(callback).toHaveBeenCalledWith('value', program); +}); + +// storeOptionsAsProperties + +test('when storeOptionsAsProperties() then options stored on command', () => { + const program = new commander.Command(); + program + .storeOptionsAsProperties() + .option('--foo ', 'description'); + program.parse(['node', 'test', '--foo', 'bar']); + expect(program.foo).toBe('bar'); +}); + +test('when storeOptionsAsProperties(true) then options stored on command', () => { + const program = new commander.Command(); + program + .storeOptionsAsProperties(true) + .option('--foo ', 'description'); + program.parse(['node', 'test', '--foo', 'bar']); + expect(program.foo).toBe('bar'); +}); + +test('when storeOptionsAsProperties(false) then options not stored on command', () => { + const program = new commander.Command(); + program + .storeOptionsAsProperties(false) + .option('--foo ', 'description'); + program.parse(['node', 'test', '--foo', 'bar']); + expect(program.foo).toBeUndefined(); +}); + +// passCommandToAction + +test('when passCommandToAction() then command passed to action', () => { + const program = new commander.Command(); + const callback = jest.fn(); + program + .passCommandToAction() + .arguments('') + .action(callback); + program.parse(['node', 'test', 'value']); + expect(callback).toHaveBeenCalledWith('value', program); +}); + +test('when passCommandToAction(true) then command passed to action', () => { + const program = new commander.Command(); + const callback = jest.fn(); + program + .passCommandToAction(true) + .arguments('') + .action(callback); + program.parse(['node', 'test', 'value']); + expect(callback).toHaveBeenCalledWith('value', program); +}); + +test('when passCommandToAction(false) then options passed to action', () => { + const program = new commander.Command(); + const callback = jest.fn(); + program + .passCommandToAction(false) + .arguments('') + .action(callback); + program.parse(['node', 'test', 'value']); + expect(callback).toHaveBeenCalledWith('value', program.opts()); +}); diff --git a/tests/options.opts.test.js b/tests/options.opts.test.js index 83476d9d9..337a909e2 100644 --- a/tests/options.opts.test.js +++ b/tests/options.opts.test.js @@ -12,44 +12,62 @@ test('when .version used then version in opts', () => { expect(program.opts()).toEqual({ version }); }); -test('when boolean flag not specified then not in opts', () => { +test('when .version used with storeOptionsAsProperties(false) then version not in opts', () => { + // New behaviour, stop storing version as an option value. const program = new commander.Command(); + const version = '0.0.1'; program - .option('--pepper', 'add pepper'); + .storeOptionsAsProperties(false) + .version(version); program.parse(['node', 'test']); expect(program.opts()).toEqual({ }); }); -test('when boolean flag specified then value true', () => { - const program = new commander.Command(); - program - .option('--pepper', 'add pepper'); - program.parse(['node', 'test', '--pepper']); - expect(program.opts()).toEqual({ pepper: true }); -}); +describe.each([true, false])('storeOptionsAsProperties is %s', (storeOptionsAsProperties) => { + test('when boolean flag not specified then not in opts', () => { + const program = new commander.Command(); + program.storeOptionsAsProperties(storeOptionsAsProperties); + program + .option('--pepper', 'add pepper'); + program.parse(['node', 'test']); + expect(program.opts()).toEqual({ }); + }); -test('when option with required value not specified then not in opts', () => { - const program = new commander.Command(); - program - .option('--pepper ', 'add pepper'); - program.parse(['node', 'test']); - expect(program.opts()).toEqual({ }); -}); + test('when boolean flag specified then value true', () => { + const program = new commander.Command(); + program.storeOptionsAsProperties(storeOptionsAsProperties); + program + .option('--pepper', 'add pepper'); + program.parse(['node', 'test', '--pepper']); + expect(program.opts()).toEqual({ pepper: true }); + }); -test('when option with required value specified then value as specified', () => { - const pepperValue = 'red'; - const program = new commander.Command(); - program - .option('--pepper ', 'add pepper'); - program.parse(['node', 'test', '--pepper', pepperValue]); - expect(program.opts()).toEqual({ pepper: pepperValue }); -}); + test('when option with required value not specified then not in opts', () => { + const program = new commander.Command(); + program.storeOptionsAsProperties(storeOptionsAsProperties); + program + .option('--pepper ', 'add pepper'); + program.parse(['node', 'test']); + expect(program.opts()).toEqual({ }); + }); -test('when option with default value not specified then default value in opts', () => { - const pepperDefault = 'red'; - const program = new commander.Command(); - program - .option('--pepper ', 'add pepper', pepperDefault); - program.parse(['node', 'test']); - expect(program.opts()).toEqual({ pepper: pepperDefault }); + test('when option with required value specified then value as specified', () => { + const pepperValue = 'red'; + const program = new commander.Command(); + program.storeOptionsAsProperties(storeOptionsAsProperties); + program + .option('--pepper ', 'add pepper'); + program.parse(['node', 'test', '--pepper', pepperValue]); + expect(program.opts()).toEqual({ pepper: pepperValue }); + }); + + test('when option with default value not specified then default value in opts', () => { + const pepperDefault = 'red'; + const program = new commander.Command(); + program.storeOptionsAsProperties(storeOptionsAsProperties); + program + .option('--pepper ', 'add pepper', pepperDefault); + program.parse(['node', 'test']); + expect(program.opts()).toEqual({ pepper: pepperDefault }); + }); }); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 25c00e9e5..9d05be47f 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -10,6 +10,9 @@ const errorInstance = new program.CommanderError(1, 'code', 'message'); const name = program.name(); +program.storeOptionsAsProperties(true); +program.passCommandToAction(true); + program .name('set name') .version('0.0.1') diff --git a/typings/index.d.ts b/typings/index.d.ts index 92c639ce4..2ed0fa6c4 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -171,6 +171,23 @@ declare namespace commander { requiredOption(flags: string, description?: string, fn?: ((arg1: any, arg2: any) => void) | RegExp, defaultValue?: any): Command; requiredOption(flags: string, description?: string, defaultValue?: any): Command; + + /** + * Whether to store option values as properties on command object, + * or store separately (specify false). In both cases the option values can be accessed using .opts(). + * + * @return Command for chaining + */ + storeOptionsAsProperties(value?: boolean): Command; + + /** + * Whether to pass command to action handler, + * or just the options (specify false). + * + * @return Command for chaining + */ + passCommandToAction(value?: boolean): Command; + /** * Allow unknown options on the command line. * From 29a7f46a650d83c111108faa610367fcdf5d1085 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 11 Dec 2019 16:48:24 +1300 Subject: [PATCH 13/22] 4.1.0-0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index f432c7412..30d097da6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "commander", - "version": "4.0.1", + "version": "4.1.0-0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fccff0ad8..0daf89640 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "commander", - "version": "4.0.1", + "version": "4.1.0-0", "description": "the complete solution for node.js command-line programs", "keywords": [ "commander", From 9de09686ad486a717cde3b4ebb1f099023421ec6 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 11 Dec 2019 16:56:10 +1300 Subject: [PATCH 14/22] Add feature notes to README --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f2c76189..1b42b518e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. -## [4.0.2] (date goes here) +## [4.1.0] (date goes here) + +### Added + +- two routines to change how option values are handled, and avoid name clashes with command properties ([#1102]) + - see storeOptionsAsProperties and passCommandToAction in README ### Fixed @@ -404,6 +409,7 @@ program [#1081]: https://github.com/tj/commander.js/pull/1081 [#1091]: https://github.com/tj/commander.js/pull/1091 [#1096]: https://github.com/tj/commander.js/pull/1096 +[#1102]: https://github.com/tj/commander.js/pull/1102 [Unreleased]: https://github.com/tj/commander.js/compare/master...develop [4.0.1]: https://github.com/tj/commander.js/compare/v4.0.0..v4.0.1 From 808d4bc651155c1fb5693b1723416ab67ad7bbb8 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 20 Dec 2019 16:10:04 +1300 Subject: [PATCH 15/22] Removed explicit use of ts-node (#1125) * Remove explicit use of ts-node, and dependency. Speed tests. * Fix typo --- package-lock.json | 53 +++++-------------- package.json | 1 - ...ommand.executableSubcommand.lookup.test.js | 6 ++- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30d097da6..0e9f1436c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -600,12 +600,6 @@ "normalize-path": "^2.1.1" } }, - "arg": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.1.tgz", - "integrity": "sha512-SlmP3fEA88MBv0PypnXZ8ZfJhwmDeIE3SP71j37AiXQBXYosPV0x6uISAaHYSlSVhmHOVkomen0tbGk6Anlebw==", - "dev": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1078,6 +1072,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, "optional": true }, "component-emitter": { @@ -1305,12 +1300,6 @@ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, - "diff": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", - "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", - "dev": true - }, "diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -2914,6 +2903,7 @@ "version": "4.5.3", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", + "dev": true, "requires": { "neo-async": "^2.6.0", "optimist": "^0.6.1", @@ -4075,12 +4065,6 @@ } } }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -4165,7 +4149,8 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mixin-deep": { "version": "1.3.2", @@ -4244,7 +4229,8 @@ "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true }, "nice-try": { "version": "1.0.5", @@ -4482,6 +4468,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" @@ -4490,7 +4477,8 @@ "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true } } }, @@ -5297,7 +5285,8 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true }, "source-map-resolve": { "version": "0.5.2", @@ -5868,19 +5857,6 @@ "punycode": "^2.1.0" } }, - "ts-node": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.5.2.tgz", - "integrity": "sha512-W1DK/a6BGoV/D4x/SXXm6TSQx6q3blECUzd5TN+j56YEMX3yPVMpHsICLedUw3DvGF3aTQ8hfdR9AKMaHjIi+A==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.6", - "yn": "^3.0.0" - } - }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -5927,6 +5903,7 @@ "version": "3.6.9", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", + "dev": true, "optional": true, "requires": { "commander": "~2.20.3", @@ -6249,12 +6226,6 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true } } } diff --git a/package.json b/package.json index 0daf89640..48fec80e8 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "eslint-plugin-jest": "^22.21.0", "jest": "^24.8.0", "standard": "^14.3.1", - "ts-node": "^8.5.2", "typescript": "^3.7.2" }, "typings": "typings/index.d.ts", diff --git a/tests/command.executableSubcommand.lookup.test.js b/tests/command.executableSubcommand.lookup.test.js index 50c2cfe1f..6f28f8f5d 100644 --- a/tests/command.executableSubcommand.lookup.test.js +++ b/tests/command.executableSubcommand.lookup.test.js @@ -89,9 +89,11 @@ testOrSkipOnWindows('when subcommand file is double symlink then lookup succeeds }); test('when subcommand suffix is .ts then lookup succeeds', (done) => { - jest.setTimeout(20000); // Extend timeout for GitHub Actions + // We support looking for ts files for ts-node in particular, but don't need to test ts-node itself. + // The program and the subcommand `pm-install.ts` are both plain JavaScript code. const binLinkTs = path.join(__dirname, 'fixtures-ts', 'pm.ts'); - childProcess.execFile('node', ['-r', 'ts-node/register', binLinkTs, 'install'], function(_error, stdout, stderr) { + // childProcess.execFile('node', ['-r', 'ts-node/register', binLinkTs, 'install'], function(_error, stdout, stderr) { + childProcess.execFile('node', [binLinkTs, 'install'], function(_error, stdout, stderr) { expect(stdout).toBe('install\n'); done(); }); From 03e77df852eda1ffbc71a0b35744be33c247ff8b Mon Sep 17 00:00:00 2001 From: oGsLP <1145234011@qq.com> Date: Wed, 25 Dec 2019 15:42:47 +0800 Subject: [PATCH 16/22] Update Chinese README for v4.1.0 --- Readme_zh-CN.md | 46 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/Readme_zh-CN.md b/Readme_zh-CN.md index 89fa4b1b2..c15bcd673 100644 --- a/Readme_zh-CN.md +++ b/Readme_zh-CN.md @@ -31,6 +31,7 @@ - [.help(cb)](#helpcb) - [自定义事件监听](#%e8%87%aa%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6%e7%9b%91%e5%90%ac) - [零碎知识](#%e9%9b%b6%e7%a2%8e%e7%9f%a5%e8%af%86) + - [避免选项命名冲突](#避免选项命名冲突) - [TypeScript](#typescript) - [Node 选项例如 `--harmony`](#node-%e9%80%89%e9%a1%b9%e4%be%8b%e5%a6%82---harmony) - [Node 调试](#node-%e8%b0%83%e8%af%95) @@ -38,6 +39,7 @@ - [例子](#%e4%be%8b%e5%ad%90) - [许可证](#%e8%ae%b8%e5%8f%af%e8%af%81) - [支持](#%e6%94%af%e6%8c%81) + - [企业使用Commander](#企业使用Commander) ## 安装 @@ -68,6 +70,8 @@ program.version('0.0.1'); 选项会被放到 Commander 对象的属性上,多词选项如"--template-engine"会被转为驼峰法`program.templateEngine`。多个短标识可以组合为一个参数,如`-a -b -c`等价于`-abc`。 + 另请参看可选的新功能 [避免命名冲突](#避免选项命名冲突). + ### 常用选项类型,boolean和值 最常用的两个选项类型是boolean(选项后面不跟值)和选项跟一个值(使用尖括号声明)。除非在命令行中指定,否则两者都是`undefined`。 @@ -540,6 +544,39 @@ program.on('command:*', function () { ## 零碎知识 +### 避免选项命名冲突 + +Commander原本和默认的行为是将选项值作为program的属性存储的,并且给操作处理程序(action handler)传递了一个将选项值作为属性存储的command对象。 +这样确实使得编程很方便,但是会带来有可能会和Command对象的属性相冲突的缺点。 + +这里有两种方法来改变着这样的行为,而且我们有可能在将来改变默认的行为 + +- `storeOptionsAsProperties`: 是否将选项值作为command对象的属性来存储,亦或者分开地存储(指定 false)并使用`.opts()`来获得。 +- `passCommandToAction`: 是否把command对象传递给操作处理程序,亦或者仅仅传递这些选项(指定 false) + +```js +// 文件: ./examples/storeOptionsAsProperties.action.js +program + .storeOptionsAsProperties(false) + .passCommandToAction(false); + +program + .name('my-program-name') + .option('-n,--name '); + +program + .command('show') + .option('-a,--action ') + .action((options) => { + console.log(options.action); + }); + +program.parse(process.argv); + +const programOptions = program.opts(); +console.log(programOptions.name); +``` + ### TypeScript 包里包含 TypeScript 定义文件,但是需要你自己安装 node types。如: @@ -636,10 +673,13 @@ program.parse(process.argv); ## 支持 -Commander 现在在Node 8以及更高的版本上得到支持。(尽管Commander仍有可能在更低的Node版本上工作,但是Node 8以下版本不再保证相关的测试) +Commander 4.x版本现在在Node 8以及更高的版本上得到支持,尽管仍有可能在Node 6版本上工作,但是不再保证相关的测试。 +(对于Node版本低于6的情况,建议使用Commander 3.x 或 2.x版本。) 主要的社区支持的免费论坛就在Github上的项目[Issues](https://github.com/tj/commander.js/issues) -[专业支持的commander现在已经可用!](https://tidelift.com/subscription/pkg/npm-commander?utm_source=npm-commander&utm_medium=referral&utm_campaign=readme) +### 企业使用Commander + +作为Tidelift订阅的一部分现在可用 -Tidelift为软件开发团队提供了购买和维护其软件的单一来源,并由最了解软件的专家提供专业级保证,同时与现有工具无缝集成。 +Commander和数以千计的其他包的维护者在与Tidelift合作,提供对于企业用来构筑应用的开源依赖的商业支持与维护。通过向相关依赖包的维护者支付一定费用,从而帮助企业节省时间,降低风险,改进代码运行情况。[了解更多](https://tidelift.com/subscription/pkg/npm-commander?utm_source=npm-commander&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) From 4faad59f75b6ea5566db69cb4c55a8a1ba118d1d Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 Dec 2019 09:38:05 +1300 Subject: [PATCH 17/22] Improve JSDoc to match code --- index.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index 918d04a2b..fc8f5355f 100644 --- a/index.js +++ b/index.js @@ -115,7 +115,7 @@ exports.CommanderError = CommanderError; /** * Initialize a new `Command`. * - * @param {String} name + * @param {String} [name] * @api public */ @@ -694,7 +694,7 @@ Command.prototype.parse = function(argv) { * @param {Array} argv * @param {Array} args * @param {Array} unknown - * @param {String} specifySubcommand + * @param {String} executableFile * @api private */ @@ -927,7 +927,7 @@ Command.prototype._checkForMissingMandatoryOptions = function() { * void of these options. * * @param {Array} argv - * @return {Array} + * @return {{args: Array, unknown: Array}} * @api public */ @@ -1039,8 +1039,8 @@ Command.prototype.missingArgument = function(name) { /** * `Option` is missing an argument, but received `flag` or nothing. * - * @param {String} option - * @param {String} flag + * @param {Option} option + * @param {String} [flag] * @api private */ @@ -1058,7 +1058,7 @@ Command.prototype.optionMissingArgument = function(option, flag) { /** * `Option` does not have a value, and is a mandatory option. * - * @param {String} option + * @param {Option} option * @api private */ @@ -1130,7 +1130,7 @@ Command.prototype.version = function(str, flags, description) { * Set the description to `str`. * * @param {String} str - * @param {Object} argsDescription + * @param {Object} [argsDescription] * @return {String|Command} * @api public */ @@ -1167,7 +1167,7 @@ Command.prototype.alias = function(alias) { /** * Set / get the command usage `str`. * - * @param {String} str + * @param {String} [str] * @return {String|Command} * @api public */ @@ -1190,7 +1190,7 @@ Command.prototype.usage = function(str) { /** * Get or set the name of the command * - * @param {String} str + * @param {String} [str] * @return {String|Command} * @api public */ @@ -1536,8 +1536,8 @@ function optionalWrap(str, width, indent) { /** * Output help information if necessary * - * @param {Command} command to output help for - * @param {Array} array of options to search for -h or --help + * @param {Command} cmd - command to output help for + * @param {Array} options - array of options to search for -h or --help * @api private */ From d47fb0c58b00f5260d4c9a942509b2dc4a89d852 Mon Sep 17 00:00:00 2001 From: John Gee Date: Tue, 31 Dec 2019 09:03:18 +1300 Subject: [PATCH 18/22] Rename help check routine --- index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 918d04a2b..a5896191e 100644 --- a/index.js +++ b/index.js @@ -328,7 +328,7 @@ Command.prototype.action = function(fn) { var parsed = self.parseOptions(unknown); // Output help if necessary - outputHelpIfNecessary(self, parsed.unknown); + outputHelpIfRequested(self, parsed.unknown); self._checkForMissingMandatoryOptions(); // If there are still any unknown options, then we simply @@ -873,7 +873,7 @@ Command.prototype.parseArgs = function(args, unknown) { this.emit('command:*', args, unknown); } } else { - outputHelpIfNecessary(this, unknown); + outputHelpIfRequested(this, unknown); // If there were no args and we have unknown options, // then they are extraneous and we need to error. @@ -1534,14 +1534,14 @@ function optionalWrap(str, width, indent) { } /** - * Output help information if necessary + * Output help information if help flags specified * * @param {Command} command to output help for * @param {Array} array of options to search for -h or --help * @api private */ -function outputHelpIfNecessary(cmd, options) { +function outputHelpIfRequested(cmd, options) { options = options || []; for (var i = 0; i < options.length; i++) { From 7bcf1175a35d6754a8af63e796c133d9b8a6ac94 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 20:14:40 +1300 Subject: [PATCH 19/22] Add parseAsync (#1118) * First cut at parseAsync * Add await parseSync test * Fix typo in JSDoc * Add parseAsync to README Some noise in TOC due to changes in plugin which maintains TOC. --- Readme.md | 17 +++++++++++++++-- index.js | 25 +++++++++++++++++++++++-- tests/command.action.test.js | 16 ++++++++++++++++ typings/commander-tests.ts | 6 ++++++ typings/index.d.ts | 13 +++++++++++-- 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index afe042d5e..aa4f42b59 100644 --- a/Readme.md +++ b/Readme.md @@ -11,7 +11,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Commander.js](#commanderjs) - [Installation](#installation) - - [Declaring _program_ variable](#declaring-program-variable) + - [Declaring program variable](#declaring-program-variable) - [Options](#options) - [Common option types, boolean and value](#common-option-types-boolean-and-value) - [Default option value](#default-option-value) @@ -33,7 +33,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [Bits and pieces](#bits-and-pieces) - [Avoiding option name clashes](#avoiding-option-name-clashes) - [TypeScript](#typescript) - - [Node options such as `--harmony`](#node-options-such-as---harmony) + - [Node options such as --harmony](#node-options-such-as---harmony) - [Node debugging](#node-debugging) - [Override exit handling](#override-exit-handling) - [Examples](#examples) @@ -377,6 +377,19 @@ program program.parse(process.argv) ``` +You may supply an `async` action handler, in which case you call `.parseAsync` rather than `.parse`. + +```js +async function run() { /* code goes here */ } + +async function main() { + program + .command('run') + .action(run); + await program.parseAsync(process.argv); +} +``` + A command's options on the command line are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated. Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. diff --git a/index.js b/index.js index b7e35778a..37d20cc5e 100644 --- a/index.js +++ b/index.js @@ -129,6 +129,7 @@ function Command(name) { this._optionValues = {}; this._storeOptionsAsProperties = true; // backwards compatible by default this._passCommandToAction = true; // backwards compatible by default + this._actionResults = []; this._helpFlags = '-h, --help'; this._helpDescription = 'output usage information'; @@ -366,7 +367,13 @@ Command.prototype.action = function(fn) { actionArgs.push(args.slice(expectedArgsCount)); } - fn.apply(self, actionArgs); + const actionResult = fn.apply(self, actionArgs); + // Remember result in case it is async. Assume parseAsync getting called on root. + let rootCommand = self; + while (rootCommand.parent) { + rootCommand = rootCommand.parent; + } + rootCommand._actionResults.push(actionResult); }; var parent = this.parent || this; var name = parent === this ? '*' : this._name; @@ -604,7 +611,7 @@ Command.prototype._getOptionValue = function(key) { }; /** - * Parse `argv`, settings options and invoking commands when defined. + * Parse `argv`, setting options and invoking commands when defined. * * @param {Array} argv * @return {Command} for chaining @@ -688,6 +695,20 @@ Command.prototype.parse = function(argv) { return result; }; +/** + * Parse `argv`, setting options and invoking commands when defined. + * + * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise. + * + * @param {Array} argv + * @return {Promise} + * @api public + */ +Command.prototype.parseAsync = function(argv) { + this.parse(argv); + return Promise.all(this._actionResults); +}; + /** * Execute a sub-command executable. * diff --git a/tests/command.action.test.js b/tests/command.action.test.js index fa0f85c35..59469be9b 100644 --- a/tests/command.action.test.js +++ b/tests/command.action.test.js @@ -88,3 +88,19 @@ test('when .action on program with subcommand and program argument then program expect(actionMock).toHaveBeenCalledWith('a', program); }); + +test('when action is async then can await parseAsync', async() => { + let asyncFinished = false; + async function delay() { + await new Promise(resolve => setTimeout(resolve, 100)); + asyncFinished = true; + }; + const program = new commander.Command(); + program + .action(delay); + + const later = program.parseAsync(['node', 'test']); + expect(asyncFinished).toBe(false); + await later; + expect(asyncFinished).toBe(true); +}); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 9d05be47f..7661bcf0e 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -133,4 +133,10 @@ program.exitOverride((err):void => { program.parse(process.argv); +program.parseAsync(process.argv).then(() => { + console.log('parseAsync success'); +}).catch(err => { + console.log('parseAsync failed'); +}); + console.log('stuff'); diff --git a/typings/index.d.ts b/typings/index.d.ts index 2ed0fa6c4..d1a89d8c2 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -197,12 +197,21 @@ declare namespace commander { allowUnknownOption(arg?: boolean): Command; /** - * Parse `argv`, settings options and invoking commands when defined. + * Parse `argv`, setting options and invoking commands when defined. * - * @returns {Command} for chaining + * @returns Command for chaining */ parse(argv: string[]): Command; + /** + * Parse `argv`, setting options and invoking commands when defined. + * + * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise. + * + * @returns Promise + */ + parseAsync(argv: string[]): Promise; + /** * Parse options from `argv` returning `argv` void of these options. */ From 6f692cf88d8ef0fc16896f2d540dc6cbc43ae444 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 5 Jan 2020 21:44:28 +1300 Subject: [PATCH 20/22] Updated changelog for 4.1 --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b42b518e..24779278b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. -## [4.1.0] (date goes here) +## [4.1.0] (2020-01-06) ### Added -- two routines to change how option values are handled, and avoid name clashes with command properties ([#1102]) +- two routines to change how option values are handled, and eliminate name clashes with command properties ([#933] [#1102]) - see storeOptionsAsProperties and passCommandToAction in README +- `.parseAsync` to use instead of `.parse` if supply async action handlers ([#806] [#1118]) ### Fixed @@ -25,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - improvements to README - improvements to TypeScript definition documentation - move old versions out of main CHANGELOG +- removed explicit use of `ts-node` in tests ## [4.0.1] (2019-11-12) @@ -383,6 +385,7 @@ program [#611]: https://github.com/tj/commander.js/issues/611 [#697]: https://github.com/tj/commander.js/issues/697 [#795]: https://github.com/tj/commander.js/issues/795 +[#806]: https://github.com/tj/commander.js/issues/806 [#915]: https://github.com/tj/commander.js/issues/915 [#938]: https://github.com/tj/commander.js/issues/938 [#963]: https://github.com/tj/commander.js/issues/963 @@ -391,6 +394,7 @@ program [#987]: https://github.com/tj/commander.js/issues/987 [#990]: https://github.com/tj/commander.js/issues/990 [#991]: https://github.com/tj/commander.js/issues/991 +[#993]: https://github.com/tj/commander.js/issues/993 [#999]: https://github.com/tj/commander.js/issues/999 [#1010]: https://github.com/tj/commander.js/pull/1010 [#1018]: https://github.com/tj/commander.js/pull/1018 @@ -410,6 +414,7 @@ program [#1091]: https://github.com/tj/commander.js/pull/1091 [#1096]: https://github.com/tj/commander.js/pull/1096 [#1102]: https://github.com/tj/commander.js/pull/1102 +[#1118]: https://github.com/tj/commander.js/pull/1118 [Unreleased]: https://github.com/tj/commander.js/compare/master...develop [4.0.1]: https://github.com/tj/commander.js/compare/v4.0.0..v4.0.1 From 81f5079b87a95060ada17f837f77ea58a8f87a05 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 6 Jan 2020 21:23:53 +1300 Subject: [PATCH 21/22] Bump version for release --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0e9f1436c..8895fea9c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "commander", - "version": "4.1.0-0", + "version": "4.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 48fec80e8..f824fbb70 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "commander", - "version": "4.1.0-0", + "version": "4.1.0", "description": "the complete solution for node.js command-line programs", "keywords": [ "commander", From 1c66935d7bd18c26db2262116f5fea7508782aa8 Mon Sep 17 00:00:00 2001 From: oGsLP <1145234011@qq.com> Date: Mon, 6 Jan 2020 16:39:47 +0800 Subject: [PATCH 22/22] add zh-CN translation for parseAsync --- Readme_zh-CN.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Readme_zh-CN.md b/Readme_zh-CN.md index c15bcd673..ca2c8ace4 100644 --- a/Readme_zh-CN.md +++ b/Readme_zh-CN.md @@ -33,7 +33,7 @@ - [零碎知识](#%e9%9b%b6%e7%a2%8e%e7%9f%a5%e8%af%86) - [避免选项命名冲突](#避免选项命名冲突) - [TypeScript](#typescript) - - [Node 选项例如 `--harmony`](#node-%e9%80%89%e9%a1%b9%e4%be%8b%e5%a6%82---harmony) + - [Node 选项例如 --harmony](#node-%e9%80%89%e9%a1%b9%e4%be%8b%e5%a6%82---harmony) - [Node 调试](#node-%e8%b0%83%e8%af%95) - [重载退出(exit)处理](#%e9%87%8d%e8%bd%bd%e9%80%80%e5%87%baexit%e5%a4%84%e7%90%86) - [例子](#%e4%be%8b%e5%ad%90) @@ -372,6 +372,20 @@ program program.parse(process.argv) ``` +你可以自行实现一个`async`操作处理程序,同时调用`.parseAsync`代替`.parse`。 + +```js +async function run() { /* 在这里编写代码 */ } + +async function main() { + program + .command('run') + .action(run); + await program.parseAsync(process.argv); +} +``` + + 当一个命令在命令行上被使用时,它的选项必须是合法的。使用任何未知的选项会报错。然而如果一个基于操作的命令没有定义任何操作,那么这些选项是不合法的。 定义配置选项可以随着调用 `.command()` 传递。