Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 41 additions & 80 deletions src/releases/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,27 +103,6 @@ export const SOCKET_BTM_REPO = {

const logger = getDefaultLogger()

// In-memory TTL cache for GitHub API responses.
// Prevents redundant API calls during parallel asset downloads and
// repeated builds (e.g., pre-commit hooks) within the same process.
const API_CACHE_TTL_MS = 5 * 60_000
const _apiCache = new Map<string, { data: unknown; expiresAt: number }>()

function getCachedApiResponse<T>(key: string): T | undefined {
const entry = _apiCache.get(key)
if (entry && entry.expiresAt > Date.now()) {
return entry.data as T
}
if (entry) {
_apiCache.delete(key)
}
return undefined
}

function setCachedApiResponse(key: string, data: unknown): void {
_apiCache.set(key, { data, expiresAt: Date.now() + API_CACHE_TTL_MS })
}

let _fs: typeof import('node:fs') | undefined
let _path: typeof import('node:path') | undefined

Expand Down Expand Up @@ -383,42 +362,32 @@ export async function getLatestRelease(
// Create matcher function if pattern provided.
const isMatch = assetPattern ? createAssetMatcher(assetPattern) : undefined

const cacheKey = `releases:${owner}/${repo}`

return await pRetry(
async () => {
// Check in-memory cache first to avoid redundant API calls.
let releases = getCachedApiResponse<
Array<{
tag_name: string
published_at: string
assets: Array<{ name: string }>
}>
>(cacheKey)

if (!releases) {
// Fetch recent releases (100 should cover all tool releases).
const response = await httpRequest(
`https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`,
{
headers: getAuthHeaders(),
},
)

if (!response.ok) {
throw new Error(`Failed to fetch releases: ${response.status}`)
}
// Fetch recent releases (100 should cover all tool releases).
const response = await httpRequest(
`https://api.github.com/repos/${owner}/${repo}/releases?per_page=100`,
{
headers: getAuthHeaders(),
},
)

try {
releases = JSON.parse(response.body.toString('utf8'))
} catch (cause) {
throw new Error(
`Failed to parse GitHub releases response from https://api.github.com/repos/${owner}/${repo}/releases`,
{ cause },
)
}
if (!response.ok) {
throw new Error(`Failed to fetch releases: ${response.status}`)
}

setCachedApiResponse(cacheKey, releases)
let releases: Array<{
tag_name: string
published_at: string
assets: Array<{ name: string }>
}>
try {
releases = JSON.parse(response.body.toString('utf8'))
} catch (cause) {
throw new Error(
`Failed to parse GitHub releases response from https://api.github.com/repos/${owner}/${repo}/releases`,
{ cause },
)
}

// Filter releases matching the tool prefix.
Expand Down Expand Up @@ -514,37 +483,29 @@ export async function getReleaseAssetUrl(
? (input: string) => input === assetPattern
: createAssetMatcher(assetPattern as AssetPattern)

const cacheKey = `release:${owner}/${repo}:${tag}`

return await pRetry(
async () => {
// Check in-memory cache first to avoid redundant API calls.
let release = getCachedApiResponse<{
assets: Array<{ name: string; browser_download_url: string }>
}>(cacheKey)

if (!release) {
const response = await httpRequest(
`https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`,
{
headers: getAuthHeaders(),
},
)

if (!response.ok) {
throw new Error(`Failed to fetch release ${tag}: ${response.status}`)
}
const response = await httpRequest(
`https://api.github.com/repos/${owner}/${repo}/releases/tags/${tag}`,
{
headers: getAuthHeaders(),
},
)

try {
release = JSON.parse(response.body.toString('utf8'))
} catch (cause) {
throw new Error(
`Failed to parse GitHub release response for tag ${tag}`,
{ cause },
)
}
if (!response.ok) {
throw new Error(`Failed to fetch release ${tag}: ${response.status}`)
}

setCachedApiResponse(cacheKey, release)
let release: {
assets: Array<{ name: string; browser_download_url: string }>
}
try {
release = JSON.parse(response.body.toString('utf8'))
} catch (cause) {
throw new Error(
`Failed to parse GitHub release response for tag ${tag}`,
{ cause },
)
}

// Find the matching asset.
Expand Down
Loading