From b7c8b7749da3c227f16169c74a8cec98778e6318 Mon Sep 17 00:00:00 2001 From: Osamaali313 <86572800+Osamaali313@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:54:51 +0300 Subject: [PATCH 1/2] fix(args): honor explicit value on boolean flags (--flag=false) parseFlags set a boolean flag to `true` as soon as it matched the flag, before inspecting an inline `=value`. So `--flag=false` (or `--flag=0`) was silently set to `true` -- the opposite of what the user typed -- and the value was discarded. Honor an explicit value: `--flag=false`/`0`/`no`/`off` -> false; a bare `--flag` and `--flag=true` (or any other value) stay true, so there is no behavior change for existing usage. Adds parseFlags tests for the bare-flag and explicit-value cases. --- src/args.ts | 7 ++++++- test/args.test.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/args.ts b/src/args.ts index 3e37952d..a5c2e3e2 100644 --- a/src/args.ts +++ b/src/args.ts @@ -114,7 +114,12 @@ export function parseFlags(argv: string[], options: OptionDef[]): GlobalFlags { const camelKey = kebabToCamel(key); if (schema.booleans.has(camelKey)) { - (flags as Record)[camelKey] = true; + // A bare boolean flag (`--flag`) is true. Honour an explicit value + // such as `--flag=false` / `--flag=0` instead of silently forcing the + // flag to true and discarding what the user typed. + (flags as Record)[camelKey] = + value === undefined || + !['false', '0', 'no', 'off'].includes(value.trim().toLowerCase()); i++; continue; } diff --git a/test/args.test.ts b/test/args.test.ts index e71ac2cb..ce70a8f5 100644 --- a/test/args.test.ts +++ b/test/args.test.ts @@ -5,6 +5,7 @@ import type { OptionDef } from '../src/command'; const OPTIONS: OptionDef[] = [ { flag: '--timeout ', description: 'Request timeout', type: 'number' }, { flag: '--message ', description: 'Message text', type: 'array' }, + { flag: '--verbose', description: 'Verbose output' }, ]; describe('parseFlags', () => { @@ -25,4 +26,15 @@ describe('parseFlags', () => { expect(flags.timeout).toBe(1.5); }); + + it('treats a bare boolean flag as true', () => { + expect(parseFlags(['--verbose'], OPTIONS).verbose).toBe(true); + }); + + it('honours an explicit value on a boolean flag instead of forcing true', () => { + // Regression: `--flag=false` / `--flag=0` were silently set to true. + expect(parseFlags(['--verbose=false'], OPTIONS).verbose).toBe(false); + expect(parseFlags(['--verbose=0'], OPTIONS).verbose).toBe(false); + expect(parseFlags(['--verbose=true'], OPTIONS).verbose).toBe(true); + }); }); From 9f1f093b812f5bbf90bed2ce7745225469496ca1 Mon Sep 17 00:00:00 2001 From: Osamaali313 <86572800+Osamaali313@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:15:26 +0300 Subject: [PATCH 2/2] refactor(args): validate explicit boolean flag values (review feedback) Address PR review: - hoist the recognised true/false token sets to module-level Sets instead of allocating an array on every boolean-flag parse - accept common spellings (true/1/yes/on, false/0/no/off; case- and whitespace-insensitive) and throw a clear error for an unrecognised explicit value (e.g. `--flag=maybe`, `--flag=`) so a typo cannot silently enable a flag, consistent with numeric-flag validation - expand tests to cover no/off, case/whitespace, true-like values, and the rejection path --- src/args.ts | 25 ++++++++++++++++++++----- test/args.test.ts | 23 +++++++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/args.ts b/src/args.ts index a5c2e3e2..0fe2f343 100644 --- a/src/args.ts +++ b/src/args.ts @@ -1,6 +1,10 @@ import type { GlobalFlags } from './types/flags'; import type { OptionDef } from './command'; +/** Recognised spellings for an explicit boolean flag value, e.g. `--flag=false`. */ +const BOOLEAN_TRUE_VALUES = new Set(['true', '1', 'yes', 'on']); +const BOOLEAN_FALSE_VALUES = new Set(['false', '0', 'no', 'off']); + function kebabToCamel(str: string): string { return str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase()); } @@ -115,11 +119,22 @@ export function parseFlags(argv: string[], options: OptionDef[]): GlobalFlags { if (schema.booleans.has(camelKey)) { // A bare boolean flag (`--flag`) is true. Honour an explicit value - // such as `--flag=false` / `--flag=0` instead of silently forcing the - // flag to true and discarding what the user typed. - (flags as Record)[camelKey] = - value === undefined || - !['false', '0', 'no', 'off'].includes(value.trim().toLowerCase()); + // (`--flag=false`, `--flag=0`, ...) and reject an unrecognised one so a + // typo cannot silently enable the flag, mirroring numeric-flag handling. + if (value === undefined) { + (flags as Record)[camelKey] = true; + } else { + const normalized = value.trim().toLowerCase(); + if (BOOLEAN_TRUE_VALUES.has(normalized)) { + (flags as Record)[camelKey] = true; + } else if (BOOLEAN_FALSE_VALUES.has(normalized)) { + (flags as Record)[camelKey] = false; + } else { + throw new Error( + `Flag --${key} requires a boolean value (e.g. true/false), got "${value}".`, + ); + } + } i++; continue; } diff --git a/test/args.test.ts b/test/args.test.ts index ce70a8f5..e196d3e2 100644 --- a/test/args.test.ts +++ b/test/args.test.ts @@ -31,10 +31,25 @@ describe('parseFlags', () => { expect(parseFlags(['--verbose'], OPTIONS).verbose).toBe(true); }); - it('honours an explicit value on a boolean flag instead of forcing true', () => { + it('honours explicit false-like values on a boolean flag', () => { // Regression: `--flag=false` / `--flag=0` were silently set to true. - expect(parseFlags(['--verbose=false'], OPTIONS).verbose).toBe(false); - expect(parseFlags(['--verbose=0'], OPTIONS).verbose).toBe(false); - expect(parseFlags(['--verbose=true'], OPTIONS).verbose).toBe(true); + for (const v of ['false', '0', 'no', 'off', ' OFF ', 'False']) { + expect(parseFlags([`--verbose=${v}`], OPTIONS).verbose).toBe(false); + } + }); + + it('honours explicit true-like values on a boolean flag', () => { + for (const v of ['true', '1', 'yes', 'on', 'TRUE']) { + expect(parseFlags([`--verbose=${v}`], OPTIONS).verbose).toBe(true); + } + }); + + it('rejects an unrecognised explicit boolean value', () => { + expect(() => parseFlags(['--verbose=maybe'], OPTIONS)).toThrow( + 'Flag --verbose requires a boolean value', + ); + expect(() => parseFlags(['--verbose='], OPTIONS)).toThrow( + 'Flag --verbose requires a boolean value', + ); }); });