Files
awesome-ai-dev-tools/main.go
T
tiennm99 00c90c5be9 feat: split data update from site build; publish via Cloudflare Pages
The updater fetched GitHub metadata and rendered the dashboard payload in
a single step, so anything that published the site needed a GITHUB_TOKEN.
Cloudflare Pages builds from a Git webhook and has no business holding
one.

Split the tool into three modes. The default update step still fetches
GitHub and now records every API-sourced field in data/metadata.json,
which is committed alongside README.md and data/history.jsonl. The new
-build mode joins that snapshot with data/agents.yml and
data/history.jsonl to render dist/ with no network access and no token;
-check is unchanged.

Supporting changes:

- computeDeltaAt anchors the delta windows to when the data was fetched
  rather than the wall clock, so a redeploy days later reproduces the
  same Δ7d instead of sliding the window past its slack allowance.
- writeSiteData takes updatedAt explicitly for the same reason: the
  timestamp labels data freshness, not build time.
- sortStats is extracted from fetchStats so the build step re-ranks
  identically from committed metadata.
- An agents.yml entry with no metadata yet is omitted with a warning
  instead of failing the build, which would otherwise block every deploy
  between merging a new entry and the next nightly run.
- update.yml drops the GitHub Pages deploy steps and commits
  data/metadata.json; ci.yml runs `go run . -build` so a build that would
  break on deploy breaks in CI first.
- site/_headers stops the edge serving a stale data.json after a refresh.
- docs/DEPLOY.md covers the Cloudflare setup, including the one-time
  bootstrap of data/metadata.json that the build depends on.

Also carries the in-progress curation work already in the tree: the
module rename to awesome-ai-dev-tools, retagged entries, and removal of
the archived Roo-Code, void, continue and suna entries.
2026-09-16 22:24:48 +07:00

100 lines
2.5 KiB
Go

package main
import (
"flag"
"fmt"
"log"
"os"
)
// Paths the updater reads and writes, relative to the repository root.
const (
agentsPath = "data/agents.yml"
historyPath = "data/history.jsonl"
metadataPath = "data/metadata.json"
readmeTmpl = "templates/readme.tmpl"
readmePath = "README.md"
siteDir = "site"
distDir = "dist"
)
// The tool has three modes, deliberately separated so the only step that needs
// network access and a GITHUB_TOKEN is the one that runs in CI:
//
// (default) update — fetch GitHub, refresh history/metadata/README
// -build — render dist/ from committed data, offline
// -check — validate data/agents.yml, offline
//
// The split is what lets an untrusted build environment (Cloudflare Pages)
// publish the site without ever holding a token.
func main() {
check := flag.Bool("check", false, "validate data/agents.yml offline (no network, no token) and exit")
build := flag.Bool("build", false, "render the static site into dist/ from committed data (no network, no token) and exit")
flag.Parse()
if *check && *build {
log.Print("-check and -build are mutually exclusive")
os.Exit(1)
}
switch {
case *check:
if err := runCheck(agentsPath); err != nil {
log.Printf("check failed: %v", err)
os.Exit(1)
}
case *build:
if err := runBuild(agentsPath, metadataPath, historyPath, siteDir, distDir); err != nil {
log.Printf("build failed: %v", err)
os.Exit(1)
}
default:
if err := run(); err != nil {
log.Fatalf("update failed: %v", err)
}
}
}
// run is the update step: it is the only mode that talks to GitHub. It writes
// data/metadata.json, which the build step later consumes offline.
func run() error {
agents, err := loadAgents(agentsPath)
if err != nil {
return err
}
if len(agents) == 0 {
return fmt.Errorf("no agents in %s", agentsPath)
}
token := os.Getenv("GITHUB_TOKEN")
if token == "" {
return fmt.Errorf("GITHUB_TOKEN env var required")
}
stats, err := fetchStats(token, agents)
if err != nil {
return err
}
stats = enforceStarFloor(stats)
if len(stats) == 0 {
return fmt.Errorf("no agents in %s meet the %d-star minimum", agentsPath, minStars)
}
_, deltas7, _, err := appendHistory(historyPath, stats)
if err != nil {
return err
}
if err := renderReadme(readmeTmpl, readmePath, stats, deltas7); err != nil {
return err
}
if err := writeMetadata(metadataPath, stats); err != nil {
return err
}
fmt.Printf("updated %d agents\n", len(stats))
return nil
}