Files
supabase/apps/www/scripts/fetchAgentSkills.mjs
salmanrf 8696762b4b fix(www): keep committed agent-skills index when fetch fails outside production (#50556)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix (build resilience)

## What is the current behavior?

`apps/www/scripts/fetchAgentSkills.mjs` runs as part of `content:build`
and fails the whole `www` build (and `pnpm dev:www`) whenever the GitHub
API call fails. #50106 added `AGENT_SKILLS_GITHUB_TOKEN` to mitigate
rate limits on Vercel, but that does not cover local runs or builds
where the env var is not available (e.g. fork PRs).

Example from a local `pnpm dev:www` hitting the unauthenticated rate
limit:

```
www:dev: Error: GET https://api.github.com/repos/supabase/agent-skills/releases/latest → 403
www:dev: at fetchJson (file:///.../apps/www/scripts/fetchAgentSkills.mjs:38:22)
www:dev: at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
www:dev: at async main (file:///.../apps/www/scripts/fetchAgentSkills.mjs:53:19)
```

## What is the new behavior?

`public/.well-known/agent-skills/index.json` is already committed to the
repo, so when the fetch fails and `VERCEL_ENV` is not `production`, the
script logs the error, keeps the committed file, and exits 0:

```
www:dev: Error: GET https://api.github.com/repos/supabase/agent-skills/releases/latest → 403
www:dev: ...
www:dev: Fetch failed — keeping committed public/.well-known/agent-skills/index.json
```

Production builds still fail loudly so a stale skills list is never
silently shipped.

Verified locally by forcing a 401 with a bad token:

- `VERCEL_ENV=preview` exits 0 and keeps the committed `index.json`
- `VERCEL_ENV=production` exits 1

## Additional context

Follow-up to #50106.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
  * Improved handling of skill data fetch failures outside production.
* Preserves previously available skill data when a fetch fails and a
committed fallback is available.
  * Continues to report failures when no fallback data exists.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-30 11:11:39 -07:00

65 lines
2.3 KiB
JavaScript

// @ts-check
/**
* Fetches the latest agent-skills index.json from supabase/agent-skills and
* writes it to public/.well-known/agent-skills/index.json.
*
* Skill URLs in the published index.json are absolute GitHub Release asset
* URLs — no rewriting needed on this side.
*
* Spec: https://github.com/agentskills/agentskills/pull/254
* Uses AGENT_SKILLS_GITHUB_TOKEN if set to avoid GitHub's unauthenticated
* rate limit (60 req/hr per IP, shared across Vercel build machines).
*
* If the fetch fails outside production, the committed index.json is kept so
* preview and local builds don't break on GitHub rate limits.
*/
import { existsSync, promises as fs } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const OUT_DIR = join(__dirname, '..', 'public', '.well-known', 'agent-skills')
const REPO = 'supabase/agent-skills'
async function fetchJson(url) {
const headers = { 'User-Agent': 'supabase-www-build' }
if (process.env.AGENT_SKILLS_GITHUB_TOKEN) {
headers['Authorization'] = `Bearer ${process.env.AGENT_SKILLS_GITHUB_TOKEN}`
}
const res = await fetch(url, { headers })
if (!res.ok) throw new Error(`GET ${url} → ${res.status}`)
return res.json()
}
async function main() {
const release = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`)
console.log(`Fetching agent-skills release: ${release.tag_name}`)
const indexAsset = release.assets.find((a) => a.name === 'index.json')
if (!indexAsset) throw new Error('No index.json found in release assets')
const index = await fetchJson(indexAsset.browser_download_url)
await fs.mkdir(OUT_DIR, { recursive: true })
await fs.writeFile(join(OUT_DIR, 'index.json'), JSON.stringify(index, null, 2) + '\n')
for (const skill of index.skills ?? []) {
console.log(` ${skill.name}`)
}
console.log(`Done — wrote public/.well-known/agent-skills/index.json`)
}
main().catch((err) => {
console.error(err)
const canFallBack = process.env.VERCEL_ENV !== 'production'
const hasPreviousWrite = existsSync(join(OUT_DIR, 'index.json'))
if (canFallBack && hasPreviousWrite) {
console.warn('Done — keeping committed public/.well-known/agent-skills/index.json')
process.exit(0)
}
process.exit(1)
})