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

This commit is contained in:
upstream-mirror
2026-09-23 16:12:52 +05:00
commit a774ac673a
4541 changed files with 770499 additions and 0 deletions

View File

@@ -0,0 +1,165 @@
import { nicelog } from '@/utils/nicelog'
import { BrokenLink, extractLinks } from './checkBrokenLinks'
import { connect } from './connect'
const CONCURRENCY = 10
const TIMEOUT_MS = 30_000
const MAX_RETRIES = 2
// Domains that block automated requests (403/captcha regardless of method)
const SKIP_DOMAINS = ['www.npmjs.com', 'npmjs.com', 'www.shadertoy.com', 'shadertoy.com']
function shouldSkipUrl(url: string): boolean {
if (url.includes('{')) return true
try {
const host = new URL(url).hostname
if (host === 'example.com' || host.endsWith('.example.com')) return true
if (SKIP_DOMAINS.includes(host)) return true
} catch {
// skip malformed URLs
}
return false
}
function isTransientError(result: { ok: boolean; status?: number; error?: string }): boolean {
if (result.ok) return false
// Retry on network errors (timeout, connection reset, etc.)
if (result.error) return true
// Retry on server errors and rate limits
if (result.status && (result.status >= 500 || result.status === 429)) return true
return false
}
async function checkUrlOnce(
url: string
): Promise<{ ok: boolean; status?: number; error?: string }> {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)
try {
// Try HEAD first (faster, less bandwidth)
let res = await fetch(url, {
method: 'HEAD',
signal: controller.signal,
redirect: 'follow',
headers: { 'User-Agent': 'tldraw-docs-link-checker' },
})
// Some servers reject HEAD — fall back to GET
if (res.status === 405 || res.status === 403) {
res = await fetch(url, {
method: 'GET',
signal: controller.signal,
redirect: 'follow',
headers: { 'User-Agent': 'tldraw-docs-link-checker' },
})
}
if (res.ok) return { ok: true }
return { ok: false, status: res.status }
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err)
return { ok: false, error: message }
} finally {
clearTimeout(timer)
}
}
async function checkUrl(url: string): Promise<{ ok: boolean; status?: number; error?: string }> {
let result = await checkUrlOnce(url)
for (let attempt = 0; attempt < MAX_RETRIES && isTransientError(result); attempt++) {
// Back off briefly before retrying
await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)))
result = await checkUrlOnce(url)
}
return result
}
export async function checkExternalLinks(): Promise<number> {
const db = await connect({ mode: 'readonly' })
const articles = await db.all<{ path: string | null; content: string }[]>(
'SELECT path, content FROM articles'
)
// Collect all external link occurrences, deduplicating by URL
const urlOccurrences = new Map<string, Array<{ articlePath: string; line: number }>>()
for (const article of articles) {
if (!article.path) continue
const links = extractLinks(article.content)
for (const { url, line } of links) {
if (!url.startsWith('http://') && !url.startsWith('https://')) continue
if (shouldSkipUrl(url)) continue
// Strip fragment for checking (servers don't validate fragments)
const hashIdx = url.indexOf('#')
const urlWithoutFragment = hashIdx >= 0 ? url.slice(0, hashIdx) : url
let list = urlOccurrences.get(urlWithoutFragment)
if (!list) {
list = []
urlOccurrences.set(urlWithoutFragment, list)
}
list.push({ articlePath: article.path, line })
}
}
await db.close()
const uniqueUrls = [...urlOccurrences.keys()]
nicelog(
`Checking ${uniqueUrls.length} unique external URLs across ${articles.length} articles...`
)
// Check URLs with bounded concurrency
const results = new Map<string, { ok: boolean; status?: number; error?: string }>()
let checked = 0
async function worker(urls: string[]) {
for (const url of urls) {
results.set(url, await checkUrl(url))
checked++
if (checked % 50 === 0) {
nicelog(` ...checked ${checked}/${uniqueUrls.length}`)
}
}
}
// Partition URLs across workers
const chunks: string[][] = Array.from({ length: CONCURRENCY }, () => [])
for (let i = 0; i < uniqueUrls.length; i++) {
chunks[i % CONCURRENCY].push(uniqueUrls[i])
}
await Promise.all(chunks.map(worker))
// Collect broken links
const broken: BrokenLink[] = []
for (const [url, result] of results) {
if (result.ok) continue
const reason = result.status ? `HTTP ${result.status}` : (result.error ?? 'unknown error')
const occurrences = urlOccurrences.get(url)!
for (const { articlePath, line } of occurrences) {
broken.push({ articlePath, line, url, reason })
}
}
if (broken.length > 0) {
nicelog(
`\n✗ Found ${broken.length} broken external link${broken.length === 1 ? '' : 's'}:\n\n` +
broken
.map(
(b, i) =>
`${i + 1}.\t${b.url}\n\tIn: ${b.articlePath} (line ${b.line})\n\tReason: ${b.reason}`
)
.join('\n\n') +
'\n'
)
} else {
nicelog('\n✔ All external links are valid!')
}
return broken.length
}