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

このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。 合格した契約受領証を提出した異なる署名鍵の数です。1 なら作者だけ、2 以上なら他の誰かもビルドしています。鍵は自己生成で背後に登録された身元がないため、数えているのは人ではなく鍵です。 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