mirror of
https://github.com/tiennm99/awesome-ai-dev-tools.git
synced 2026-10-05 00:13:12 +00:00
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.
100 lines
2.5 KiB
Go
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
|
|
}
|