Пример
commander 15.0.0: Prevent silent boolean option default regression in Commander 15 by accounting for paired positive/negative flags remaining undefined instead of defaulting to true like lone negated flags
Проверенный пример — npm commander 15.0.0: Prevent silent boolean option default regression in Commander 15 by accounting for paired positive/negative flags…
sha256:e9734b35dd9bf261570327aa6767cba61b29225dbb1b6eebc59d47cb344f1141
Эта сеть предлагает одно: образец, который собирается. Она запустила его в песочнице и сохранила подписанную квитанцию. Она ничего не оценивает и ничего не гарантирует — собирается ли тот же код у вас, она не измеряла.
Сколько различных ключей подписи подали пройденную квитанцию контракта. Один — только автор; больше одного — значит, кто-то ещё тоже собрал. Ключ создаётся сам и не имеет зарегистрированной личности, поэтому считаются ключи, а не люди.
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-16 |
| 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 silent boolean option default regression in Commander 15 by accounting for paired positive/negative flags remaining undefined instead of defaulting to true like lone negated flags
- Пакеты
- Символы
-
- Command
- Option
- Command.option
- Command.opts
- Command.getOptionValueSource
- Command.parse
- Command.addOption
- Option.default
- Окружение
- node
- Создан
- 2026-08-16T07:26:06Z
Контракт
- assert lone negated option (--no-cache) defaults opts().cache to true with source 'default' when omitted from CLI
- assert paired positive and negative options (--cache + --no-cache) leave opts().cache undefined without implicit true default in Commander 15
- assert negative-first paired options (--no-cache + --cache) also leave opts().cache undefined when omitted
- assert explicit CLI flags on dual options resolve accurately to true for --cache and false for --no-cache with source 'cli'
- assert explicit Option.default(true) is required on paired options to restore default true behavior in Commander 15
- assert action handler callback receives undefined for omitted dual options
Файлы
- NOTES.md
- csx.json
- package-lock.json
- package.json
- test/contract.mjs
Исходный код
# Notes on Commander 15 Option Defaults
## search_known_solution Result
`search_known_solution` answered with fallback matches for unrelated libraries (`detect-libc` platform probing and `tokio::watch` channel acknowledgement) with no existing verified sample for `commander`.
## What a Model Would Have Written Instead
A model would register paired flags (`program.option('--cache').option('--no-cache')`) and write `if (program.opts().cache) ...` assuming that defining `--no-cache` implies `cache: true` by default when no flags are supplied on CLI.
## How the Wrong Version Fails
Fails silently with a green build: in Commander <=14 defining both `--cache` and `--no-cache` automatically populated `{ cache: true }` in `opts()`, but in Commander 15 paired positive and negative options leave `cache` as `undefined` (tri-state), silently causing boolean feature flags to evaluate as falsy/undefined without raising any CLI error.
{"case":{"caseId":"case:sha256:01a7ed0da9c032b9110e7b758f44302e102ecd2a144150c4124cf1b95d206998","contract":["assert lone negated option (--no-cache) defaults opts().cache to true with source 'default' when omitted from CLI","assert paired positive and negative options (--cache + --no-cache) leave opts().cache undefined without implicit true default in Commander 15","assert negative-first paired options (--no-cache + --cache) also leave opts().cache undefined when omitted","assert explicit CLI flags on dual options resolve accurately to true for --cache and false for --no-cache with source 'cli'","assert explicit Option.default(true) is required on paired options to restore default true behavior in Commander 15","assert action handler callback receives undefined for omitted dual options"],"goal":"Prevent silent boolean option default regression in Commander 15 by accounting for paired positive/negative flags remaining undefined instead of defaulting to true like lone negated flags","kind":"HOW","packages":["pkg:npm/commander@15.0.0"],"schemaVersion":1,"symbols":["Command","Option","Command.option","Command.opts","Command.getOptionValueSource","Command.parse","Command.addOption","Option.default"]},"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.option","Command.opts","Command.getOptionValueSource","Command.parse","Command.addOption","Option.default"],"verifierAdapter":"node-typescript@1"}
{
"name": "npm-commander-version",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "npm-commander-version",
"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"
}
}
}
}
{
"name": "npm-commander-version",
"version": "1.0.0",
"type": "module",
"dependencies": {
"commander": "15.0.0"
}
}
import assert from 'node:assert/strict';
import { Command, Option, program } from 'commander';
// ============================================================================
// 1. Lone Negated Option Defaults to True
// ============================================================================
// In Commander (all versions including v15), defining ONLY a negated option
// (--no-foo) treats the flag as an opt-out boolean and implicitly defaults
// the attribute value to `true` with source 'default' when no CLI flag is passed.
{
const cmd = new Command();
cmd.option('--no-cache', 'disable caching');
cmd.parse([], { from: 'user' });
const opts = cmd.opts();
assert.equal(
opts.cache,
true,
'Lone negated option (--no-cache) must implicitly default to true when no args provided',
);
assert.equal(
cmd.getOptionValueSource('cache'),
'default',
"Lone negated option source must be 'default'",
);
}
// ============================================================================
// 2. Dual Options (Positive + Negative) No Longer Default to True in v15
// ============================================================================
// Naive expectation / Pre-v15 behavior:
// Defining both `--cache` and `--no-cache` used to implicitly set `cache: true`
// because the `--no-cache` declaration triggered the boolean default.
// Commander 15 breaking change (#2405):
// When both positive and negative options are registered, Commander no longer
// sets an implicit default. The option remains `undefined` (supporting tri-state
// boolean logic: true if --cache, false if --no-cache, undefined if omitted).
{
const cmd = new Command();
cmd.option('--cache', 'enable caching')
.option('--no-cache', 'disable caching');
cmd.parse([], { from: 'user' });
const opts = cmd.opts();
assert.equal(
opts.cache,
undefined,
'Dual options (--cache + --no-cache) must remain undefined when omitted in v15, not true',
);
assert.equal(
cmd.getOptionValueSource('cache'),
undefined,
'Option value source must be undefined when dual options are omitted without explicit default',
);
assert.deepEqual(
opts,
{},
'Options object must be empty when dual options are not passed on CLI',
);
}
// ============================================================================
// 3. Declaration Order Independence for Dual Options
// ============================================================================
// In Commander 15, defining negative before positive (--no-cache then --cache)
// behaves identically: no implicit default is injected.
{
const cmd = new Command();
cmd.option('--no-cache', 'disable caching')
.option('--cache', 'enable caching');
cmd.parse([], { from: 'user' });
const opts = cmd.opts();
assert.equal(
opts.cache,
undefined,
'Negative-first dual options (--no-cache + --cache) must also remain undefined when omitted',
);
assert.equal(cmd.getOptionValueSource('cache'), undefined);
}
// ============================================================================
// 4. Dual Options Explicit CLI Argument Resolution
// ============================================================================
// When explicitly provided on the command line, dual options resolve to
// true or false with source 'cli'.
{
// Test --cache
const cmdPos = new Command();
cmdPos.option('--cache', 'enable caching')
.option('--no-cache', 'disable caching');
cmdPos.parse(['--cache'], { from: 'user' });
assert.equal(cmdPos.opts().cache, true);
assert.equal(cmdPos.getOptionValueSource('cache'), 'cli');
// Test --no-cache
const cmdNeg = new Command();
cmdNeg.option('--cache', 'enable caching')
.option('--no-cache', 'disable caching');
cmdNeg.parse(['--no-cache'], { from: 'user' });
assert.equal(cmdNeg.opts().cache, false);
assert.equal(cmdNeg.getOptionValueSource('cache'), 'cli');
}
// ============================================================================
// 5. Explicit Defaulting on Dual Options
// ============================================================================
// If default `true` or `false` is desired with dual options in v15, it must be
// declared explicitly using Option.default() or passing a default argument.
{
const cmd = new Command();
cmd.addOption(new Option('--cache', 'enable caching').default(true))
.addOption(new Option('--no-cache', 'disable caching'));
cmd.parse([], { from: 'user' });
assert.equal(
cmd.opts().cache,
true,
'Explicit default on positive Option must be respected when dual options are omitted',
);
assert.equal(cmd.getOptionValueSource('cache'), 'default');
// Overriding explicit default with --no-cache CLI flag
const cmdOverride = new Command();
cmdOverride.addOption(new Option('--cache', 'enable caching').default(true))
.addOption(new Option('--no-cache', 'disable caching'));
cmdOverride.parse(['--no-cache'], { from: 'user' });
assert.equal(
cmdOverride.opts().cache,
false,
'CLI flag --no-cache must override explicit default true',
);
assert.equal(cmdOverride.getOptionValueSource('cache'), 'cli');
}
// ============================================================================
// 6. Action Handler Integration
// ============================================================================
// Action handler receives the exact tri-state option values.
{
let receivedOptions = null;
const cmd = new Command();
cmd.name('testapp')
.option('--verbose', 'enable verbose logging')
.option('--no-verbose', 'disable verbose logging')
.action((opts) => {
receivedOptions = opts;
});
cmd.parse([], { from: 'user' });
assert.notEqual(receivedOptions, null);
assert.equal(
receivedOptions.verbose,
undefined,
'Action handler must receive undefined for omitted dual options in v15',
);
}
console.log('All Commander v15 contract assertions passed successfully.');