Skip to content

Review follow-ups: retries, option validation, OAuth hardening, CI gates - #5

Closed
cprice70 wants to merge 7 commits into
users/cprice/multi-status-errorsfrom
users/cprice/review-followups
Closed

cprice70 wants to merge 7 commits into
users/cprice/multi-status-errorsfrom
users/cprice/review-followups

Conversation

@cprice70

Copy link
Copy Markdown
Owner

Stacked on #4. Remaining items from the review pass, each its own commit with tests written first.

Client and config

  • Retry connection failures — fetch throwing (ECONNRESET, DNS, TLS) is now retried with the same backoff as 429/5xx. Final error names the attempt count and keeps the cause.
  • Single source of config — the client no longer reads process.env; loadConfig() applies env → file → defaults (region NA, sandbox false). A config file that is not valid JSON prints a warning instead of being silently ignored.
  • Empty response bodies return { success: true } rather than campaignId: "delete".

Commands

  • campaigns performance reuses createReport + the campaign preset + waitForReport, so it waits up to --timeout (default 600s) instead of giving up after 5s, and uses the same 7-day attribution as reports create.
  • Option validation — --type, --state, --targeting are restricted to known values (case-insensitive, listed in --help and in the error). campaigns create only accepts sponsoredProducts, since only that request body is implemented.
  • runAction wrapper replaces eleven copies of the same try/catch. Errors set process.exitCode so stdout flushes before exit.

OAuth login

  • Random state on the consent URL, verified on callback (CSRF).
  • Callback HTML escapes interpolated values.
  • EADDRINUSE on port 3000 gets an actionable message.

Tooling

  • eslint + typescript-eslint, prettier config and --check, tsconfig.test.json so tests are typechecked. npm run check runs them all; CI and release call it.
  • Release workflow refuses a tag that does not match package.json; npm pinned to the 11 line.
  • engines.node >= 20.

Test plan

  • npm run check — lint, format, typecheck, 74 tests pass
  • npm run build
  • amazonads auth login end to end (state round-trip) against a real account

- Retry when fetch itself throws (ECONNRESET, DNS, TLS), not only on
  429/5xx. The final error names the attempt count and the cause.
- Empty response bodies return { success: true }; the old fallback
  reported campaignId "delete" for the delete endpoint.
- The client no longer reads process.env. loadConfig() is the single
  source of precedence (env, then file, then defaults) and now fills
  in region NA and sandbox false.
- A config file that is not valid JSON prints a warning instead of
  being silently treated as missing.
- Type the Login with Amazon token response.
getCampaignPerformance had its own copy of the create/poll/download
flow with a 5-second cap, so it almost always returned PENDING and
asked the user to poll by hand. It also used 14-day attribution while
the reports command uses 7-day, so ACoS differed between the two.

Reuse createReport, the campaign preset, and waitForReport. The command
waits up to --timeout seconds (default 600) and reads cost and sales7d.
--type, --state, and --targeting were free text. A typo fell through to
the Sponsored Products code path silently, and campaigns create accepted
sponsoredBrands/sponsoredDisplay even though only the SP request body is
implemented, so those calls failed at the API with an opaque error.

Add choiceOption(), a case-insensitive commander Option that lists the
allowed values in --help and in the error, and restrict create to
sponsoredProducts until the SB/SD payloads exist.
- Send a random state value on the consent URL and refuse callbacks
  whose state does not match, as Amazon's docs recommend, so a forged
  redirect cannot hand the CLI someone else's authorization code.
- Escape everything interpolated into the callback HTML page; the
  provider's error string was previously reflected verbatim.
- Explain EADDRINUSE on the callback port instead of printing the raw
  listen error, since port 3000 is a common dev-server port.
- Move the config save out of the server close callback so the
  promise resolves with the refresh token and the flow reads top-down.
Every action repeated the same try/catch that printed the error and
called process.exit(1). Replace it with runAction(), which prints once
and sets process.exitCode so stdout is flushed before exit, and fail()
for validation messages that are already user-facing.

Also add a prettier config and format src/ with it, and build the CSV
header set with a Set instead of repeated includes() scans.
- eslint with typescript-eslint (flat config) and a lint script. Fixes
  the findings it raised: unused test spies, a dropped error cause, and
  unused imports.
- prettier --check over the whole repo; README table reformatted.
- tsconfig.test.json so tsc covers the test files, which the build
  config excludes and vitest does not typecheck. Fixes two spy typings.
- npm run check runs all of the above plus tests; CI and the release
  workflow call it.
- Release workflow refuses a tag that does not match package.json and
  pins npm to the 11 line instead of latest.
- engines.node >=20 to match the CI matrix; README said 18+.
Stacked PRs target a feature branch rather than main and were getting no
checks.
@cprice70
cprice70 deleted the branch users/cprice/multi-status-errors October 13, 2026 23:02
@cprice70 cprice70 closed this Oct 13, 2026
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