mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 01:57:57 +00:00
* feat(github): bind projects to a specific gh account Adds per-project `Repo.ghAccount` so repo-scoped gh calls (create-worktree issue/PR search, work items, hosted-review reads and mutations) run as the bound account via ephemeral child-env token injection instead of the globally active gh login. Multi-account resolution is capability-gated (gh >= 2.40) and fails closed when the bound account or host is unavailable; Project View stays ambient by design. Repository settings gains a section for selecting or clearing a keyring-backed account (shadcn `Select`), with mixed-version "not enforced" handling for older remote runtimes. Attached `-Rhost/owner/repo` forms are covered by the host-drift guard and its tests; es/ja/ko/zh catalogs carry the section's strings. `getLocalProjectGhExecOptions` centralizes the binding lookup so every gh execution path picks it up, including the Electron `hostedReview:*` handlers that previously stayed on the ambient login. `gh auth token` (a keyring read) is exempt from the rate-limit breaker gate so a tripped bucket cannot turn a bound-token resolve into a false "unavailable". The `ghAccount` update field and the two binding RPC methods live in the shared RPC params contract; the generated catalog is regenerated. Fixes #13612 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012B3QEP5iP4WGGEpPLtkHqA * fix(settings): make GitHub account refresh secondary * fix(github): satisfy strict casting quality checks * test(rpc): use runtime fixture for repo binding * fix(github): preserve project account for PR worktree lookups * test(rpc): avoid incomplete runtime settings fixture * fix(i18n): add GitHub account refresh label * fix(i18n): refresh runtime required catalog * fix(windows): preserve mobile patch bytes --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai>
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
const { ghExecFileAsyncMock } = vi.hoisted(() => ({
|
|
ghExecFileAsyncMock: vi.fn()
|
|
}))
|
|
|
|
vi.mock('../git/runner', () => ({
|
|
ghExecFileAsync: ghExecFileAsyncMock
|
|
}))
|
|
|
|
import {
|
|
clearGhCapabilityStateForTests,
|
|
getGhExecutionHostKey,
|
|
getGhMultiAccountCapability,
|
|
invalidateGhMultiAccountCapability
|
|
} from './gh-capability-state'
|
|
|
|
describe('gh-capability-state', () => {
|
|
beforeEach(() => {
|
|
clearGhCapabilityStateForTests()
|
|
ghExecFileAsyncMock.mockReset()
|
|
})
|
|
|
|
afterEach(() => {
|
|
clearGhCapabilityStateForTests()
|
|
})
|
|
|
|
it('keys local vs wsl execution hosts', () => {
|
|
expect(getGhExecutionHostKey({})).toBe('local')
|
|
expect(getGhExecutionHostKey({ wslDistro: 'Ubuntu' })).toBe('wsl:Ubuntu')
|
|
})
|
|
|
|
it('reports supported when help lists --user', async () => {
|
|
ghExecFileAsyncMock.mockResolvedValue({
|
|
stdout: 'Usage: gh auth token [--user login]',
|
|
stderr: ''
|
|
})
|
|
await expect(getGhMultiAccountCapability()).resolves.toBe('supported')
|
|
expect(ghExecFileAsyncMock).toHaveBeenCalledWith(
|
|
['auth', 'token', '--help'],
|
|
expect.objectContaining({ timeout: 10_000 })
|
|
)
|
|
})
|
|
|
|
it('reports unsupported when help omits --user', async () => {
|
|
ghExecFileAsyncMock.mockResolvedValue({
|
|
stdout: 'Usage: gh auth token',
|
|
stderr: ''
|
|
})
|
|
await expect(getGhMultiAccountCapability({ wslDistro: 'Ubuntu' })).resolves.toBe('unsupported')
|
|
})
|
|
|
|
it('caches per runtime and invalidates on request', async () => {
|
|
ghExecFileAsyncMock.mockResolvedValue({
|
|
stdout: 'Usage: gh auth token [--user login]',
|
|
stderr: ''
|
|
})
|
|
await getGhMultiAccountCapability({ wslDistro: 'Ubuntu' })
|
|
await getGhMultiAccountCapability({ wslDistro: 'Ubuntu' })
|
|
expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(1)
|
|
|
|
invalidateGhMultiAccountCapability({ wslDistro: 'Ubuntu' })
|
|
await getGhMultiAccountCapability({ wslDistro: 'Ubuntu' })
|
|
expect(ghExecFileAsyncMock).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|