-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrefs.ts
More file actions
124 lines (116 loc) · 4.1 KB
/
refs.ts
File metadata and controls
124 lines (116 loc) · 4.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
* @fileoverview Resolve GitHub git refs (tag / branch / commit) to
* full commit SHAs.
*
* Two-tier strategy with cross-backend fallback:
*
* 1. REST cascade (`fetchRefSha`): tag → branch → commit. The
* first 200 OK wins.
* 2. GraphQL fallback (`fetchRefShaViaGraphQL`): fires only when
* the REST cascade hits the documented 200-OK-empty-body
* incident shape. GraphQL queries hit a different backend at
* GitHub, so it stays consistent through Elasticsearch outages
* that produce empty REST bodies.
*
* Caching: a `TtlCache` keyed by `${owner}/${repo}@${ref}` with a
* 5-minute TTL plus in-memory memoization. `clearRefCache()` clears
* the in-memory tier; the persistent disk tier survives so the cache
* warms quickly on the next run. Disable everything with the
* `DISABLE_GITHUB_CACHE` env var.
*
* Module shape: this file holds the public `resolveRefToSha` entry
* point. Supporting surface lives in sibling leaves and is re-exported
* here so existing `github/refs` importers keep working unchanged:
*
* - REST tier cascade — `./refs-rest`
* - GraphQL fallback — `./refs-graphql`
* - TtlCache singleton + clearRefCache — `./refs-cache`
*/
import process from 'node:process'
import { fetchRefSha } from './refs-rest'
import { getGithubCache } from './refs-cache'
import type { ResolveRefOptions } from './types'
/**
* Resolve a git ref (tag, branch, or commit SHA) to its full commit SHA.
* Handles tags (annotated and lightweight), branches, and commit SHAs.
* Results are cached in-memory and on disk (with TTL) to minimize API calls.
*
* Resolution strategy:
* 1. Try as a tag (refs/tags/{ref})
* 2. If tag is annotated, dereference to get the commit SHA
* 3. If not a tag, try as a branch (refs/heads/{ref})
* 4. If not a branch, try as a commit SHA directly
*
* Caching behavior:
* - In-memory cache (Map) for immediate lookups
* - Persistent disk cache (cacache) for durability across runs
* - Default TTL: 5 minutes
* - Disable caching with `DISABLE_GITHUB_CACHE` env var
*
* @param owner - Repository owner (user or organization name)
* @param repo - Repository name
* @param ref - Git reference to resolve (tag name, branch name, or commit SHA)
* @param options - Resolution options including authentication token
* @returns The full commit SHA (40-character hex string)
*
* @throws {Error} When ref cannot be resolved after trying all strategies
* @throws {GitHubRateLimitError} When API rate limit is exceeded
*
* @example
* ```ts
* // Resolve a tag to commit SHA
* const sha = await resolveRefToSha('owner', 'repo', 'v1.0.0')
* console.log(sha) // 'a1b2c3d4e5f6...'
* ```
*
* @example
* ```ts
* // Resolve a branch to latest commit SHA
* const sha = await resolveRefToSha('owner', 'repo', 'main')
* console.log(sha) // Latest commit on main branch
* ```
*
* @example
* ```ts
* // Resolve with custom token
* const sha = await resolveRefToSha(
* 'owner',
* 'repo',
* 'develop',
* { token: 'ghp_customtoken' }
* )
* ```
*
* @example
* ```ts
* // Commit SHA passes through unchanged (but validates it exists)
* const sha = await resolveRefToSha('owner', 'repo', 'a1b2c3d4')
* console.log(sha) // Full 40-char SHA
* ```
*/
export async function resolveRefToSha(
owner: string,
repo: string,
ref: string,
options?: ResolveRefOptions | undefined,
): Promise<string> {
const opts = {
__proto__: null,
...options,
} as ResolveRefOptions
const cacheKey = `${owner}/${repo}@${ref}`
// Optionally disable cache.
if (process.env['DISABLE_GITHUB_CACHE']) {
return await fetchRefSha(owner, repo, ref, opts)
}
// Use TTL cache for persistent storage and in-memory memoization.
const cache = getGithubCache()
return await cache.getOrFetch(cacheKey, async () => {
return await fetchRefSha(owner, repo, ref, opts)
})
}
// Re-exports — preserve the historical `github/refs` surface so
// downstream importers don't have to chase the split.
export { clearRefCache, getGithubCache } from './refs-cache'
export { fetchRefShaViaGraphQL } from './refs-graphql'
export { fetchRefSha } from './refs-rest'