OneOf Option
The oneOf option type lets a single flag accept values of different types.
This is common in CLI tools where a flag doubles as a boolean toggle and
a typed selector — for example --color / --no-color / --color=always.
When boolean is one of the value types, bare flags (--flag), negation
(--no-flag), and the literals true / false are claimed exclusively
by the boolean parser. All other values are tried against the remaining
types in array order.
Boolean-or-string: the --color pattern
The most common use case for oneOf is a flag that toggles a feature on/off
but also accepts specific modes. Think git --color, ls --color, or
webpack --devtool.
1import cliForge from 'cli-forge';
2
3const cli = cliForge('color-demo')
4 .option('color', {
5 type: 'oneOf',
6 valueTypes: [
7 { type: 'string', choices: ['auto', 'always', 'never'] as const },
8 { type: 'boolean' },
9 ],
10 default: 'auto',
11 description: 'When to use colors in output',
12 })
13 .handler((args) => {
14 console.log(`Color mode: ${args.color}`);
15 });
16
17export default cli;
18
19if (require.main === module) {
20 (async () => {
21 await cli.forge();
22 })();
23}The valueTypes array lists the types the flag can accept. Boolean is
always tried last, regardless of where it appears in the array, but it has
exclusive claim on three patterns:
| Input | Result | Why |
|---|---|---|
--color | true | Bare flag — only boolean can match with no value |
--no-color | false | Negation prefix — boolean-only |
--color true | true | Literal true/false — boolean claims these |
--color always | "always" | Any other value — string parser wins |
When the string entry includes choices, only those values are accepted.
Passing --color invalid would fail validation because "invalid" is not
in ['auto', 'always', 'never'].
Number-or-string: priority ordering
For non-boolean types, array order determines priority. The first parser that successfully handles the value wins. This is useful when a value could be interpreted as multiple types.
1import cliForge from 'cli-forge';
2
3const cli = cliForge('verbosity-demo')
4 .option('verbose', {
5 type: 'oneOf',
6 valueTypes: [{ type: 'number' }, { type: 'string' }],
7 description: 'Set verbosity as a number (0-5) or named level',
8 })
9 .handler((args) => {
10 if (args.verbose === undefined) {
11 console.log('Verbosity: default');
12 } else {
13 console.log(`Verbosity (${typeof args.verbose}): ${args.verbose}`);
14 }
15 });
16
17export default cli;
18
19if (require.main === module) {
20 (async () => {
21 await cli.forge();
22 })();
23}Here number is listed before string, so --verbose 3 parses as the
number 3, not the string "3". If the value is not numeric (like
--verbose debug), the number parser fails and the string parser takes over.
Without boolean in valueTypes, bare flags (--verbose) and negation
(--no-verbose) are errors — the option always requires an explicit value.
Inside object properties
oneOf can be used as the type for an object property. This lets a single
dot-notation path accept values of different types. In this example, each
filter property accepts either a shorthand string (--filter.prs ">5")
or structured sub-properties via dot-notation (--filter.prs.min 1).
1import cliForge from 'cli-forge';
2
3const cli = cliForge('filter-demo')
4 .option('filter', {
5 type: 'object',
6 description: 'Filter criteria for search results',
7 properties: {
8 prs: {
9 type: 'oneOf',
10 description:
11 'Filter by PR count — a shorthand like ">5" or structured min/max',
12 valueTypes: [
13 {
14 type: 'object',
15 properties: {
16 min: { type: 'number' },
17 max: { type: 'number' },
18 },
19 },
20 { type: 'string' },
21 ],
22 } as any,
23 stars: {
24 type: 'oneOf',
25 description:
26 'Filter by star count — a shorthand like ">=100" or structured min/max',
27 valueTypes: [
28 {
29 type: 'object',
30 properties: {
31 min: { type: 'number' },
32 max: { type: 'number' },
33 },
34 },
35 { type: 'string' },
36 ],
37 } as any,
38 },
39 })
40 .handler((args) => {
41 const parts: string[] = [];
42 if (args.filter?.prs !== undefined) {
43 parts.push(`prs=${JSON.stringify(args.filter.prs)}`);
44 }
45 if (args.filter?.stars !== undefined) {
46 parts.push(`stars=${JSON.stringify(args.filter.stars)}`);
47 }
48 if (parts.length === 0) {
49 console.log('No filters applied');
50 } else {
51 console.log(`Filters: ${parts.join(', ')}`);
52 }
53 });
54
55export default cli;
56
57if (require.main === module) {
58 (async () => {
59 await cli.forge();
60 })();
61}When the value is set directly (e.g. --filter.prs ">5"), the oneOf
parser tries each value type in order. The object parser fails on ">5"
(not valid JSON), so the string parser takes over.
When dot-notation is used (e.g. --filter.prs.min 1), the object parser
recognizes the oneOf type and searches its valueTypes for an object
branch that has the requested sub-property.
| Input | Result |
|---|---|
--filter.prs ">5" | { prs: ">5" } — string branch wins |
--filter.prs.min 1 | { prs: { min: 1 } } — object branch, dot-notation |
--filter.prs.min 1 --filter.prs.max 10 | { prs: { min: 1, max: 10 } } |
--filter.prs ">5" --filter.stars.min 100 | { prs: ">5", stars: { min: 100 } } — mixed |
All Example Files
1import cliForge from 'cli-forge';
2
3const cli = cliForge('color-demo')
4 .option('color', {
5 type: 'oneOf',
6 valueTypes: [
7 { type: 'string', choices: ['auto', 'always', 'never'] as const },
8 { type: 'boolean' },
9 ],
10 default: 'auto',
11 description: 'When to use colors in output',
12 })
13 .handler((args) => {
14 console.log(`Color mode: ${args.color}`);
15 });
16
17export default cli;
18
19if (require.main === module) {
20 (async () => {
21 await cli.forge();
22 })();
23}
24