| 1 | import type { SourceControlProviderAuth } from "@t3tools/contracts"; |
| 2 | import type { GitHubSettings } from "@t3tools/client-runtime/source-control-clients"; |
| 3 | |
| 4 | export type GitHubDiscoveredAccount = NonNullable<SourceControlProviderAuth["accounts"]>[number]; |
| 5 | |
| 6 | export interface GitHubHostGroup { |
| 7 | readonly host: string; |
| 8 | /** gh's active stored login, the one used when Settings pin nothing. */ |
| 9 | readonly activeAccount: string | null; |
| 10 | /** Stored logins that work and can be pinned with `gh auth token --user`. */ |
| 11 | readonly selectable: ReadonlyArray<string>; |
| 12 | /** Logins gh holds but cannot use; shown with their error. */ |
| 13 | readonly broken: ReadonlyArray<GitHubDiscoveredAccount>; |
| 14 | /** Set when GH_TOKEN or a sibling overrides every stored login for this host. */ |
| 15 | readonly environmentVariable: string | null; |
| 16 | } |
| 17 | |
| 18 | /** Groups gh's logins by host, in the order gh reported the hosts. */ |
| 19 | export function groupGitHubAccounts( |
| 20 | accounts: ReadonlyArray<GitHubDiscoveredAccount>, |
| 21 | ): ReadonlyArray<GitHubHostGroup> { |
| 22 | const hosts = [...new Set(accounts.map((entry) => entry.host))]; |
| 23 | return hosts.map((host) => { |
| 24 | const entries = accounts.filter((entry) => entry.host === host); |
| 25 | const stored = entries.filter((entry) => entry.environmentVariable === undefined); |
| 26 | const selectable = stored.filter((entry) => entry.authenticated); |
| 27 | return { |
| 28 | host, |
| 29 | activeAccount: (selectable.find((entry) => entry.active) ?? selectable[0])?.account ?? null, |
| 30 | selectable: selectable.map((entry) => entry.account), |
| 31 | broken: stored.filter((entry) => !entry.authenticated), |
| 32 | environmentVariable: |
| 33 | entries.find((entry) => entry.environmentVariable)?.environmentVariable ?? null, |
| 34 | }; |
| 35 | }); |
| 36 | } |
| 37 | |
| 38 | /** |
| 39 | * The full `hosts` map after one host changes. Hosts left on gh's defaults (enabled, |
| 40 | * no pinned account) are dropped so settings only hold real choices. |
| 41 | */ |
| 42 | export function nextGitHubHosts( |
| 43 | current: GitHubSettings["hosts"], |
| 44 | host: string, |
| 45 | change: { readonly enabled?: boolean; readonly account?: string | null }, |
| 46 | ): GitHubSettings["hosts"] { |
| 47 | const previous = current[host]; |
| 48 | const enabled = change.enabled ?? previous?.enabled ?? true; |
| 49 | const account = change.account === undefined ? previous?.account : (change.account ?? undefined); |
| 50 | const { [host]: _replaced, ...rest } = current; |
| 51 | if (enabled && account === undefined) return rest; |
| 52 | return { ...rest, [host]: { enabled, ...(account === undefined ? {} : { account }) } }; |
| 53 | } |