Skip to content
Open
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
119 changes: 119 additions & 0 deletions tasks/clear-gh-cache.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env node
Comment thread
Tobbe marked this conversation as resolved.

import { execSync } from 'node:child_process'

const REPO = 'cedarjs/cedar'
const API = 'https://api.github.com'

interface GitHubCache {
id: number
key: string
}

interface GitHubCacheResponse {
actions_caches: GitHubCache[]
}

function getToken(): string {
try {
return execSync('gh auth token', { encoding: 'utf-8' }).trim()
} catch {
console.error('Failed to get GitHub token. Run `gh auth login` first.')
process.exit(1)
}
}

async function listCaches(token: string): Promise<GitHubCache[]> {
const caches: GitHubCache[] = []
let page = 1
let hasNextPage = true

while (hasNextPage) {
const res = await fetch(
`${API}/repos/${REPO}/actions/caches?per_page=100&page=${page}`,
{
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github+json',
},
},
)

if (!res.ok) {
console.error(`Failed to list caches: ${res.status} ${await res.text()}`)
process.exit(1)
}

const data: GitHubCacheResponse = await res.json()
caches.push(...data.actions_caches)

if (data.actions_caches.length < 100) {
hasNextPage = false
}

page++
}

return caches
}

async function deleteCache(token: string, id: number): Promise<boolean> {
const res = await fetch(`${API}/repos/${REPO}/actions/caches/${id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github+json',
},
})
return res.ok
}

async function deleteCachesByKey(token: string, key: string): Promise<boolean> {
const res = await fetch(
`${API}/repos/${REPO}/actions/caches?key=${encodeURIComponent(key)}`,
{
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/vnd.github+json',
},
},
)
return res.ok
}

const token = getToken()

const keyFilter = process.argv[2]

if (keyFilter) {
console.log(`Deleting all caches matching key "${keyFilter}"...`)
const ok = await deleteCachesByKey(token, keyFilter)
if (ok) {
console.log('Done.')
} else {
console.error('Failed to delete caches.')
process.exit(1)
}
} else {
console.log('Listing all caches...')
const caches = await listCaches(token)
console.log(`Found ${caches.length} cache(s).`)

if (caches.length === 0) {
console.log('Nothing to delete.')
process.exit(0)
}

let deleted = 0
for (const cache of caches) {
const ok = await deleteCache(token, cache.id)
if (ok) {
deleted++
process.stdout.write('.')
} else {
process.stdout.write('!')
}
Comment on lines +111 to +116
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 When individual cache deletions fail in the bulk-delete loop, the error is silently summarised as a ! character with no indication of which cache failed or why. Including the cache key (and optionally the HTTP status) makes it much easier to retry failed deletions.

Suggested change
if (ok) {
deleted++
process.stdout.write('.')
} else {
process.stdout.write('!')
}
if (ok) {
deleted++
process.stdout.write('.')
} else {
process.stdout.write(`\n! Failed to delete cache id=${cache.id} key="${cache.key}"`)
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

}
console.log(`\nDeleted ${deleted}/${caches.length} cache(s).`)
}
Loading