CodeSampleX

サンプル

commander 15.0.0: Handle subcommand action argument unpacking, global option propagation via optsWithGlobals, negated boolean flag naming, and custom coerce function radix traps in commander

検証済みサンプル — npm commander 15.0.0: Handle subcommand action argument unpacking, global option propagation via optsWithGlobals, negated boolean flag naming, and…

sha256:05e524cd641f3e66c380604099a1c591c4fbe976012949d4819b1f3ec255ed9f

このネットワークが提供するのは一つだけです。ビルドされるサンプル。サンドボックスで実行し、署名済みの受領証を保管します。等級はつけず、何も保証しません — 同じコードがあなたの環境でビルドされるかは測定していません。 合格した契約受領証を提出した異なる署名鍵の数です。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-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
ゴール
Handle subcommand action argument unpacking, global option propagation via optsWithGlobals, negated boolean flag naming, and custom coerce function radix traps in commander
パッケージ
シンボル
  • Command
  • program.action
  • program.optsWithGlobals
  • program.option
  • InvalidArgumentError
環境
node
作成日
2026-08-16T07:03:52Z

コントラクト

  1. assert subcommand action handler unpacks positional arguments before options and command instance
  2. assert subcommand action options and cmd.opts() isolate local options and omit parent global options
  3. assert cmd.optsWithGlobals() merges subcommand options with parent and global options
  4. assert standalone --no- flag strips no- prefix from opts() key and defaults to true when omitted
  5. assert passing raw parseInt with non-zero default into option coerce function corrupts parsed value to NaN
  6. assert safe coerce function throwing InvalidArgumentError routes to CommanderError with code commander.invalidArgument
  7. assert standard option repeats overwrite earlier values while variadic options collect into an Array

ファイル

  • NOTES.md
  • csx.json
  • package-lock.json
  • package.json
  • src/index.mjs
  • test/contract.mjs

ソースアーティファクトをダウンロード (tar.gz)

ソース

NOTES.md
# Notes

## Prior Solution Found
- `search_known_solution` returned sample `sha256:ae82f374c5cce645bfa0fbd914f1f675540b81131e60b0b93130f5caa6b74638` ("Parse command line options with commander without touching process.argv"), which demonstrated passing `{ from: 'user' }` to prevent Commander from discarding the first two argv elements.

## What a Model Would Have Written
- A competent model writes subcommand action handlers as `action((options) => ...)` expecting options first and assuming global root options (`--verbose`) are included in `options` or `cmd.opts()`, looks for `opts().noCache` on `--no-cache` flags, passes raw `parseInt` directly as option coerce callbacks (`.option('-p, --port <n>', 'port', parseInt, 8080)`), and expects repeated standard flags to collect into arrays.

## How the Wrong Version Fails
- It fails silently with green builds: `action((opts) => opts.dryRun)` receives the positional string `"production"` causing `opts.dryRun` to evaluate to `undefined`, `cmd.opts().verbose` silently evaluates to `undefined` inside subcommands, `opts().noCache` evaluates to `undefined` while `opts().cache` is `true`, and passing `parseInt` directly treats the default port `8080` as the radix parameter causing `parseInt("3000", 8080)` to silently evaluate to `NaN`.
csx.json
{"case":{"caseId":"case:sha256:0f1b82968ec34c118878ee4c5f92670436caba24f231f40d593aac31b0db2db5","contract":["assert subcommand action handler unpacks positional arguments before options and command instance","assert subcommand action options and cmd.opts() isolate local options and omit parent global options","assert cmd.optsWithGlobals() merges subcommand options with parent and global options","assert standalone --no- flag strips no- prefix from opts() key and defaults to true when omitted","assert passing raw parseInt with non-zero default into option coerce function corrupts parsed value to NaN","assert safe coerce function throwing InvalidArgumentError routes to CommanderError with code commander.invalidArgument","assert standard option repeats overwrite earlier values while variadic options collect into an Array"],"goal":"Handle subcommand action argument unpacking, global option propagation via optsWithGlobals, negated boolean flag naming, and custom coerce function radix traps in commander","kind":"HOW","packages":["pkg:npm/commander@15.0.0"],"schemaVersion":1,"symbols":["Command","program.action","program.optsWithGlobals","program.option","InvalidArgumentError"]},"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","program.action","program.optsWithGlobals","program.option","InvalidArgumentError"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "csx-sample-commander-api-traps",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "csx-sample-commander-api-traps",
      "version": "1.0.0",
      "license": "MIT-0",
      "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": "csx-sample-commander-api-traps",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "license": "MIT-0",
  "dependencies": {
    "commander": "15.0.0"
  }
}
src/index.mjs
import { Command, InvalidArgumentError } from 'commander';

/**
 * Parses integer option values safely.
 * Commander passes (value, previous) to custom coerce functions.
 * Passing raw `parseInt` directly into `.option('-p, --port <n>', 'port', parseInt, 8080)`
 * causes Commander to call `parseInt(val, 8080)` where 8080 is treated as the radix parameter,
 * silently returning NaN.
 */
export function parseIntegerOption(value, _previous) {
  const parsedValue = parseInt(value, 10);
  if (Number.isNaN(parsedValue)) {
    throw new InvalidArgumentError('Not a valid integer.');
  }
  return parsedValue;
}

/**
 * Creates a CLI with global options and a subcommand to demonstrate
 * action handler argument unpacking and parent option scoping.
 */
export function createDeploymentCli(actionHandler) {
  const program = new Command();
  program
    .name('deploy-cli')
    .exitOverride()
    .option('-v, --verbose', 'enable verbose output');

  program
    .command('deploy <environment> [cluster]')
    .description('Deploy services to a target environment')
    .option('-d, --dry-run', 'simulate deployment without applying changes')
    .action(actionHandler);

  return program;
}

/**
 * Creates a CLI demonstrating negated boolean flags and variadic options.
 */
export function createConfigCli() {
  const program = new Command();
  program
    .name('config-cli')
    .exitOverride()
    .option('--no-cache', 'disable build caching')
    .option('-t, --tag <tag>', 'single tag (overwrites on repeat)')
    .option('-H, --header <headers...>', 'HTTP headers (accumulates into array)');

  return program;
}

/**
 * Creates a CLI demonstrating the naive `parseInt` radix trap.
 */
export function createNaivePortCli(defaultPort = 8080) {
  const program = new Command();
  program
    .name('naive-port-cli')
    .exitOverride()
    .option('-p, --port <number>', 'server port', parseInt, defaultPort);

  return program;
}

/**
 * Creates a CLI demonstrating the safe integer parsing pattern.
 */
export function createSafePortCli(defaultPort = 8080) {
  const program = new Command();
  program
    .name('safe-port-cli')
    .exitOverride()
    .option('-p, --port <number>', 'server port', parseIntegerOption, defaultPort);

  return program;
}
test/contract.mjs
import { strict as assert } from 'node:assert';
import { Command, InvalidArgumentError, CommanderError } from 'commander';
import {
  createDeploymentCli,
  createConfigCli,
  createNaivePortCli,
  createSafePortCli,
} from '../src/index.mjs';

// --- Trap 1: Subcommand Action Argument Unpacking & Global Option Scope ---
// Naive models assume subcommand action callbacks receive (options, command) or (args, options),
// and expect global options (--verbose) to be present on the local options object or cmd.opts().
// In reality:
// 1. Positional arguments are unpacked individually first: (arg1, arg2, ..., options, command).
// 2. The options parameter and cmd.opts() contain ONLY subcommand-scoped options; global/parent
//    options are undefined unless accessed via cmd.optsWithGlobals().
let actionReceivedArgs = null;
const deployCli = createDeploymentCli((...args) => {
  actionReceivedArgs = args;
});

deployCli.parse(['-v', 'deploy', 'production', 'cluster-1', '--dry-run'], { from: 'user' });

assert.ok(actionReceivedArgs !== null, 'subcommand action handler must be called');
assert.equal(actionReceivedArgs.length, 4, 'action handler receives (environment, cluster, options, command)');

const [envArg, clusterArg, localOptions, commandInstance] = actionReceivedArgs;
assert.equal(envArg, 'production', 'first argument is the positional <environment> string');
assert.equal(clusterArg, 'cluster-1', 'second argument is the positional [cluster] string');
assert.equal(typeof localOptions, 'object', 'third argument is the subcommand options object');
assert.equal(localOptions.dryRun, true, 'subcommand option --dry-run is present in local options');

// Local options and cmd.opts() do NOT inherit root command options
assert.equal(localOptions.verbose, undefined, 'global --verbose is NOT present in local action options object');
assert.equal(commandInstance.opts().verbose, undefined, 'global --verbose is NOT present in command.opts()');

// cmd.optsWithGlobals() merges subcommand options with parent/root options
assert.equal(commandInstance instanceof Command, true, 'fourth argument is the Command instance');
const mergedOptions = commandInstance.optsWithGlobals();
assert.equal(mergedOptions.dryRun, true, 'optsWithGlobals() includes local subcommand options');
assert.equal(mergedOptions.verbose, true, 'optsWithGlobals() includes parent/root global options');

// --- Trap 2: Negated Boolean Flags (--no-<name>) ---
// Naive models expect .option('--no-cache') to set opts().noCache = true when passed,
// and opts().noCache = false or undefined when omitted.
// In reality:
// 1. Commander strips the "no-" prefix: the key in opts() is "cache" (positive identifier).
// 2. Defining a standalone "--no-" flag flips the default to boolean `true`.
// 3. Passing "--no-cache" sets opts().cache = false.
const configCliDefault = createConfigCli();
configCliDefault.parse([], { from: 'user' });
const defaultOpts = configCliDefault.opts();
assert.equal(defaultOpts.noCache, undefined, 'key in opts() is "cache", not "noCache"');
assert.equal(defaultOpts.cache, true, 'standalone --no- flag defaults to true when omitted');

const configCliExplicit = createConfigCli();
configCliExplicit.parse(['--no-cache'], { from: 'user' });
const explicitOpts = configCliExplicit.opts();
assert.equal(explicitOpts.cache, false, 'passing --no-cache sets opts().cache to false');

// --- Trap 3: Custom Option Coerce Signature & parseInt Radix Corruption ---
// Commander invokes custom coerce functions with signature (value, previous).
// Passing raw `parseInt` into `.option('-p, --port <n>', 'desc', parseInt, 8080)`
// causes Commander to invoke `parseInt("3000", 8080)`.
// Because 8080 is not a valid radix (must be 2-36), JavaScript returns NaN.
const naivePortCli = createNaivePortCli(8080);
naivePortCli.parse(['-p', '3000'], { from: 'user' });
assert.equal(
  Number.isNaN(naivePortCli.opts().port),
  true,
  'passing raw parseInt with default 8080 passes 8080 as radix, corrupting parsed port to NaN'
);

// Safe custom coerce function wraps parseInt(val, 10) and handles InvalidArgumentError
const safePortCli = createSafePortCli(8080);
safePortCli.parse(['-p', '3000'], { from: 'user' });
assert.equal(safePortCli.opts().port, 3000, 'safe integer parser correctly parses port 3000');

const safePortInvalidCli = createSafePortCli(8080);
assert.throws(
  () => safePortInvalidCli.parse(['-p', 'not-a-number'], { from: 'user' }),
  (err) =>
    err instanceof CommanderError &&
    err.code === 'commander.invalidArgument' &&
    err.message.includes('Not a valid integer'),
  'throwing InvalidArgumentError in custom coerce function routes to CommanderError with code commander.invalidArgument'
);

// --- Trap 4: Variadic Options vs Standard Option Overwrites ---
// Naive models assume repeated flags (-t dev -t prod) collect into an array.
// In reality:
// 1. Standard options overwrite earlier occurrences (last value wins).
// 2. Variadic options (<headers...>) collect all space-separated or repeated values into an array.
const tagCli = createConfigCli();
tagCli.parse(['-t', 'dev', '-t', 'staging'], { from: 'user' });
assert.equal(tagCli.opts().tag, 'staging', 'standard option repeats overwrite previous value');

const variadicCli = createConfigCli();
variadicCli.parse(
  ['-H', 'Accept: application/json', 'Authorization: Bearer token', '-H', 'X-Custom-Header: 1'],
  { from: 'user' }
);
assert.deepEqual(
  variadicCli.opts().header,
  ['Accept: application/json', 'Authorization: Bearer token', 'X-Custom-Header: 1'],
  'variadic option collects multiple arguments into an array'
);

console.log('CONTRACT PASS: commander action unpacking, optsWithGlobals, negated flags, coerce radix and variadic traps verified');

オリジンシーダー

csx-seed