From f1f43a39d8c623b9476f5b1c953cdd2dc8f6eef2 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Fri, 16 Oct 2020 00:15:59 +0200 Subject: [PATCH 1/5] add faq (closes #45) --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 9a02706..e45083e 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,20 @@ node cli.js --extraneous true Unknown or unexpected option: --extraneous ``` +# FAQ + +A few questions and answers that have been asked before: + +### How do I require an argument with `arg`? + +Do the assertion yourself, such as: + +```javascript +const args = arg({ '--name': String }); + +if (!args['--name']) throw new Error('missing required argument: --name'); +``` + # License Copyright © 2017-2019 by ZEIT, Inc. Released under the [MIT License](LICENSE.md). From 0acfb7b7f8fb850f9ec6e8d9d577cb21bfc26fe3 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Tue, 17 Nov 2020 02:19:02 +0100 Subject: [PATCH 2/5] lowercase error messages and use a unified ArgError type --- index.d.ts | 6 ++++++ index.js | 33 ++++++++++++++++++++++----------- test.js | 32 ++++++++++++++++---------------- 3 files changed, 44 insertions(+), 27 deletions(-) diff --git a/index.d.ts b/index.d.ts index 5752aa8..bfd3de6 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,6 +9,12 @@ declare namespace arg { export type Handler = (value: string, name: string, previousValue?: T) => T; + export class ArgError extends Error { + constructor(message: string, code: string); + + code: string; + } + export interface Spec { [key: string]: string | Handler | [Handler]; } diff --git a/index.js b/index.js index 7d794f8..67b5ec4 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,18 @@ const flagSymbol = Symbol('arg flag'); +class ArgError extends Error { + constructor(msg, code) { + super(msg); + this.name = 'ArgError'; + this.code = code; + + Object.setPrototypeOf(this, ArgError.prototype); + } +} + function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPositional = false} = {}) { if (!opts) { - throw new Error('Argument specification object is required'); + throw new ArgError('argument specification object is required', 'ARG_CONFIG_NO_SPEC'); } const result = {_: []}; @@ -12,15 +22,15 @@ function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPosi for (const key of Object.keys(opts)) { if (!key) { - throw new TypeError('Argument key cannot be an empty string'); + throw new ArgError('argument key cannot be an empty string', 'ARG_CONFIG_EMPTY_KEY'); } if (key[0] !== '-') { - throw new TypeError(`Argument key must start with '-' but found: '${key}'`); + throw new ArgError(`argument key must start with '-' but found: '${key}'`, 'ARG_CONFIG_NONOPT_KEY'); } if (key.length === 1) { - throw new TypeError(`Argument key must have a name; singular '-' keys are not allowed: ${key}`); + throw new ArgError(`argument key must have a name; singular '-' keys are not allowed: ${key}`, 'ARG_CONFIG_NONAME_KEY'); } if (typeof opts[key] === 'string') { @@ -41,11 +51,11 @@ function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPosi } else if (typeof type === 'function') { isFlag = type === Boolean || type[flagSymbol] === true; } else { - throw new TypeError(`Type missing or not a function or valid array type: ${key}`); + throw new ArgError(`type missing or not a function or valid array type: ${key}`, 'ARG_CONFIG_VAD_TYPE'); } if (key[1] !== '-' && key.length > 2) { - throw new TypeError(`Short argument keys (with a single hyphen) must have only one character: ${key}`); + throw new ArgError(`short argument keys (with a single hyphen) must have only one character: ${key}`, 'ARG_CONFIG_SHORTOPT_TOOLONG'); } handlers[key] = [type, isFlag]; @@ -85,16 +95,14 @@ function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPosi result._.push(arg); continue; } else { - const err = new Error(`Unknown or unexpected option: ${originalArgName}`); - err.code = 'ARG_UNKNOWN_OPTION'; - throw err; + throw new ArgError(`unknown or unexpected option: ${originalArgName}`, 'ARG_UNKNOWN_OPTION'); } } const [type, isFlag] = handlers[argName]; if (!isFlag && ((j + 1) < separatedArguments.length)) { - throw new TypeError(`Option requires argument (but was followed by another short argument): ${originalArgName}`); + throw new ArgError(`option requires argument (but was followed by another short argument): ${originalArgName}`, 'ARG_MISSING_REQUIRED_SHORTARG'); } if (isFlag) { @@ -116,7 +124,7 @@ function arg(opts, {argv = process.argv.slice(2), permissive = false, stopAtPosi ) ) { const extended = originalArgName === argName ? '' : ` (alias for ${argName})`; - throw new Error(`Option requires argument: ${originalArgName}${extended}`); + throw new ArgError(`option requires argument: ${originalArgName}${extended}`, 'ARG_MISSING_REQUIRED_LONGARG'); } result[argName] = type(argv[i + 1], argName, result[argName]); @@ -141,4 +149,7 @@ arg.flag = fn => { // Utility types arg.COUNT = arg.flag((v, name, existingCount) => (existingCount || 0) + 1); +// Expose error class +arg.ArgError = ArgError; + module.exports = arg; diff --git a/test.js b/test.js index 19bd44a..59db895 100644 --- a/test.js +++ b/test.js @@ -25,7 +25,7 @@ test('basic parses arguments from process.argv', () => { }); test('arg with no arguments', () => { - expect(() => arg()).to.throw('Argument specification object is required'); + expect(() => arg()).to.throw(arg.ArgError, 'argument specification object is required'); }); test('basic extra arguments parsing', () => { @@ -112,44 +112,44 @@ test('double-dash parsing', () => { test('error: invalid option', () => { const argv = ['--foo', '1234', '--bar', '8765']; - expect(() => arg({'--foo': Number}, {argv})).to.throw('Unknown or unexpected option: --bar'); + expect(() => arg({'--foo': Number}, {argv})).to.throw(arg.ArgError, 'unknown or unexpected option: --bar'); }); test('error: expected argument', () => { const argv = ['--foo', '--bar', '1234']; - expect(() => arg({'--foo': String, '--bar': Number}, {argv})).to.throw('Option requires argument: --foo'); + expect(() => arg({'--foo': String, '--bar': Number}, {argv})).to.throw(arg.ArgError, 'option requires argument: --foo'); }); test('error: expected argument (end flag)', () => { const argv = ['--foo', '--bar']; - expect(() => arg({'--foo': Boolean, '--bar': Number}, {argv})).to.throw('Option requires argument: --bar'); + expect(() => arg({'--foo': Boolean, '--bar': Number}, {argv})).to.throw(arg.ArgError, 'option requires argument: --bar'); }); test('error: expected argument (alias)', () => { const argv = ['--foo', '--bar', '1234']; - expect(() => arg({'--realfoo': String, '--foo': '--realfoo', '--bar': Number}, {argv})).to.throw('Option requires argument: --foo (alias for --realfoo)'); + expect(() => arg({'--realfoo': String, '--foo': '--realfoo', '--bar': Number}, {argv})).to.throw(arg.ArgError, 'option requires argument: --foo (alias for --realfoo)'); }); test('error: expected argument (end flag) (alias)', () => { const argv = ['--foo', '--bar']; - expect(() => arg({'--foo': Boolean, '--realbar': Number, '--bar': '--realbar'}, {argv})).to.throw('Option requires argument: --bar (alias for --realbar)'); + expect(() => arg({'--foo': Boolean, '--realbar': Number, '--bar': '--realbar'}, {argv})).to.throw(arg.ArgError, 'option requires argument: --bar (alias for --realbar)'); }); test('error: non-function type', () => { const argv = []; - expect(() => arg({'--foo': 10}, {argv})).to.throw('Type missing or not a function or valid array type: --foo'); - expect(() => arg({'--foo': null}, {argv})).to.throw('Type missing or not a function or valid array type: --foo'); - expect(() => arg({'--foo': undefined}, {argv})).to.throw('Type missing or not a function or valid array type: --foo'); + expect(() => arg({'--foo': 10}, {argv})).to.throw(arg.ArgError, 'type missing or not a function or valid array type: --foo'); + expect(() => arg({'--foo': null}, {argv})).to.throw(arg.ArgError, 'type missing or not a function or valid array type: --foo'); + expect(() => arg({'--foo': undefined}, {argv})).to.throw(arg.ArgError, 'type missing or not a function or valid array type: --foo'); }); test('error: no singular - keys allowed', () => { const argv = ['--foo', '--bar', '1234']; - expect(() => arg({'-': Boolean, '--bar': Number}, {argv})).to.throw('Argument key must have a name; singular \'-\' keys are not allowed: -'); + expect(() => arg({'-': Boolean, '--bar': Number}, {argv})).to.throw(arg.ArgError, 'argument key must have a name; singular \'-\' keys are not allowed: -'); }); test('error: no multi character short arguments', () => { const argv = ['--foo', '--bar', '1234']; - expect(() => arg({'-abc': Boolean, '--bar': Number}, {argv})).to.throw('Short argument keys (with a single hyphen) must have only one character: -abc'); + expect(() => arg({'-abc': Boolean, '--bar': Number}, {argv})).to.throw(arg.ArgError, 'short argument keys (with a single hyphen) must have only one character: -abc'); }); test('permissive mode allows unknown args', () => { @@ -201,7 +201,7 @@ test('ensure that all argument properties start with a hyphen', () => { bar: String, '--baz': Boolean }) - ).to.throw(TypeError, 'Argument key must start with \'-\' but found: \'bar\''); + ).to.throw(arg.ArgError, 'argument key must start with \'-\' but found: \'bar\''); }); test('ensure argument property is not an empty string', () => { @@ -209,7 +209,7 @@ test('ensure argument property is not an empty string', () => { arg({ '': Number }) - ).to.throw(TypeError, 'Argument key cannot be an empty string'); + ).to.throw(arg.ArgError, 'argument key cannot be an empty string'); }); test('types with the Flag symbol should be passed true instead of an argument', () => { @@ -312,7 +312,7 @@ test('should error if a non-flag shortarg comes before a shortarg flag in a cond '-s': String }, { argv - })).to.throw(TypeError, 'Option requires argument (but was followed by another short argument): -s'); + })).to.throw(arg.ArgError, 'option requires argument (but was followed by another short argument): -s'); }); test('should stop parsing early with positional argument', () => { @@ -385,7 +385,7 @@ test('should error if numeric type is followed by non-negative, non-argument', ( '--int': Number }, { argv - })).to.throw(Error, 'Option requires argument: --int'); + })).to.throw(arg.ArgError, 'option requires argument: --int'); }); test('should error if negative numeric argument is passed to non-negative argument', () => { @@ -395,5 +395,5 @@ test('should error if negative numeric argument is passed to non-negative argume '--str': String }, { argv - })).to.throw(Error, 'Option requires argument: --str'); + })).to.throw(arg.ArgError, 'option requires argument: --str'); }); From cbc4e8db8fb1a2938d7f5889679ff1a11b604de4 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Tue, 17 Nov 2020 02:28:02 +0100 Subject: [PATCH 3/5] make description a bit more descriptive --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e45083e..9f224c0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Arg [![CircleCI](https://circleci.com/gh/zeit/arg.svg?style=svg)](https://circleci.com/gh/zeit/arg) -`arg` is yet another command line option parser. +`arg` is an unopinionated, no-frills CLI argument parser. ## Installation diff --git a/package.json b/package.json index 2226189..6a50990 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "arg", "version": "4.1.3", - "description": "Another simple argument parser", + "description": "Unopinionated, no-frills CLI argument parser", "main": "index.js", "types": "index.d.ts", "repository": "zeit/arg", From 48177627295aca7cc158235e2e3605207c51a307 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Tue, 17 Nov 2020 02:30:07 +0100 Subject: [PATCH 4/5] ZEIT -> Vercel --- LICENSE.md | 1 + README.md | 7 +++++-- package.json | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 4cc2d3c..ed59ecc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,7 @@ MIT License Copyright (c) 2017-2019 Zeit, Inc. +Copyright (c) 2020 Vercel, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 9f224c0..2fbef59 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Arg [![CircleCI](https://circleci.com/gh/zeit/arg.svg?style=svg)](https://circleci.com/gh/zeit/arg) +# Arg [![CircleCI](https://circleci.com/gh/vercel/arg.svg?style=svg)](https://circleci.com/gh/vercel/arg) `arg` is an unopinionated, no-frills CLI argument parser. @@ -291,4 +291,7 @@ if (!args['--name']) throw new Error('missing required argument: --name'); # License -Copyright © 2017-2019 by ZEIT, Inc. Released under the [MIT License](LICENSE.md). +Copyright © 2017-2019 by ZEIT, Inc. +Copyright © 2020 by Vercel, Inc. + +Released under the [MIT License](LICENSE.md). diff --git a/package.json b/package.json index 6a50990..e37ce67 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Unopinionated, no-frills CLI argument parser", "main": "index.js", "types": "index.d.ts", - "repository": "zeit/arg", - "author": "Josh Junon ", + "repository": "vercel/arg", + "author": "Josh Junon ", "license": "MIT", "files": [ "index.js", From 99b578ee3e7822c8e42a02907c02f58f5d2745e8 Mon Sep 17 00:00:00 2001 From: Josh Junon Date: Tue, 17 Nov 2020 02:30:51 +0100 Subject: [PATCH 5/5] 5.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e37ce67..edce0c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "arg", - "version": "4.1.3", + "version": "5.0.0", "description": "Unopinionated, no-frills CLI argument parser", "main": "index.js", "types": "index.d.ts",