Files
orca/src/main/github/gh-utils.ts
T
OrcaWinandOrcaWin 4687e90a16 [comment audit] Continue main-process comment cleanup (#32946)
* [comment audit] Shorten daemon, hook, and emulator comments [skip ci]

* [comment audit] Shorten VM, conversation, SQLite, and theme-import comments [skip ci]

* [comment audit] Tighten Git execution and source-control comment contracts [skip ci]

* [comment audit] Clarify repository probes and maintenance comments [skip ci]

* [comment audit] Shorten status, diff-cache, and checkout comments [skip ci]

* [comment audit] Clarify worktree lifecycle and WSL routing comments [skip ci]

* [comment audit] Shorten forge identity and review-refresh comments [skip ci]

* [comment audit] Tighten hosted-review client comment contracts [skip ci]

* [comment audit] Clarify GitLab compatibility and review comments [skip ci]

* [comment audit] Tighten hook ownership and watchdog rationale [skip ci]

* [comment audit] Condense host and integration compatibility comments [skip ci]

* [comment audit] Clarify IPC authority and remote-routing rationale [skip ci]

* [comment audit] Finalize file-import and containment comment cleanup

* [comment audit] Fix stale Jira type imports blocking full CI

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 16:27:37 -07:00

92 lines
2.4 KiB
TypeScript

import { execFile } from 'node:child_process'
import { promisify } from 'node:util'
import { gitExecFileAsync, ghExecFileAsync } from '../git/runner'
// Import pure parsing outside runner so runner-mocked tests retain the real implementation.
import { extractExecError, parseRetryAfterMs } from '../git/exec-error'
// Repo-scoped operations need the WSL-aware runner; this legacy wrapper serves generic commands.
export const execFileAsync = promisify(execFile)
export { ghExecFileAsync, gitExecFileAsync, extractExecError, parseRetryAfterMs }
export {
classifyGhError,
classifyListIssuesError,
classifyListPrsError,
classifyPullRequestUpdateError
} from './gh-error-classification'
export {
_getOwnerRepoCacheSize,
_resetOwnerRepoCache,
getOwnerRepoForRemote,
getRemoteUrlForRepo,
ghRepoExecOptions,
githubRepoContext,
parseGitHubOwnerRepo,
parseGitHubRemoteIdentity
} from './github-repository-identity'
export type {
GitHubRemoteIdentity,
GitHubRemoteIdentityProbeOptions,
GitHubRepoContext,
LocalGitExecOptions,
OwnerRepo
} from './github-repository-identity'
export {
getIssueOwnerRepo,
getOwnerRepo,
resolveIssueSource,
resolvePRRepositoryCandidates
} from './github-owner-repo-selection'
export type { PRRepositoryCandidates, ResolvedIssueSource } from './github-owner-repo-selection'
const MAX_CONCURRENT = 4
let running = 0
type QueueEntry = {
signal?: AbortSignal
start: () => void
reject: (error: Error) => void
}
const queue: QueueEntry[] = []
function githubOperationAbortError(): Error {
const error = new Error('GitHub operation aborted')
error.name = 'AbortError'
return error
}
export function acquire(signal?: AbortSignal): Promise<void> {
if (signal?.aborted) {
return Promise.reject(githubOperationAbortError())
}
if (running < MAX_CONCURRENT) {
running += 1
return Promise.resolve()
}
return new Promise((resolve, reject) => {
const entry: QueueEntry = {
signal,
reject,
start: () => {
signal?.removeEventListener('abort', onAbort)
running += 1
resolve()
}
}
const onAbort = (): void => {
const index = queue.indexOf(entry)
if (index === -1) {
return
}
queue.splice(index, 1)
reject(githubOperationAbortError())
}
signal?.addEventListener('abort', onAbort, { once: true })
queue.push(entry)
})
}
export function release(): void {
running -= 1
const next = queue.shift()
next?.start()
}