Skip to content

Commit eca6acf

Browse files
authored
Merge pull request #179 from Osamaali313/fix/boolean-flag-explicit-value
fix(args): honor explicit value on boolean flags (--flag=false)
2 parents 9ad4f56 + 9f1f093 commit eca6acf

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

src/args.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import type { GlobalFlags } from './types/flags';
22
import type { OptionDef } from './command';
33

4+
/** Recognised spellings for an explicit boolean flag value, e.g. `--flag=false`. */
5+
const BOOLEAN_TRUE_VALUES = new Set(['true', '1', 'yes', 'on']);
6+
const BOOLEAN_FALSE_VALUES = new Set(['false', '0', 'no', 'off']);
7+
48
function kebabToCamel(str: string): string {
59
return str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
610
}
@@ -114,7 +118,23 @@ export function parseFlags(argv: string[], options: OptionDef[]): GlobalFlags {
114118
const camelKey = kebabToCamel(key);
115119

116120
if (schema.booleans.has(camelKey)) {
117-
(flags as Record<string, unknown>)[camelKey] = true;
121+
// A bare boolean flag (`--flag`) is true. Honour an explicit value
122+
// (`--flag=false`, `--flag=0`, ...) and reject an unrecognised one so a
123+
// typo cannot silently enable the flag, mirroring numeric-flag handling.
124+
if (value === undefined) {
125+
(flags as Record<string, unknown>)[camelKey] = true;
126+
} else {
127+
const normalized = value.trim().toLowerCase();
128+
if (BOOLEAN_TRUE_VALUES.has(normalized)) {
129+
(flags as Record<string, unknown>)[camelKey] = true;
130+
} else if (BOOLEAN_FALSE_VALUES.has(normalized)) {
131+
(flags as Record<string, unknown>)[camelKey] = false;
132+
} else {
133+
throw new Error(
134+
`Flag --${key} requires a boolean value (e.g. true/false), got "${value}".`,
135+
);
136+
}
137+
}
118138
i++;
119139
continue;
120140
}

test/args.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { OptionDef } from '../src/command';
55
const OPTIONS: OptionDef[] = [
66
{ flag: '--timeout <seconds>', description: 'Request timeout', type: 'number' },
77
{ flag: '--message <text>', description: 'Message text', type: 'array' },
8+
{ flag: '--verbose', description: 'Verbose output' },
89
];
910

1011
describe('parseFlags', () => {
@@ -25,4 +26,30 @@ describe('parseFlags', () => {
2526

2627
expect(flags.timeout).toBe(1.5);
2728
});
29+
30+
it('treats a bare boolean flag as true', () => {
31+
expect(parseFlags(['--verbose'], OPTIONS).verbose).toBe(true);
32+
});
33+
34+
it('honours explicit false-like values on a boolean flag', () => {
35+
// Regression: `--flag=false` / `--flag=0` were silently set to true.
36+
for (const v of ['false', '0', 'no', 'off', ' OFF ', 'False']) {
37+
expect(parseFlags([`--verbose=${v}`], OPTIONS).verbose).toBe(false);
38+
}
39+
});
40+
41+
it('honours explicit true-like values on a boolean flag', () => {
42+
for (const v of ['true', '1', 'yes', 'on', 'TRUE']) {
43+
expect(parseFlags([`--verbose=${v}`], OPTIONS).verbose).toBe(true);
44+
}
45+
});
46+
47+
it('rejects an unrecognised explicit boolean value', () => {
48+
expect(() => parseFlags(['--verbose=maybe'], OPTIONS)).toThrow(
49+
'Flag --verbose requires a boolean value',
50+
);
51+
expect(() => parseFlags(['--verbose='], OPTIONS)).toThrow(
52+
'Flag --verbose requires a boolean value',
53+
);
54+
});
2855
});

0 commit comments

Comments
 (0)