Skip to content

refactor(credentials): lean credentials command surface - #334

Merged
YosefHayim merged 2 commits into
mainfrom
refactor/credentials/creds-command
Aug 7, 2026
Merged

refactor(credentials): lean credentials command surface#334
YosefHayim merged 2 commits into
mainfrom
refactor/credentials/creds-command

Conversation

@YosefHayim

@YosefHayim YosefHayim commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Fixes #319

Summary

Lean remediation of the credentials command surface (src/core/credentials/command.ts + thin src/cli/commands/creds.ts).

  • Thin CLI: remove unused chooseAccountInteractive / setupIos Promise facades (wizard already imports core) and drop redundant AppStoreIdentityLive / AppleCredentialsClientLive provides already applied by runCliProgram — aligns with open refactor(cli): drop unused CLI compatibility facades #305 facade-delete intent without owning that PR’s other files.
  • Failure channel: preserve existing CredentialsCommandFailure tags through mapError so operation-specific messages (rename/remove/use/push-key) are not re-wrapped as run credentials command.
  • DRY within the god-file: requireMatchedAccount and requireUniqueAccountLabel replace five copy-pasted match/conflict blocks (no file split — no second callers outside this module, path globs forbid other files).
  • Dead surface: drop unused ApnsKeyRecord re-export from the command module.
  • Tests: pure validation coverage for rename/remove/use/push-key failure operations with testkit layers (no live ASC/Play).

Scope

  • src/core/credentials/command.ts
  • src/core/credentials/command.test.ts
  • src/cli/commands/creds.ts
  • src/cli/commands/creds.test.ts
  • Generated docs test-count bump only (README*.md, docs/commands.md, llms.txt) required for docs:check

Risk

Low. Behavior preserved; no ASC/Play transport changes. CLI exports shrink — only registerCredsCommand remains (nothing imported the removed facades).

Test plan

  • pnpm typecheck
  • pnpm lint
  • pnpm lint:style
  • pnpm docs:check
  • pnpm test (2092 passed)
  • pnpm build
  • Colocated validation tests for failure operations

Main safety

  • Branch from origin/main @ a57cdb1
  • No main commits
  • Do not merge from this lane

Summary by cubic

Leaned the launch creds CLI and refactored core credential handling to cut duplication and keep operation-specific error messages intact. Behavior is unchanged; the CLI surface is smaller and failures are clearer.

  • Refactors

    • Removed chooseAccountInteractive and setupIos from src/cli/commands/creds.ts; registerCredsCommand is the only export.
    • Dropped redundant AppStoreIdentityLive and AppleCredentialsClientLive provides; runCliProgram already wires adapters.
    • Added requireMatchedAccount and requireUniqueAccountLabel helpers to de-duplicate match/conflict checks.
    • Removed unused ApnsKeyRecord re-export.
    • Added tests for pure validation (rename/remove/use/push-key), logout alias acceptance, and the thin-CLI export.
    • Synced docs badges to 2326 tests across READMEs and docs/commands.md.
  • Bug Fixes

    • Preserved CredentialsCommandFailure tags by passing through existing failures in mapError, so specific operations aren’t re-wrapped as “run credentials command”.

Written for commit 086ea4a. Summary will update on new commits.

Review in cubic

@YosefHayim YosefHayim added the domain:credentials Accounts, keychain, signing assets label Aug 6, 2026
@codeant-ai

codeant-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 9a4a2e5 Aug 06, 2026 · 23:18 23:20

@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 086ea4a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@codeant-ai

codeant-ai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@YosefHayim, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 49 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b795997f-c5e1-4985-b6d4-461de716143c

📥 Commits

Reviewing files that changed from the base of the PR and between d86526e and 086ea4a.

📒 Files selected for processing (15)
  • README.de.md
  • README.es.md
  • README.fr.md
  • README.ja.md
  • README.ko.md
  • README.md
  • README.pt-BR.md
  • README.ru.md
  • README.zh-CN.md
  • docs/commands.md
  • llms.txt
  • src/cli/commands/creds.test.ts
  • src/cli/commands/creds.ts
  • src/core/credentials/command.test.ts
  • src/core/credentials/command.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Aug 6, 2026
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Refactor credentials command surface; preserve failure tags and add validation tests

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Slim creds CLI to only register the command and delegate to core.
• Preserve operation-specific CredentialsCommandFailure tags through the error channel.
• Add validation-focused tests and bump docs test counts accordingly.
Diagram

graph TD
  A["launch creds (Commander)"] --> B["src/cli/commands/creds.ts"] --> C["credentialsCommandProgram"] --> D[("Credentials store")]
  C --> E["Effect layers (env/prompt/logger)"]
  C --> F["Apple adapters (identity/client)"]
  G["command.test.ts"] --> C
  H["creds.test.ts"] --> B

  subgraph Legend
    direction LR
    _cli["CLI"] ~~~ _core["Core program"] ~~~ _db[("Storage")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Split core credentials command into submodules
  • ➕ Reduces the size of the "god-file" and improves navigability
  • ➕ Enables smaller, more focused unit tests per subcommand
  • ➖ May conflict with existing repo constraints/globs and current ownership boundaries
  • ➖ Introduces new public/internal module boundaries to maintain
2. Model failures as a closed union without runtime tag checks
  • ➕ Avoids isCredentialsCommandFailure runtime guard in mapError
  • ➕ Makes the error channel composition more explicit at compile time
  • ➖ Likely requires broader refactors across callers and typed error plumbing
  • ➖ Higher churn for limited user-facing benefit compared to current fix

Recommendation: The PR’s approach is the best incremental step: keep the CLI boundary thin, preserve operation-specific CredentialsCommandFailure values by not re-wrapping them, and DRY repeated account matching/label conflict logic. A deeper file split or closed-union error redesign could help long-term, but both would expand scope and risk beyond this targeted refactor.

Files changed (15) +199 / -89

Refactor (2) +65 / -78
creds.tsRemove creds CLI Promise facades and redundant adapter provides +1/-32

Remove creds CLI Promise facades and redundant adapter provides

• Deletes chooseAccountInteractive/setupIos wrapper exports and removes explicit Apple adapter providing in this module. The CLI now directly runs credentialsCommandProgram via runCliProgram when handling the command action.

src/cli/commands/creds.ts

command.tsPreserve CredentialsCommandFailure channel and DRY account validation +64/-46

Preserve CredentialsCommandFailure channel and DRY account validation

• Adds helpers to require a matched account and ensure unique labels, replacing repeated blocks across multiple subcommands. Adjusts error mapping to avoid re-wrapping existing CredentialsCommandFailure values, and removes an unused ApnsKeyRecord re-export.

src/core/credentials/command.ts

Tests (2) +123 / -0
creds.test.tsAssert creds module exports only register function +7/-0

Assert creds module exports only register function

• Adds a test that the creds CLI module exports only registerCredsCommand. This guards the intended thin CLI surface by preventing reintroduction of wizard Promise facades.

src/cli/commands/creds.test.ts

command.test.tsAdd pure validation tests for operation-tagged failures +116/-0

Add pure validation tests for operation-tagged failures

• Introduces a test harness that runs credentialsCommandProgram with test layers and stubs Apple dependencies. Adds cases ensuring rename/remove/use/push-key validation failures preserve the correct operation tag and message hints, plus a schema check for 'logout' alias handling.

src/core/credentials/command.test.ts

Documentation (11) +11 / -11
README.de.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.de.md

README.es.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.es.md

README.fr.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.fr.md

README.ja.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.ja.md

README.ko.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.ko.md

README.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.md

README.pt-BR.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.pt-BR.md

README.ru.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.ru.md

README.zh-CN.mdUpdate tests badge count +1/-1

Update tests badge count

• Bumps the displayed passing test count in the README badge from 2064 to 2071 to match the new suite size.

README.zh-CN.md

commands.mdRefresh generated docs test count +1/-1

Refresh generated docs test count

• Updates the headline test count in the command reference from 2064 to 2071 to keep docs:check consistent.

docs/commands.md

llms.txtRefresh commands overview test count +1/-1

Refresh commands overview test count

• Updates the commands summary to reflect 2071 tests instead of 2064.

llms.txt

selector: string,
operation: string,
): Effect.Effect<AccountRecord, CredentialsCommandFailure> => {
const matchedAccount = matchAccount([...accounts], selector);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: matchAccount resolves each record by checking its label immediately after its own Key ID, so a label collision can shadow a later account's Key ID. For example, if account A has label B and account B has Key ID B, selecting B resolves account A; this helper now applies that ambiguity to use, rename, remove, refresh, and explicit account selection. Resolve exact Key ID matches globally before falling back to labels, or reject ambiguous selectors. [incorrect condition logic]

Severity Level: Critical 🚨
-`creds remove` can delete the wrong signing account.
-`creds rename` can alter the wrong account label.
- ⚠️ `creds use` can activate the wrong Apple credentials.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/credentials/command.ts
**Line:** 139:139
**Comment:**
	*Incorrect Condition Logic: `matchAccount` resolves each record by checking its label immediately after its own Key ID, so a label collision can shadow a later account's Key ID. For example, if account A has label `B` and account B has Key ID `B`, selecting `B` resolves account A; this helper now applies that ambiguity to `use`, `rename`, `remove`, `refresh`, and explicit account selection. Resolve exact Key ID matches globally before falling back to labels, or reject ambiguous selectors.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines 341 to +342
const accounts = yield* listAccounts();
const conflictingAccount = accounts.find(
(account) =>
account.label.toLowerCase() === accountLabel.toLowerCase() && account.keyId !== keyId,
);
if (conflictingAccount !== undefined) {
return yield* failCommand(
'select account label',
`Label "${accountLabel}" is already used by key ${conflictingAccount.keyId}.`,
);
}
yield* requireUniqueAccountLabel(accounts, accountLabel, keyId, 'select account label');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The uniqueness check is a non-atomic read-before-write. Concurrent set-key invocations can both observe that the label is unused and proceed to addAccount, allowing duplicate labels or causing one registry write to overwrite the other account. Enforce the label invariant within the account update or serialize the check and write. [race condition]

Severity Level: Major ⚠️
- ❌ Concurrent imports can create duplicate account labels.
- ❌ Registry writes can lose a concurrently imported account.
- ⚠️ Subsequent selectors may resolve an unintended account.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/core/credentials/command.ts
**Line:** 341:342
**Comment:**
	*Race Condition: The uniqueness check is a non-atomic read-before-write. Concurrent `set-key` invocations can both observe that the label is unused and proceed to `addAccount`, allowing duplicate labels or causing one registry write to overwrite the other account. Enforce the label invariant within the account update or serialize the check and write.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 15 files

Re-trigger cubic

Why: Dead CLI Promise facades and re-wrapped command failures hid the thin Commander/core boundary for `launch creds`.
What: Drop unused chooseAccountInteractive/setupIos CLI facades and redundant Apple adapter provides; preserve CredentialsCommandFailure operations; DRY matched-account and unique-label checks; add pure validation tests; refresh docs test count.
Impact: Behavior preserved; thinner CLI; actionable failure operations reach callers; gate green.
@YosefHayim
YosefHayim force-pushed the refactor/credentials/creds-command branch from 9a4a2e5 to bde1f74 Compare August 7, 2026 10:53
@YosefHayim
YosefHayim merged commit 40998a7 into main Aug 7, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:credentials Accounts, keychain, signing assets size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(credentials): lean credentials command surface

1 participant