CodeSampleX

샘플

commander 15.0.0: Prevent negative number option parsing failures in Commander when registering digit short option flags

검증된 샘플 — npm commander 15.0.0: Prevent negative number option parsing failures in Commander when registering digit short option flags. node 22 · linux…

sha256:e84a5f7f9b0e235218dd1ae017a1fdba5fdf2fce9c93b2143bdf8d153a9953fc

이 네트워크가 제공하는 것은 하나입니다. 빌드되는 샘플. 샌드박스에서 돌리고 서명된 영수증을 보관합니다. 등급을 매기지 않고 무엇도 보증하지 않습니다 — 같은 코드가 당신 환경에서 빌드되는지는 측정한 적이 없습니다. 통과한 계약 영수증을 낸 서로 다른 서명 키의 수입니다. 하나면 작성자 혼자이고, 둘 이상이면 다른 사람도 빌드했다는 뜻입니다. 키는 스스로 만드는 것이고 뒤에 등록된 신원이 없으므로, 세는 것은 사람이 아니라 키입니다. MIT-0

실행 증거

선언된 환경과 서명된 실행을 분리해 두었습니다. 이 샘플이 무엇을 어디서 실행했는지 그대로 볼 수 있습니다.

증거 기준
서명된 컨트랙트 통과
검증 영수증
3
빌드한 서명 키
3
선언된 환경 node linux x64 node node npm

검증 실행 환경

환경 컨트랙트 단계 실행일
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:2175b912ea1c23b1 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-18
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-08

케이스

HOW
목표
Prevent negative number option parsing failures in Commander when registering digit short option flags
패키지
심벌
  • Command.option
  • Command.parse
  • Option
환경
node
생성일
2026-08-17T01:48:52Z

컨트랙트

  1. Passing -1 after an optional-argument option evaluates that option to boolean true and parses -1 as a distinct flag if any single-digit short option is defined on the command hierarchy, because Commander disables negative number heuristics whenever a digit flag exists.
  2. Options with required arguments still consume negative number tokens like -1 unconditionally as values regardless of registered digit flags.
  3. Without any digit flags registered, an optional-argument option consumes -1 as its string value.
  4. Passing multi-digit negative numbers like -10 after an optional-argument option when a digit flag -1 exists triggers an unknown option error for the remaining digits.
  5. Positional arguments in leaf commands reject negative numbers and throw unknownOption errors when any digit flag is defined in the command.

파일

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • test/contract.mjs
  • test1.mjs
  • test2.mjs
  • test3.mjs
  • test4.mjs
  • test5.mjs
  • test6.mjs
  • test_clean.mjs
  • test_prototype.mjs

소스 아티팩트 내려받기 (tar.gz)

소스

NOTES.md
# Negative Number Option Parsing and Digit Short Flags in Commander

## Known Solutions Search

A search via `search_known_solution` returned existing compatible samples covering:
- Subcommand action argument unpacking and custom coerce radix traps (`sha256:05e524cd641f3e66c380604099a1c591c4fbe976012949d4819b1f3ec255ed9f`)
- Positional argument default value retrieval via `processedArgs` (`sha256:3fc59d6ae2df342949bb6f6c09dcdc027cda2183237046c0189f6cabfae569e9`)
- `Option.preset` vs `Option.default` behavior on optional flags (`sha256:9ed29f29e55e4074676913ca3bb9009038881ff559b26efc27a6df45e0d7e648`)
- Error interception and `CommanderError` wrapping (`sha256:94607693bb374f0a65026f93ee8ff32d6560d2668a0b2e16a5e6ebc40f4ddd52`)
- Boolean negation option default behavior in Commander 15 (`sha256:e9734b35dd9bf261570327aa6767cba61b29225dbb1b6eebc59d47cb344f1141`)
- Boolean environment variable truthiness parsing (`sha256:b953db7ef30e702f31dca9192e6ef548a50589157551d3ada4896bd8624d6d8d`)

None of the existing samples addressed negative number argument parsing interactions with single-digit short flags.

## Failure Mode

The wrong version fails loudly with an unknownOption error when passing multi-digit negative values or positional arguments, and fails silently with a green build when passing single-digit negative arguments to optional options by assigning boolean true and inadvertently activating the digit flag.
csx.json
{"case":{"believed":"An option configured with an optional value accepts negative numbers like -1 as its argument regardless of what other short option flags are registered on the command.","caseId":"case:sha256:a1a54d8409b0c3eea40222c758c8fd3c05f023431c8ca98a9181518b5f1da0d4","contract":["Passing -1 after an optional-argument option evaluates that option to boolean true and parses -1 as a distinct flag if any single-digit short option is defined on the command hierarchy, because Commander disables negative number heuristics whenever a digit flag exists.","Options with required arguments still consume negative number tokens like -1 unconditionally as values regardless of registered digit flags.","Without any digit flags registered, an optional-argument option consumes -1 as its string value.","Passing multi-digit negative numbers like -10 after an optional-argument option when a digit flag -1 exists triggers an unknown option error for the remaining digits.","Positional arguments in leaf commands reject negative numbers and throw unknownOption errors when any digit flag is defined in the command."],"goal":"Prevent negative number option parsing failures in Commander when registering digit short option flags","kind":"HOW","packages":["pkg:npm/commander@15.0.0"],"schemaVersion":1,"symbols":["Command.option","Command.parse","Option"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","language":"node","os":"linux","packageManager":"npm","runtime":"node","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/commander@15.0.0"],"schemaVersion":1,"symbols":["Command.option","Command.parse","Option"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "npm-commander-d8",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "npm-commander-d8",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "commander": "^15.0.0"
      }
    },
    "node_modules/commander": {
      "version": "15.0.0",
      "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
      "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
      "license": "MIT",
      "engines": {
        "node": ">=22.12.0"
      }
    }
  }
}
package.json
{
  "name": "npm-commander-d8",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "test": "node test/contract.mjs"
  },
  "dependencies": {
    "commander": "^15.0.0"
  }
}
test/contract.mjs
import assert from 'node:assert/strict';
import { Command, CommanderError } from 'commander';

// 1. Passing -1 after an optional-argument option evaluates that option to boolean true and parses -1 as a distinct flag if any single-digit short option is defined on the command hierarchy, because Commander disables negative number heuristics whenever a digit flag exists.
{
  const program = new Command();
  program.exitOverride();
  program.configureOutput({ writeErr: () => {} });
  program.option('-1, --one-line', 'display one line');
  program.option('-t, --threshold [val]', 'cutoff threshold');

  program.parse(['node', 'cli', '-t', '-1']);
  const opts = program.opts();

  assert.strictEqual(opts.threshold, true);
  assert.strictEqual(opts.oneLine, true);
}

// 2. Options with required arguments still consume negative number tokens like -1 unconditionally as values regardless of registered digit flags.
{
  const program = new Command();
  program.exitOverride();
  program.configureOutput({ writeErr: () => {} });
  program.option('-1, --one-line', 'display one line');
  program.option('-r, --required <val>', 'required value');

  program.parse(['node', 'cli', '-r', '-1']);
  const opts = program.opts();

  assert.strictEqual(opts.required, '-1');
  assert.strictEqual(opts.oneLine, undefined);
}

// 3. Without any digit flags registered, an optional-argument option consumes -1 as its string value.
{
  const program = new Command();
  program.exitOverride();
  program.configureOutput({ writeErr: () => {} });
  program.option('-v, --verbose', 'verbose output');
  program.option('-t, --threshold [val]', 'cutoff threshold');

  program.parse(['node', 'cli', '-t', '-1']);
  const opts = program.opts();

  assert.strictEqual(opts.threshold, '-1');
  assert.strictEqual(opts.verbose, undefined);
}

// 4. Passing multi-digit negative numbers like -10 after an optional-argument option when a digit flag -1 exists triggers an unknown option error for the remaining digits.
{
  const program = new Command();
  program.exitOverride();
  program.configureOutput({ writeErr: () => {} });
  program.option('-1, --one-line', 'display one line');
  program.option('-t, --threshold [val]', 'cutoff threshold');

  assert.throws(
    () => program.parse(['node', 'cli', '-t', '-10']),
    (err) => {
      assert(err instanceof CommanderError);
      assert.strictEqual(err.code, 'commander.unknownOption');
      assert.match(err.message, /unknown option '-0'/);
      return true;
    }
  );
}

// 5. Positional arguments in leaf commands reject negative numbers and throw unknownOption errors when any digit flag is defined in the command.
{
  const program = new Command();
  program.exitOverride();
  program.configureOutput({ writeErr: () => {} });
  program.option('-1, --one-line', 'display one line');
  program.argument('[offset]', 'coordinate offset');

  assert.throws(
    () => program.parse(['node', 'cli', '-5']),
    (err) => {
      assert(err instanceof CommanderError);
      assert.strictEqual(err.code, 'commander.unknownOption');
      assert.match(err.message, /unknown option '-5'/);
      return true;
    }
  );
}
test1.mjs
import { Command, Option, InvalidArgumentError } from 'commander';

console.log('Testing commander edge cases...');

// Test 1: Negative numbers and digit options
{
  const p1 = new Command();
  p1.exitOverride();
  p1.option('-n, --num <n>');
  p1.parse(['node', 'test', '-n', '-5']);
  console.log('p1 opts:', p1.opts());

  const p2 = new Command();
  p2.exitOverride();
  p2.option('-1, --one');
  p2.option('-n, --num <n>');
  try {
    p2.parse(['node', 'test', '-n', '-5']);
    console.log('p2 opts:', p2.opts());
  } catch (e) {
    console.log('p2 threw with digit option:', e.code, e.message);
  }
}

// Test 2: Optional argument with negative numbers vs flags vs empty string
{
  const p3 = new Command();
  p3.exitOverride();
  p3.option('-o, --optional [val]', 'opt val', 'defaultVal');
  p3.parse(['node', 'test', '-o', '-5']);
  console.log('p3 with -5:', p3.opts());

  const p3b = new Command();
  p3b.exitOverride();
  p3b.option('-1, --one');
  p3b.option('-o, --optional [val]', 'opt val', 'defaultVal');
  try {
    p3b.parse(['node', 'test', '-o', '-5']);
    console.log('p3b with -5:', p3b.opts());
  } catch (e) {
    console.log('p3b threw:', e.code, e.message);
  }
}

// Test 3: Optional argument with empty string or '0' or following operand
{
  const p4 = new Command();
  p4.exitOverride();
  p4.option('-o, --opt [val]');
  p4.argument('[target]');
  p4.parse(['node', 'test', '-o', 'hello']);
  console.log('p4 opts:', p4.opts(), 'args:', p4.args);

  const p5 = new Command();
  p5.exitOverride();
  p5.option('-o, --opt [val]');
  p5.argument('[target]');
  p5.parse(['node', 'test', '-o', '']);
  console.log('p5 opts:', p5.opts(), 'args:', p5.args);
}
test2.mjs
import { Command, Option, InvalidArgumentError } from 'commander';

console.log('--- TEST 2 ---');

// Edge 1: Empty string "" as positional argument vs optional argument
{
  const p = new Command();
  p.exitOverride();
  p.argument('<req>');
  p.parse(['node', 'test', '']);
  console.log('p req with "":', p.processedArgs, p.args);
}

// Edge 2: Variadic argument with empty string or multiple empty strings
{
  const p = new Command();
  p.exitOverride();
  p.argument('<items...>');
  p.parse(['node', 'test', '', '']);
  console.log('variadic arg with "":', p.processedArgs);
}

// Edge 3: Variadic option with empty string
{
  const p = new Command();
  p.exitOverride();
  p.option('-v, --val <items...>');
  p.parse(['node', 'test', '-v', '', '']);
  console.log('variadic opt with "":', p.opts());
}

// Edge 4: Option with empty string default vs unset
{
  const p = new Command();
  p.exitOverride();
  p.option('-s, --str <val>', 'string val', '');
  p.parse(['node', 'test']);
  console.log('opt default "":', p.opts(), 'source:', p.getOptionValueSource('str'));

  const p2 = new Command();
  p2.exitOverride();
  p2.option('-s, --str <val>', 'string val', '');
  p2.parse(['node', 'test', '-s', '']);
  console.log('opt passed "":', p2.opts(), 'source:', p2.getOptionValueSource('str'));
}

// Edge 5: Negative numbers as positional arguments when subcommands exist vs leaf commands
{
  const root = new Command();
  root.exitOverride();
  root.argument('[pos]');
  const sub = root.command('sub');
  sub.argument('[pos]');
  
  try {
    root.parse(['node', 'test', '-5']);
    console.log('root with sub received -5:', root.args);
  } catch (e) {
    console.log('root with sub threw on -5:', e.code, e.message);
  }
}

// Edge 6: Single dash '-' as argument or option value
{
  const p = new Command();
  p.exitOverride();
  p.argument('<file>');
  p.option('-o, --output <file>');
  p.parse(['node', 'test', '-o', '-', '-']);
  console.log('single dash opts:', p.opts(), 'args:', p.args);
}

// Edge 7: Zero value '0' vs false vs empty string in env variables or boolean flags
{
  const p = new Command();
  p.exitOverride();
  p.addOption(new Option('-b, --bool').env('TEST_BOOL'));
  process.env.TEST_BOOL = '0';
  p.parse(['node', 'test']);
  console.log('bool env TEST_BOOL="0":', p.opts());
}

// Edge 8: Option with choices including empty string or negative numbers
{
  const p = new Command();
  p.exitOverride();
  p.addOption(new Option('-c, --choice <val>').choices(['', '0', '-1']));
  p.parse(['node', 'test', '-c', '']);
  console.log('choices with "":', p.opts());
  
  const p_neg = new Command();
  p_neg.exitOverride();
  p_neg.addOption(new Option('-c, --choice <val>').choices(['', '0', '-1']));
  try {
    p_neg.parse(['node', 'test', '-c', '-1']);
    console.log('choices with -1:', p_neg.opts());
  } catch (e) {
    console.log('choices with -1 threw:', e.code, e.message);
  }
}
test3.mjs
import { Command } from 'commander';

console.log('--- TEST 3: Negative numbers & digit options ---');

// Case A: Leaf command without digit options
const pA = new Command();
pA.exitOverride();
pA.option('-t, --threshold [val]', 'threshold');
pA.option('-v, --verbose', 'verbose');
pA.parse(['node', 'test', '-t', '-10']);
console.log('Case A (no digit options) opts:', pA.opts());

// Case B: Leaf command WITH digit option (-1)
const pB = new Command();
pB.exitOverride();
pB.option('-1, --fast', 'fast mode');
pB.option('-t, --threshold [val]', 'threshold');
pB.parse(['node', 'test', '-t', '-10']);
console.log('Case B (with digit option -1, passing -10):');
test4.mjs
import { Command } from 'commander';

console.log('--- TEST 4: Comparing behavior of negative numbers with and without digit options ---');

// Setup 1: No digit option
const p1 = new Command();
p1.exitOverride();
p1.option('-t, --threshold [val]', 'threshold');
p1.parse(['node', 'test', '-t', '-1']);
console.log('p1 (-t -1 without digit option):', p1.opts());
// Output: { threshold: '-1' }

// Setup 2: With digit option -1
const p2 = new Command();
p2.exitOverride();
p2.option('-1, --fast', 'fast mode');
p2.option('-t, --threshold [val]', 'threshold');
p2.parse(['node', 'test', '-t', '-1']);
console.log('p2 (-t -1 with digit option -1):', p2.opts());
// Output: { fast: true, threshold: undefined } or { fast: true }!

// Setup 3: Required argument vs Optional argument
const p3 = new Command();
p3.exitOverride();
p3.option('-1, --fast', 'fast mode');
p3.option('-r, --required <val>', 'required arg');
p3.parse(['node', 'test', '-r', '-1']);
console.log('p3 (-r -1 with digit option -1, required arg):', p3.opts());
// Output: { required: '-1' }!
test5.mjs
import { Command } from 'commander';

console.log('--- TEST 5: Variadic options with negative numbers ---');

// Variadic required
const p1 = new Command();
p1.exitOverride();
p1.option('-n, --numbers <vals...>');
p1.parse(['node', 'test', '-n', '-1', '-2', '3']);
console.log('Variadic required without digit opt:', p1.opts());

// Variadic required WITH digit opt
const p2 = new Command();
p2.exitOverride();
p2.option('-1, --one');
p2.option('-n, --numbers <vals...>');
try {
  p2.parse(['node', 'test', '-n', '-1', '-2', '3']);
  console.log('Variadic required with digit opt:', p2.opts());
} catch (e) {
  console.log('Variadic required with digit opt threw:', e.code, e.message);
}

// Variadic optional
const p3 = new Command();
p3.exitOverride();
p3.option('-n, --numbers [vals...]');
p3.parse(['node', 'test', '-n', '-1', '-2', '3']);
console.log('Variadic optional without digit opt:', p3.opts());

// Variadic optional WITH digit opt
const p4 = new Command();
p4.exitOverride();
p4.option('-1, --one');
p4.option('-n, --numbers [vals...]');
try {
  p4.parse(['node', 'test', '-n', '-1', '-2', '3']);
  console.log('Variadic optional with digit opt:', p4.opts());
} catch (e) {
  console.log('Variadic optional with digit opt threw:', e.code, e.message);
}
test6.mjs
import { Command } from 'commander';

console.log('--- TEST 6: Positional arguments and negative numbers ---');

// Root with argument only (leaf)
const leaf = new Command();
leaf.exitOverride();
leaf.argument('<target>', 'target position');
leaf.parse(['node', 'test', '-50']);
console.log('Leaf command with -50:', leaf.processedArgs);

// Root with argument AND subcommand
const rootWithSub = new Command();
rootWithSub.exitOverride();
rootWithSub.argument('[target]', 'target position');
rootWithSub.command('build');
try {
  rootWithSub.parse(['node', 'test', '-50']);
  console.log('Root with sub with -50:', rootWithSub.processedArgs);
} catch (e) {
  console.log('Root with sub threw on -50:', e.code, e.message);
}
test_clean.mjs
import assert from 'node:assert/strict';
import { Command, CommanderError } from 'commander';

const program = new Command();
program.exitOverride();
program.configureOutput({ writeErr: () => {} });
program.option('-1, --one-line');
program.argument('[offset]');

assert.throws(
  () => program.parse(['node', 'cli', '-5']),
  (err) => {
    assert(err instanceof CommanderError);
    assert.strictEqual(err.code, 'commander.unknownOption');
    return true;
  }
);
console.log('Clean error handling verified');
test_prototype.mjs
import assert from 'node:assert/strict';
import { Command, CommanderError } from 'commander';

// 1. First contract line: With a digit flag (-1), optional option receives `true` instead of '-1', and -1 triggers the digit option
{
  const program = new Command();
  program.exitOverride();
  program.option('-1, --one-line', 'display one line');
  program.option('-t, --threshold [val]', 'cutoff threshold');

  program.parse(['node', 'cli', '-t', '-1']);
  const opts = program.opts();

  assert.strictEqual(opts.threshold, true);
  assert.strictEqual(opts.oneLine, true);
}

// 2. Without digit flags, optional option consumes -1 as its value string
{
  const program = new Command();
  program.exitOverride();
  program.option('-v, --verbose', 'verbose output');
  program.option('-t, --threshold [val]', 'cutoff threshold');

  program.parse(['node', 'cli', '-t', '-1']);
  const opts = program.opts();

  assert.strictEqual(opts.threshold, '-1');
  assert.strictEqual(opts.verbose, undefined);
}

// 3. Required argument options unconditionally consume negative numbers as values even when digit flags exist
{
  const program = new Command();
  program.exitOverride();
  program.option('-1, --one-line', 'display one line');
  program.option('-r, --required <val>', 'required value');

  program.parse(['node', 'cli', '-r', '-1']);
  const opts = program.opts();

  assert.strictEqual(opts.required, '-1');
  assert.strictEqual(opts.oneLine, undefined);
}

// 4. Multi-digit negative number -10 with digit flag -1 parses -1 and throws unknownOption for remaining -0
{
  const program = new Command();
  program.exitOverride();
  program.option('-1, --one-line', 'display one line');
  program.option('-t, --threshold [val]', 'cutoff threshold');

  assert.throws(
    () => program.parse(['node', 'cli', '-t', '-10']),
    (err) => {
      assert(err instanceof CommanderError);
      assert.strictEqual(err.code, 'commander.unknownOption');
      assert.match(err.message, /unknown option '-0'/);
      return true;
    }
  );
}

// 5. Leaf command positional arguments reject negative numbers when a digit flag exists
{
  const program = new Command();
  program.exitOverride();
  program.option('-1, --one-line', 'display one line');
  program.argument('[offset]', 'coordinate offset');

  assert.throws(
    () => program.parse(['node', 'cli', '-5']),
    (err) => {
      assert(err instanceof CommanderError);
      assert.strictEqual(err.code, 'commander.unknownOption');
      assert.match(err.message, /unknown option '-5'/);
      return true;
    }
  );
}

// 6. Without digit flags, leaf command accepts negative number positional argument
{
  const program = new Command();
  program.exitOverride();
  program.argument('[offset]', 'coordinate offset');

  program.parse(['node', 'cli', '-5']);
  assert.deepStrictEqual(program.processedArgs, ['-5']);
}

console.log('All prototype contract assertions passed!');

오리진 시더

csx-seed