Files
orca/src/shared/github/api-availability.ts
T
Neil 583ab1601b refactor(shared): group worktree, github, and linear modules into folders (#14437)
`src/shared` is a flat directory of ~1,150 entries. The worktree, github, and
linear domains accounted for 71 of them, so finding the module you wanted meant
scanning a wall of same-prefixed filenames.

Move each domain into its own folder and drop the now-redundant prefix:

    src/shared/github-pr-types.ts    -> src/shared/github/pull-request-types.ts
    src/shared/worktree-id.ts        -> src/shared/worktree/id.ts
    src/shared/linear-links.ts       -> src/shared/linear/links.ts

This follows the existing `network/` and `new-workspace/` convention in the
same directory, which also drop the prefix inside the folder.

Whole clusters move, including tests. Foldering only part of a domain would be
worse than flat: a reader would have to check both `github/` and the flat
directory, and `github-auth-types.ts` / `github-project-types.ts` are type
modules that belong with the rest. No files with these prefixes remain flat.

Import specifiers were rewritten by resolving each one to an absolute path and
recomputing it, not by string substitution, so the `@/../../shared/...` alias
forms are handled correctly. 501 specifiers across 298 files.

Two things `tsc` cannot catch, handled explicitly:

- `github-project-types.ts` carries its own `max-lines` bypass, so its baseline
  entry is REPOINTED to the new path rather than pruned. Pruning would drop the
  bypass and then flag the new path as a fresh violation. Ratchet stays at 345.
- `mobile/` is outside `pnpm typecheck` and cannot be typechecked here
  (`mobile/node_modules` is empty). Instead every relative specifier in the repo
  was resolved against the filesystem: 174 unresolved before this change and 174
  after — identical, so nothing broke in mobile either.

The pinned `tests/e2e/.cross-version-checkouts` fixtures are deliberately NOT
rewritten; they are a snapshot of an older release and still reference the old
paths.

Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches).
2026-08-13 20:44:16 -07:00

53 lines
2.7 KiB
TypeScript

// Why: a GitHub outage, a dropped network, or a rate-limit all surface as
// unstructured gh/Octokit error text. Detecting them from one shared place lets
// both the main process (PR-refresh classification) and the renderer (Tasks
// work-item fan-out) attribute the failure to GitHub — not to Orca — using the
// exact same rules, so the two surfaces never disagree about whether GitHub is
// reachable. Returns null for anything that is NOT a reachability problem
// (auth, permission, 404): those are user-actionable, not "GitHub is down".
export type GitHubUnavailableKind = 'server_error' | 'network' | 'rate_limited'
// Rate-limit first: a primary rate-limit response also carries "HTTP 403", so
// it must win over any 4xx/permission interpretation downstream.
const RATE_LIMITED_PATTERN =
/rate limit|secondary rate limit|abuse detection|\bhttp[\s/]*429\b|\b429 too many requests\b/i
// Server-side outage. Anchor on "HTTP 5xx" or named 5xx statuses rather than a
// bare 3-digit match so unrelated numbers in stderr can't be misread as an
// outage.
const SERVER_ERROR_PATTERN =
/\bhttp[\s/]*5\d\d\b|\b5\d\d\s+(?:internal server error|bad gateway|service unavailable|gateway time-?out)\b|\binternal server error\b|\bbad gateway\b|\bservice unavailable\b|\bgateway time-?out\b|\bserver error\b|\btemporarily unavailable\b/i
// Transport-level failures — DNS, refused/reset connections, timeouts. Covers
// both Node (ENOTFOUND/ECONNRESET) and the gh Go client ("dial tcp", "i/o
// timeout", "no such host") shapes.
const NETWORK_PATTERN =
/timeout|\btimed out\b|\bno such host\b|could not resolve host|could not resolve to a host|\bnetwork(?:error| (?:error|unavailable|unreachable|request failed))\b|\bconnection (?:refused|reset)\b|\berror connecting to\b|\bfailed to connect to\b|\bdial tcp\b|\bi\/o timeout\b|\bfetch failed\b|\bsocket hang up\b|ENOTFOUND|ECONNRESET|ECONNREFUSED|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i
/**
* Classify a gh/Octokit error message as a GitHub-reachability problem, or
* null when it is not one (auth, permission, 404, validation, etc.).
*/
export function classifyGitHubUnavailable(message: string): GitHubUnavailableKind | null {
if (!message) {
return null
}
if (RATE_LIMITED_PATTERN.test(message)) {
return 'rate_limited'
}
if (SERVER_ERROR_PATTERN.test(message)) {
return 'server_error'
}
if (NETWORK_PATTERN.test(message)) {
return 'network'
}
return null
}
/** True when the message indicates GitHub itself is unreachable/unavailable. */
export function isGitHubUnavailableError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error ?? '')
return classifyGitHubUnavailable(message) !== null
}