Skip to content

feat: support switching to a known user#8046

Merged
sean-roberts merged 7 commits intomainfrom
sr/switcher
Mar 19, 2026
Merged

feat: support switching to a known user#8046
sean-roberts merged 7 commits intomainfrom
sr/switcher

Conversation

@sean-roberts
Copy link
Contributor

🎉 Thanks for submitting a pull request! 🎉

Summary

When using netlify switch we should be able to pass an email to auto select if it's in the list. This allows users to quickly go into the known accounts that they have instead of the default which is an interactive selection experience.

@sean-roberts sean-roberts self-assigned this Mar 17, 2026
@sean-roberts sean-roberts requested a review from a team as a code owner March 17, 2026 01:36
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4475d007-1636-49a1-b33a-c95b2605884e

📥 Commits

Reviewing files that changed from the base of the PR and between ecad52d and 09c6925.

📒 Files selected for processing (1)
  • src/commands/switch/switch.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/switch/switch.ts

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a --email option to the switch command to auto-select an account by email, falling back to the interactive prompt if no exact match is found.
  • Tests

    • Added unit tests for exact and partial email matches, prompt fallback, and triggering login when choosing a new account.
  • Documentation

    • Documented the new email flag in the switch command docs.

Walkthrough

Adds a new --email <email> option to the switch command. The command now accepts options (removed the unused underscore) and, when --email is provided, attempts to match the given email (exact or partial) against stored accounts; on a match it sets userId in global config and logs confirmation, otherwise it logs that no account matched and falls back to the interactive account-selection prompt. Unit tests for exact, partial, no-match, interactive selection, and login-new flows were added. Documentation updated to document the new flag.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: support switching to a known user' accurately reflects the main change: adding email-based account switching functionality.
Description check ✅ Passed The PR description clearly explains the feature: allowing users to pass an email to auto-select accounts instead of using interactive selection.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sr/switcher
📝 Coding Plan
  • Generate coding plan for human review comments

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

@github-actions
Copy link

github-actions bot commented Mar 17, 2026

📊 Benchmark results

Comparing with 7a1c8fa

  • Dependency count: 1,063 ⬆️ 0.19% increase vs. 7a1c8fa
  • Package size: 354 MB ⬆️ 0.01% increase vs. 7a1c8fa
  • Number of ts-expect-error directives: 357 ⬇️ 0.84% decrease vs. 7a1c8fa

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/unit/commands/switch/switch.test.ts (1)

60-68: Strengthen fallback-path assertion.

This test checks log/prompt behavior, but it should also assert that the selected account was persisted (userId set to user-2) to fully validate the fallback flow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/commands/switch/switch.test.ts` around lines 60 - 68, The test
should also assert the fallback flow persisted the chosen account: after calling
switchCommand (using createCommand, mockPrompt and logMessages), add an
expectation that the persisted userId equals 'user-2' (use the same
test-accessible persistence accessor used elsewhere in tests — e.g., the
returned command/config/store from createCommand or the mock persistence helper)
to confirm the selected account was saved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/switch/switch.ts`:
- Around line 18-27: The current options.email handling auto-switches on the
first substring match and can choose the wrong account when multiple entries
match; modify the logic around matchedAccount (found from
Object.entries(availableUsersChoices)) to detect if more than one entry matches
options.email and, in that case, do not auto-switch: instead present an
ambiguity error or prompt the user to pick (or list the matching labels),
otherwise continue to set the userId via command.netlify.globalConfig.set using
the single unambiguous match; ensure you reference options.email,
availableUsersChoices, matchedAccount, and command.netlify.globalConfig.set when
updating this behavior.

In `@tests/unit/commands/switch/switch.test.ts`:
- Line 53: The tests call switchCommand with a mocked command object typed as
any, violating the no-unsafe-argument rule; update the mock to satisfy the
switchCommand signature by replacing `command as any` with a properly typed mock
of BaseCommand (either construct a mock object that implements BaseCommand or
cast a typed mock variable to BaseCommand), ensuring methods/properties used by
switchCommand (e.g., any invoked methods or properties on the mock) are present;
apply this change to all switchCommand calls in the test so linting passes
without using `any`.

---

Nitpick comments:
In `@tests/unit/commands/switch/switch.test.ts`:
- Around line 60-68: The test should also assert the fallback flow persisted the
chosen account: after calling switchCommand (using createCommand, mockPrompt and
logMessages), add an expectation that the persisted userId equals 'user-2' (use
the same test-accessible persistence accessor used elsewhere in tests — e.g.,
the returned command/config/store from createCommand or the mock persistence
helper) to confirm the selected account was saved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a9c4e2a1-9540-4d10-9740-263946083b66

📥 Commits

Reviewing files that changed from the base of the PR and between 37b2cff and 84d36e3.

📒 Files selected for processing (3)
  • src/commands/switch/index.ts
  • src/commands/switch/switch.ts
  • tests/unit/commands/switch/switch.test.ts

@sean-roberts sean-roberts requested a review from a team as a code owner March 17, 2026 02:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/unit/commands/switch/switch.test.ts (1)

60-68: Assert the fallback path actually updates userId.

This test confirms prompt invocation, but it doesn’t validate the final state change after selection. Add an assertion for globalConfig.set('userId', ...) so the fallback flow is fully covered.

Suggested test tightening
-  test('--email falls through to prompt when no match is found', async () => {
-    const { command } = createCommand()
+  test('--email falls through to prompt when no match is found', async () => {
+    const { command, mockSet } = createCommand()
     mockPrompt.mockResolvedValueOnce({ accountSwitchChoice: 'Bob (bob@corp.com)' })

     await switchCommand({ email: 'nobody@example.com' }, command)

     expect(logMessages.some((m) => m.includes('No account found matching'))).toBe(true)
     expect(mockPrompt).toHaveBeenCalled()
+    expect(mockSet).toHaveBeenCalledWith('userId', 'user-2')
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/commands/switch/switch.test.ts` around lines 60 - 68, The test for
the fallback prompt path should also assert that the selected account actually
updates globalConfig; after calling switchCommand({ email: 'nobody@example.com'
}, command) and mocking the prompt (mockPrompt.mockResolvedValueOnce({
accountSwitchChoice: 'Bob (bob@corp.com)' })), add an expectation that
globalConfig.set was called to update 'userId' to the selected account's id
(e.g., expect(globalConfig.set).toHaveBeenCalledWith('userId', expectedUserId));
locate this in the same test that references switchCommand, mockPrompt and
logMessages and derive expectedUserId from the test fixtures or the mapping used
by the code under test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/commands/switch.md`:
- Line 21: The docs claim `email` but don't state matching semantics while
src/commands/switch/switch.ts currently does substring matching with .includes
and picks the first hit; update behavior to perform exact matching instead: in
switch.ts replace the .includes check on the account email with a strict
equality comparison (===) and prefer the exact match when selecting the target
account (e.g., find(account => account.email === email)), optionally falling
back to the previous substring-first behavior only if no exact match is found;
alternatively, if you prefer to keep substring semantics, update
docs/commands/switch.md to explicitly state that `email` is matched as a
substring and the first match is selected, referencing the `email` parameter and
the matching logic in switch.ts.

In `@tests/unit/commands/switch/switch.test.ts`:
- Around line 70-77: The test " --email matches partial email strings" currently
passes a partial email ('bob@corp') to switchCommand which encourages substring
auto-selection; change the test to use a full exact email string (e.g., the
exact email used in fixtures) or change expectations to require a prompt on
ambiguity: invoke switchCommand with the exact email to assert
mockSet('userId','user-2') and mockPrompt not called, or keep the partial email
but assert that mockPrompt is called (and mockSet not called) to enforce
prompt-on-ambiguous-match behavior; update the test name accordingly and
reference switchCommand, mockSet and mockPrompt in your change.

---

Nitpick comments:
In `@tests/unit/commands/switch/switch.test.ts`:
- Around line 60-68: The test for the fallback prompt path should also assert
that the selected account actually updates globalConfig; after calling
switchCommand({ email: 'nobody@example.com' }, command) and mocking the prompt
(mockPrompt.mockResolvedValueOnce({ accountSwitchChoice: 'Bob (bob@corp.com)'
})), add an expectation that globalConfig.set was called to update 'userId' to
the selected account's id (e.g.,
expect(globalConfig.set).toHaveBeenCalledWith('userId', expectedUserId)); locate
this in the same test that references switchCommand, mockPrompt and logMessages
and derive expectedUserId from the test fixtures or the mapping used by the code
under test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6b3f72a3-3bae-46a6-a2c9-677f782e1dcc

📥 Commits

Reviewing files that changed from the base of the PR and between 84d36e3 and 70ef776.

📒 Files selected for processing (2)
  • docs/commands/switch.md
  • tests/unit/commands/switch/switch.test.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/commands/switch/switch.ts (1)

38-39: ⚠️ Potential issue | 🔴 Critical

Remove the unused @ts-expect-error directive to fix the pipeline failure.

The TypeScript compiler reports this directive is unnecessary. The choices array type is now correctly inferred.

Proposed fix
     {
       type: 'list',
       name: 'accountSwitchChoice',
       message: 'Please select the account you want to use:',
-      // `@ts-expect-error` TS(2769) FIXME: No overload matches this call.
       choices: [...Object.entries(availableUsersChoices).map(([, val]) => val), LOGIN_NEW],
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/switch/switch.ts` around lines 38 - 39, Remove the unnecessary
TypeScript suppression placed above the choices assignment: delete the line
containing "// `@ts-expect-error` TS(2769) FIXME: No overload matches this call."
and keep the existing choices expression as-is (choices:
[...Object.entries(availableUsersChoices).map(([, val]) => val), LOGIN_NEW]).
This removes the unused directive and lets the compiler use the now-correctly
inferred type for choices while preserving use of availableUsersChoices and
LOGIN_NEW.
🧹 Nitpick comments (1)
src/commands/switch/switch.ts (1)

46-54: Consider using a type guard to avoid the @ts-expect-error directives.

The selectedAccount lookup could theoretically return undefined. While unlikely in practice (since the choice comes from the same list), using a guard would satisfy TypeScript and remove the suppression comments.

Proposed refactor
   } else {
-    // `@ts-expect-error` TS(2769) FIXME: No overload matches this call.
     const selectedAccount = Object.entries(availableUsersChoices).find(
       ([, availableUsersChoice]) => availableUsersChoice === accountSwitchChoice,
     )
-    // `@ts-expect-error` TS(2532) FIXME: Object is possibly 'undefined'.
-    command.netlify.globalConfig.set('userId', selectedAccount[0])
-    log('')
-    // `@ts-expect-error` TS(2532) FIXME: Object is possibly 'undefined'.
-    log(`You're now using ${chalk.bold(selectedAccount[1])}.`)
+    if (selectedAccount) {
+      command.netlify.globalConfig.set('userId', selectedAccount[0])
+      log('')
+      log(`You're now using ${chalk.bold(selectedAccount[1])}.`)
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/switch/switch.ts` around lines 46 - 54, selectedAccount (result
of Object.entries(availableUsersChoices).find(...)) can be undefined, so remove
the `@ts-expect-error` markers and add a type guard: check if selectedAccount is
undefined before using it; if undefined, log an error and return (or throw) to
avoid calling command.netlify.globalConfig.set or accessing
selectedAccount[0]/[1]; otherwise proceed to set('userId', selectedAccount[0])
and log the success message with chalk.bold(selectedAccount[1]). Ensure the
guard is placed immediately after computing selectedAccount so TypeScript can
narrow its type for subsequent uses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/commands/switch/switch.ts`:
- Around line 38-39: Remove the unnecessary TypeScript suppression placed above
the choices assignment: delete the line containing "// `@ts-expect-error` TS(2769)
FIXME: No overload matches this call." and keep the existing choices expression
as-is (choices: [...Object.entries(availableUsersChoices).map(([, val]) => val),
LOGIN_NEW]). This removes the unused directive and lets the compiler use the
now-correctly inferred type for choices while preserving use of
availableUsersChoices and LOGIN_NEW.

---

Nitpick comments:
In `@src/commands/switch/switch.ts`:
- Around line 46-54: selectedAccount (result of
Object.entries(availableUsersChoices).find(...)) can be undefined, so remove the
`@ts-expect-error` markers and add a type guard: check if selectedAccount is
undefined before using it; if undefined, log an error and return (or throw) to
avoid calling command.netlify.globalConfig.set or accessing
selectedAccount[0]/[1]; otherwise proceed to set('userId', selectedAccount[0])
and log the success message with chalk.bold(selectedAccount[1]). Ensure the
guard is placed immediately after computing selectedAccount so TypeScript can
narrow its type for subsequent uses.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7c7cfaec-a4db-431b-9fbb-aafae9070c08

📥 Commits

Reviewing files that changed from the base of the PR and between 70ef776 and ecad52d.

📒 Files selected for processing (2)
  • src/commands/switch/switch.ts
  • tests/unit/commands/switch/switch.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/commands/switch/switch.test.ts

@sean-roberts sean-roberts enabled auto-merge (squash) March 19, 2026 13:56
@sean-roberts sean-roberts merged commit e460e68 into main Mar 19, 2026
71 checks passed
@sean-roberts sean-roberts deleted the sr/switcher branch March 19, 2026 14:20
serhalp pushed a commit that referenced this pull request Mar 20, 2026
🤖 I have created a release *beep* *boop*
---


## [24.4.0](v24.3.0...v24.4.0)
(2026-03-20)


### Features

* propagate @netlify/build version, primary framework and its version
([#8049](#8049))
([1db6f6e](1db6f6e))
* support switching to a known user
([#8046](#8046))
([e460e68](e460e68))


### Bug Fixes

* **deps:** bump h3 from 1.15.5 to 1.15.8
([#8055](#8055))
([7a1c8fa](7a1c8fa))
* **deps:** update dependency @netlify/dev to v4.16.3
([#8053](#8053))
([4460d87](4460d87))
* **deps:** update dependency @netlify/dev to v4.16.4
([#8060](#8060))
([d0491da](d0491da))
* **deps:** update dependency @netlify/dev-utils to v4.4.2
([#8054](#8054))
([bdb944f](bdb944f))
* **deps:** update dependency @netlify/dev-utils to v4.4.3
([#8061](#8061))
([78b5af9](78b5af9))
* **deps:** update dependency @netlify/edge-functions to v3.0.5
([#8056](#8056))
([6254a75](6254a75))
* **deps:** update dependency @netlify/edge-functions to v3.0.6
([#8063](#8063))
([7646545](7646545))
* **deps:** update dependency @netlify/functions to v5.1.4
([#8057](#8057))
([18d5ccb](18d5ccb))
* **deps:** update dependency @netlify/functions to v5.1.5
([#8064](#8064))
([77a9249](77a9249))
* **deps:** update dependency @netlify/images to v1.3.6
([#8058](#8058))
([06f564b](06f564b))
* **deps:** update dependency @netlify/images to v1.3.7
([#8065](#8065))
([12a3a3f](12a3a3f))
* **deps:** update dependency cookie to v1.1.1
([#8037](#8037))
([6e6bcf5](6e6bcf5))
* **deps:** update dependency envinfo to v7.21.0
([#8039](#8039))
([08b5fc5](08b5fc5))
* **deps:** update netlify packages
([#8047](#8047))
([d57ce32](d57ce32))
* **deps:** update netlify packages
([#8062](#8062))
([3006f8c](3006f8c))
* **deps:** update netlify packages
([#8067](#8067))
([02632aa](02632aa))
* **deps:** upgrade deps to fix new vulnerabilities
([#8070](#8070))
([e3655f9](e3655f9))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: token-generator-app[bot] <82042599+token-generator-app[bot]@users.noreply.github.com>
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.

2 participants