Files
orca/src/main/git/exec-error.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

102 lines
3.0 KiB
TypeScript

// Keep diagnostics independent of runner imports and mocks.
/** Prefer stdout/stderr fields: Node may truncate or omit them from message. */
export function extractExecError(err: unknown): { stderr: string; stdout: string } {
if (err && typeof err === 'object') {
const e = err as { stderr?: unknown; stdout?: unknown; message?: unknown }
const stderr =
typeof e.stderr === 'string'
? e.stderr
: Buffer.isBuffer(e.stderr)
? e.stderr.toString('utf-8')
: ''
const stdout =
typeof e.stdout === 'string'
? e.stdout
: Buffer.isBuffer(e.stdout)
? e.stdout.toString('utf-8')
: ''
if (stderr || stdout) {
return { stderr, stdout }
}
if (typeof e.message === 'string') {
return { stderr: e.message, stdout: '' }
}
}
return { stderr: String(err), stdout: '' }
}
/** Recognizes spawn ENOENT; callers must separately rule out a missing cwd. */
export function isMissingCommandBinaryError(err: unknown): boolean {
return Boolean(
err &&
typeof err === 'object' &&
'code' in err &&
err.code === 'ENOENT' &&
'syscall' in err &&
typeof err.syscall === 'string' &&
err.syscall.startsWith('spawn ')
)
}
/** Parse Retry-After seconds or HTTP dates; callers can surface explicit server waits. */
export function parseRetryAfterMs(stderr: string): number | null {
const raw = findRetryAfterHeaderValue(stderr)
if (raw === null) {
return null
}
if (/^\d+$/.test(raw)) {
const seconds = Number(raw)
return Number.isFinite(seconds) ? seconds * 1000 : null
}
const ts = Date.parse(raw)
if (Number.isNaN(ts)) {
return null
}
return Math.max(0, ts - Date.now())
}
function findRetryAfterHeaderValue(stderr: string): string | null {
const headerIndex = indexOfAsciiIgnoreCase(stderr, 'retry-after:', 0)
if (headerIndex === -1) {
return null
}
let valueStart = headerIndex + 'retry-after:'.length
while (valueStart < stderr.length) {
const code = stderr.charCodeAt(valueStart)
if (code !== 9 && code !== 32) {
break
}
valueStart++
}
let valueEnd = valueStart
while (valueEnd < stderr.length) {
const code = stderr.charCodeAt(valueEnd)
if (code === 10 || code === 13) {
break
}
valueEnd++
}
const value = stderr.slice(valueStart, valueEnd).trim()
return value.length > 0 ? value : null
}
function indexOfAsciiIgnoreCase(value: string, search: string, fromIndex: number): number {
const lastStart = value.length - search.length
for (let index = Math.max(0, fromIndex); index <= lastStart; index++) {
let matches = true
for (let offset = 0; offset < search.length; offset++) {
const code = value.charCodeAt(index + offset)
const normalizedCode = code >= 65 && code <= 90 ? code + 32 : code
if (normalizedCode !== search.charCodeAt(offset)) {
matches = false
break
}
}
if (matches) {
return index
}
}
return -1
}