diff --git a/src/utils/prompts.ts b/src/utils/prompts.ts index 62a095d..9df6dca 100644 --- a/src/utils/prompts.ts +++ b/src/utils/prompts.ts @@ -1,4 +1,4 @@ -import { input, password } from '@inquirer/prompts'; +import { confirm, input, password, select } from '@inquirer/prompts'; import { safeValidateIdentifier } from './validation.js'; /** @@ -54,3 +54,39 @@ export async function promptForLogin(): Promise<{ password: passwordValue, }; } + +/** + * Prompt user to select a Git remote when multiple tangled remotes exist + * + * @param remotes - Array of available remotes with name and URL + * @returns Selected remote name + */ +export async function promptForRemoteSelection( + remotes: Array<{ name: string; url: string }> +): Promise { + const choices = remotes.map((remote) => ({ + name: `${remote.name} (${remote.url})`, + value: remote.name, + })); + + // Default to "origin" if present + const defaultValue = remotes.find((r) => r.name === 'origin')?.name; + + return await select({ + message: 'Multiple tangled.org remotes found. Which one would you like to use?', + choices, + default: defaultValue, + }); +} + +/** + * Prompt user whether to save remote selection to config + * + * @returns true if user wants to save + */ +export async function promptToSaveRemote(): Promise { + return await confirm({ + message: 'Save this remote selection for this repository? (saves to .tangledrc)', + default: false, + }); +} diff --git a/tests/utils/prompts.test.ts b/tests/utils/prompts.test.ts index 8f6ada2..d708c3c 100644 --- a/tests/utils/prompts.test.ts +++ b/tests/utils/prompts.test.ts @@ -1,20 +1,32 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { promptForIdentifier, promptForLogin, promptForPassword } from '../../src/utils/prompts.js'; +import { + promptForIdentifier, + promptForLogin, + promptForPassword, + promptForRemoteSelection, + promptToSaveRemote, +} from '../../src/utils/prompts.js'; // Mock @inquirer/prompts vi.mock('@inquirer/prompts', () => ({ + confirm: vi.fn(), input: vi.fn(), password: vi.fn(), + select: vi.fn(), })); describe('Prompts', () => { let mockInput: ReturnType; let mockPassword: ReturnType; + let mockSelect: ReturnType; + let mockConfirm: ReturnType; beforeEach(async () => { const inquirer = await import('@inquirer/prompts'); mockInput = vi.mocked(inquirer.input); mockPassword = vi.mocked(inquirer.password); + mockSelect = vi.mocked(inquirer.select); + mockConfirm = vi.mocked(inquirer.confirm); vi.clearAllMocks(); }); @@ -103,4 +115,79 @@ describe('Prompts', () => { expect(mockPassword).toHaveBeenCalledOnce(); }); }); + + describe('promptForRemoteSelection', () => { + it('should prompt user to select from multiple remotes', async () => { + const remotes = [ + { name: 'origin', url: 'git@tangled.org:did:plc:abc123/repo.git' }, + { name: 'upstream', url: 'git@tangled.org:did:plc:xyz789/repo.git' }, + ]; + + mockSelect.mockResolvedValue('upstream'); + + const result = await promptForRemoteSelection(remotes); + + expect(result).toBe('upstream'); + expect(mockSelect).toHaveBeenCalledWith({ + message: 'Multiple tangled.org remotes found. Which one would you like to use?', + choices: [ + { name: 'origin (git@tangled.org:did:plc:abc123/repo.git)', value: 'origin' }, + { name: 'upstream (git@tangled.org:did:plc:xyz789/repo.git)', value: 'upstream' }, + ], + default: 'origin', + }); + }); + + it('should default to "origin" if present', async () => { + const remotes = [ + { name: 'upstream', url: 'git@tangled.org:did:plc:abc123/repo.git' }, + { name: 'origin', url: 'git@tangled.org:did:plc:xyz789/repo.git' }, + ]; + + mockSelect.mockResolvedValue('origin'); + + await promptForRemoteSelection(remotes); + + const call = mockSelect.mock.calls[0]?.[0]; + expect(call?.default).toBe('origin'); + }); + + it('should not have default if "origin" not present', async () => { + const remotes = [ + { name: 'upstream', url: 'git@tangled.org:did:plc:abc123/repo.git' }, + { name: 'fork', url: 'git@tangled.org:did:plc:xyz789/repo.git' }, + ]; + + mockSelect.mockResolvedValue('upstream'); + + await promptForRemoteSelection(remotes); + + const call = mockSelect.mock.calls[0]?.[0]; + expect(call?.default).toBeUndefined(); + }); + }); + + describe('promptToSaveRemote', () => { + it('should prompt user to save remote selection', async () => { + mockConfirm.mockResolvedValue(true); + + const result = await promptToSaveRemote(); + + expect(result).toBe(true); + expect(mockConfirm).toHaveBeenCalledWith({ + message: 'Save this remote selection for this repository? (saves to .tangledrc)', + default: false, + }); + }); + + it('should default to false', async () => { + mockConfirm.mockResolvedValue(false); + + const result = await promptToSaveRemote(); + + expect(result).toBe(false); + const call = mockConfirm.mock.calls[0]?.[0]; + expect(call?.default).toBe(false); + }); + }); });