snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
142
internal/scripts/cloudflare/ssl-cleanup.ts
Normal file
142
internal/scripts/cloudflare/ssl-cleanup.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { execFileSync } from 'child_process'
|
||||
import { makeEnv } from '../lib/makeEnv'
|
||||
import { nicelog } from '../lib/nicelog'
|
||||
|
||||
// Delete orphaned per-PR advanced cert packs (pr-NNNN-* hostnames) for CLOSED PRs only.
|
||||
// Usage: CLOUDFLARE_API_TOKEN=xxx yarn tsx internal/scripts/cloudflare/ssl-cleanup.ts <zone_name> [--delete]
|
||||
// Default is dry-run. Token needs "Zone: Read" + "SSL and Certificates: Edit";
|
||||
// gh CLI must be authed for tldraw/tldraw.
|
||||
|
||||
const env = makeEnv(['CLOUDFLARE_API_TOKEN'])
|
||||
|
||||
const [zoneName, mode] = process.argv.slice(2)
|
||||
if (!zoneName || (mode && mode !== '--delete')) {
|
||||
nicelog('usage: ssl-cleanup.ts <zone_name> [--delete]')
|
||||
process.exit(1)
|
||||
}
|
||||
const doDelete = mode === '--delete'
|
||||
|
||||
const API = 'https://api.cloudflare.com/client/v4'
|
||||
|
||||
async function cfApi<T>(endpoint: string, options: RequestInit = {}, attempt = 0): Promise<T> {
|
||||
const res = await fetch(`${API}${endpoint}`, {
|
||||
...options,
|
||||
headers: { Authorization: `Bearer ${env.CLOUDFLARE_API_TOKEN}` },
|
||||
})
|
||||
if (res.status === 429 && attempt < 3) {
|
||||
const waitSeconds = Number(res.headers.get('retry-after')) || 10
|
||||
nicelog(`rate limited, retrying in ${waitSeconds}s...`)
|
||||
await new Promise((resolve) => setTimeout(resolve, waitSeconds * 1000))
|
||||
return cfApi(endpoint, options, attempt + 1)
|
||||
}
|
||||
if (!res.ok) {
|
||||
throw new Error(`${options.method ?? 'GET'} ${endpoint}: ${res.status} ${res.statusText}`)
|
||||
}
|
||||
const data = (await res.json()) as { success: boolean; errors: unknown; result: T }
|
||||
if (!data.success) {
|
||||
throw new Error(`${options.method ?? 'GET'} ${endpoint}: ${JSON.stringify(data.errors)}`)
|
||||
}
|
||||
return data.result
|
||||
}
|
||||
|
||||
// Only an HTTP 404 (deleted PR) counts as closed; any other gh failure throws
|
||||
// so a broken token can't authorize deleting open-PR packs. Uses the gh CLI so
|
||||
// local runs reuse the developer's existing auth.
|
||||
const prStateCache = new Map<string, string>()
|
||||
function prState(pr: string): string {
|
||||
let state = prStateCache.get(pr)
|
||||
if (!state) {
|
||||
try {
|
||||
state = execFileSync('gh', ['api', `repos/tldraw/tldraw/pulls/${pr}`, '--jq', '.state'], {
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim()
|
||||
} catch (err: any) {
|
||||
if (String(err.stderr).includes('HTTP 404')) {
|
||||
state = 'closed'
|
||||
} else {
|
||||
throw new Error(`gh api failed for PR #${pr}: ${err.stderr ?? err}`)
|
||||
}
|
||||
}
|
||||
prStateCache.set(pr, state)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
interface CertPack {
|
||||
id: string
|
||||
type: string
|
||||
status: string
|
||||
hosts: string[]
|
||||
}
|
||||
|
||||
function isPreviewPack(p: CertPack) {
|
||||
// status=all also returns packs that are already deleted/pending deletion, skip those
|
||||
return (
|
||||
p.type === 'advanced' &&
|
||||
p.status !== 'deleted' &&
|
||||
p.status !== 'pending_deletion' &&
|
||||
p.hosts.some((h) => /^pr-\d+-/.test(h))
|
||||
)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const zones = await cfApi<{ id: string }[]>(`/zones?name=${zoneName}`)
|
||||
if (!zones[0]) throw new Error(`zone not found: ${zoneName}`)
|
||||
const zoneId = zones[0].id
|
||||
|
||||
let deleted = 0
|
||||
let kept = 0
|
||||
let failed = 0
|
||||
|
||||
// Process one page at a time instead of fetching everything up front, so an
|
||||
// abort partway through (rate limit, network) loses nothing already deleted
|
||||
// and a rerun picks up where it left off. Deleting shifts later items into
|
||||
// the current page, so after a page with deletions we refetch the same page
|
||||
// (skipping packs we've already handled) and only advance once it yields
|
||||
// nothing new. The endpoint caps per_page at 50.
|
||||
const processed = new Set<string>()
|
||||
let page = 1
|
||||
while (true) {
|
||||
const result = await cfApi<CertPack[]>(
|
||||
`/zones/${zoneId}/ssl/certificate_packs?status=all&per_page=50&page=${page}`
|
||||
)
|
||||
let deletedThisPage = 0
|
||||
for (const pack of result.filter(isPreviewPack)) {
|
||||
if (processed.has(pack.id)) continue
|
||||
processed.add(pack.id)
|
||||
const pr = pack.hosts.find((h) => /^pr-\d+-/.test(h))!.match(/^pr-(\d+)-/)![1]
|
||||
const hosts = pack.hosts.join(',')
|
||||
if (prState(pr) !== 'closed') {
|
||||
nicelog(`keeping (PR #${pr} open) ${hosts}`)
|
||||
kept++
|
||||
continue
|
||||
}
|
||||
if (doDelete) {
|
||||
try {
|
||||
await cfApi(`/zones/${zoneId}/ssl/certificate_packs/${pack.id}`, { method: 'DELETE' })
|
||||
nicelog(`deleted (PR #${pr} closed) ${pack.id} ${hosts}`)
|
||||
deleted++
|
||||
deletedThisPage++
|
||||
} catch (err) {
|
||||
nicelog(`FAILED to delete ${pack.id} ${hosts}: ${err}`)
|
||||
failed++
|
||||
}
|
||||
} else {
|
||||
nicelog(`would delete (PR #${pr} closed) ${pack.id} ${hosts}`)
|
||||
deleted++
|
||||
}
|
||||
}
|
||||
if (result.length < 50) break
|
||||
if (deletedThisPage === 0) page++
|
||||
}
|
||||
|
||||
nicelog('---')
|
||||
nicelog(
|
||||
`${deleted} closed-PR packs ${doDelete ? 'deleted' : 'to delete'}, ${kept} open-PR packs kept, ${failed} failed`
|
||||
)
|
||||
if (!doDelete) nicelog('(dry run — rerun with --delete to remove)')
|
||||
if (failed > 0) process.exit(1)
|
||||
}
|
||||
|
||||
main()
|
||||
Reference in New Issue
Block a user