mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +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>
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
import type { GitHubOwnerRepo, GitHubPRFile } from '../../shared/github/pull-request-types'
|
|
import type { Repo } from '../../shared/repo-types'
|
|
import type { LocalProjectGhExecOptions } from '../project-runtime-git-options'
|
|
import { getIssue, getPRCheckDetails, getPRChecks, getPRComments } from '../github/client'
|
|
import { getPRFileContents } from '../github/work-item-details'
|
|
|
|
type LocalGitArgs = [] | [LocalProjectGhExecOptions]
|
|
|
|
type RuntimeGitHubReviewQueryCommandsDeps = {
|
|
resolveRepo: (selector: string) => Promise<Repo>
|
|
getLocalGitArgs: (repo: Repo) => LocalGitArgs
|
|
}
|
|
|
|
export class RuntimeGitHubReviewQueryCommands {
|
|
constructor(private readonly deps: RuntimeGitHubReviewQueryCommandsDeps) {}
|
|
|
|
async getRepoIssue(
|
|
repoSelector: string,
|
|
number: number
|
|
): Promise<Awaited<ReturnType<typeof getIssue>>> {
|
|
const repo = await this.deps.resolveRepo(repoSelector)
|
|
return getIssue(
|
|
repo.path,
|
|
number,
|
|
repo.connectionId ?? null,
|
|
...this.deps.getLocalGitArgs(repo)
|
|
)
|
|
}
|
|
|
|
async getRepoPRChecks(
|
|
repoSelector: string,
|
|
prNumber: number,
|
|
headSha?: string,
|
|
prRepo?: GitHubOwnerRepo | null,
|
|
options?: { noCache?: boolean }
|
|
): Promise<Awaited<ReturnType<typeof getPRChecks>>> {
|
|
const repo = await this.deps.resolveRepo(repoSelector)
|
|
return getPRChecks(
|
|
repo.path,
|
|
prNumber,
|
|
headSha,
|
|
prRepo ?? null,
|
|
options,
|
|
repo.connectionId ?? null,
|
|
...this.deps.getLocalGitArgs(repo)
|
|
)
|
|
}
|
|
|
|
async getRepoPRCheckDetails(
|
|
repoSelector: string,
|
|
args: {
|
|
checkRunId?: number
|
|
workflowRunId?: number
|
|
checkName?: string
|
|
url?: string | null
|
|
prRepo?: GitHubOwnerRepo | null
|
|
},
|
|
signal?: AbortSignal
|
|
): Promise<Awaited<ReturnType<typeof getPRCheckDetails>>> {
|
|
const repo = await this.deps.resolveRepo(repoSelector)
|
|
const localGitOptions = this.deps.getLocalGitArgs(repo)[0] ?? {}
|
|
return getPRCheckDetails(
|
|
repo.path,
|
|
{ ...args, prRepo: args.prRepo ?? null },
|
|
repo.connectionId ?? null,
|
|
localGitOptions,
|
|
signal
|
|
)
|
|
}
|
|
|
|
async getRepoPRComments(
|
|
repoSelector: string,
|
|
prNumber: number,
|
|
prRepo?: GitHubOwnerRepo | null,
|
|
options?: { noCache?: boolean }
|
|
): Promise<Awaited<ReturnType<typeof getPRComments>>> {
|
|
const repo = await this.deps.resolveRepo(repoSelector)
|
|
return getPRComments(
|
|
repo.path,
|
|
prNumber,
|
|
{ ...options, prRepo: prRepo ?? null },
|
|
repo.connectionId ?? null,
|
|
...this.deps.getLocalGitArgs(repo)
|
|
)
|
|
}
|
|
|
|
async getRepoPRFileContents(
|
|
repoSelector: string,
|
|
args: {
|
|
prNumber: number
|
|
prRepo?: GitHubOwnerRepo | null
|
|
path: string
|
|
oldPath?: string
|
|
status: GitHubPRFile['status']
|
|
headSha: string
|
|
baseSha: string
|
|
}
|
|
): Promise<Awaited<ReturnType<typeof getPRFileContents>>> {
|
|
const repo = await this.deps.resolveRepo(repoSelector)
|
|
return getPRFileContents({
|
|
repoPath: repo.path,
|
|
connectionId: repo.connectionId ?? null,
|
|
localGitOptions: this.deps.getLocalGitArgs(repo)[0],
|
|
...args
|
|
})
|
|
}
|
|
}
|