Skip to content

Let the resume subcommand receive the options it declares - #328

Open
beeman wants to merge 1 commit into
mainfrom
beeman/resume-option-parsing
Open

Let the resume subcommand receive the options it declares#328
beeman wants to merge 1 commit into
mainfrom
beeman/resume-option-parsing

Conversation

@beeman

@beeman beeman commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

The bug

dapp-store resume cannot run. Every invocation of its own documented usage fails on a flag that is right there in the command line:

$ dapp-store resume --release-id <id> --keypair ~/publisher.json
`--keypair` is required.

resume redeclares seven options the root command also owns — --keypair, --verbose, --portal-url, --rpc-url, --api-key-stdin, --local-dev, --skip-self-update. Commander gives the root command first refusal on the whole argv unless positional option parsing is enabled, so the root consumed all seven and the subcommand was left with only the flags the root does not declare. Parsing resume --keypair /tmp/x.json --session-id abc --verbose --portal-url https://x.test shows exactly where they went:

resume.opts(): {"apiKeyEnv":"DAPP_STORE_API_KEY","sessionId":"abc"}
main.opts()  : {"apiKeyEnv":"…","keypair":"/tmp/x.json","verbose":true,"portalUrl":"https://x.test"}

Only --session-id and --release-id — the two options the root does not declare — reached the subcommand. runResumeAction reads options.keypair off that stripped set, so validateResumeArgs threw before any work started, under every argument order. That made the recovery path for a partially completed publication unreachable, which matters precisely when a publish has already half-run and starting a fresh one is the wrong move.

The fix

enablePositionalOptions() assigns each option to the command it was typed after, which fixes the documented form.

Options typed before the subcommand still legitimately land on the root — someone who read these flags off the root help writes dapp-store --keypair <path> resume --release-id <id> — so withRootOptionFallbacks reads them back from there. Reporting a flag as missing when it is plainly present is the worse failure, and both orders now work. --api-key-env needs care since it defaults on both commands: an untouched subcommand value is indistinguishable from an unset one, so the root is consulted only while the subcommand still holds the default.

Verification

Against the staging portal, with the flags in either position, resume now runs the real workflow instead of dying on argument validation:

$ dapp-store resume --release-id 4277c264-… --keypair <path> --verbose
Resuming publication: Connecting to publishing portal
Verbose: Release ID: 4277c264-0c06-443c-bdc7-5c433220acb2
3/7 Load release context: Loading existing publication session
  Publication session not found

That last line is the portal's answer for a release whose session is gone, which is the correct response for the id I tested with — the point is that it gets there at all.

Three tests, all parse-level since that is where the bug was:

  • resume receives the options it shares with the root command — fails without enablePositionalOptions()
  • resume falls back to options typed before the subcommand — fails without withRootOptionFallbacks
  • a keypair that was never supplied is still reported as missing, so the fallback cannot paper over a genuine omission

The third one caught something worth knowing: Commander retains option values on both the root and the subcommand across parses, so a test that parses repeatedly leaks flags into the next case. Harmless in production, where main() parses argv exactly once per process, but the suite now clears both commands in beforeEach.

pnpm run build and packages/cli tests pass: 60 tests, 9 suites.

Independent of #327 and branched from main; the two can land in either order.

`resume` redeclares options the root command also owns — `--keypair`, `--verbose`, `--portal-url`, `--rpc-url`, `--api-key-stdin`, `--local-dev`, `--skip-self-update`. Commander gives the root command first refusal on the whole argv unless positional option parsing is enabled, so it consumed all of them and the subcommand was left with only the flags the root does not declare. `runResumeAction` then read `options.keypair` off that stripped set and threw, which made every invocation of the documented usage fail with "`--keypair` is required" while the keypair sat in the command line:

    $ dapp-store resume --release-id <id> --keypair ~/publisher.json
    `--keypair` is required.

Parsing `resume --keypair /tmp/x.json --session-id abc --verbose` showed where they went: the subcommand saw `{apiKeyEnv, sessionId}` while the root held `{keypair, verbose}`. `resume` could not complete under any argument order, so the recovery path for a partially completed publication was unreachable.

`enablePositionalOptions()` assigns each option to the command it was typed after, which fixes the documented form. Options typed before the subcommand still land on the root, so `withRootOptionFallbacks` reads them back from there rather than reporting a flag as missing when it is plainly present — both orders now work.

Verified against the staging portal: `dapp-store resume --release-id <id> --keypair <path>` reaches "Loading existing publication session" and reports what the portal says about that session, with the flags in either position.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant