Files
awesome-ai-dev-tools/site.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

73 lines
2.3 KiB
Go

package main
import (
"encoding/json"
"io"
)
// siteRow is one ranked repo in the generated dist/data.json, consumed by site/index.html.
type siteRow struct {
Key string `json:"key"` // canonical owner/repo, matches history keys
NameWithOwner string `json:"nameWithOwner"`
URL string `json:"url"`
Stars int `json:"stars"`
Delta7d int `json:"delta7d"`
HasDelta bool `json:"hasDelta"`
Delta30d int `json:"delta30d"`
HasDelta30 bool `json:"hasDelta30"`
Language string `json:"language"`
PushedAt string `json:"pushedAt"`
Description string `json:"description"`
Tags []string `json:"tags"`
Notes string `json:"notes,omitempty"`
Archived bool `json:"archived"`
}
type siteData struct {
UpdatedAt string `json:"updatedAt"`
Rows []siteRow `json:"rows"`
History []Snapshot `json:"history"`
// Facets drives the dashboard's filter chips, so the tag vocabulary is
// defined once in Go rather than duplicated in site/index.html.
Facets []tagFacet `json:"facets"`
}
// writeSiteData emits the JSON payload the dashboard fetches at runtime.
// It is build output (dist/data.json), never committed.
//
// updatedAt is passed in rather than read from the clock because it labels
// data freshness, not build time: a redeploy of unchanged data must not claim
// the figures are newer than the fetch that produced them.
func writeSiteData(path string, updatedAt string, stats []Stat, deltas7, deltas30 map[string]int, history []Snapshot) error {
rows := make([]siteRow, len(stats))
for i, s := range stats {
delta7, has7 := deltas7[s.CanonicalKey]
delta30, has30 := deltas30[s.CanonicalKey]
rows[i] = siteRow{
Key: s.CanonicalKey,
NameWithOwner: s.NameWithOwner,
URL: s.URL,
Stars: s.Stars,
Delta7d: delta7,
HasDelta: has7,
Delta30d: delta30,
HasDelta30: has30,
Language: s.Language,
PushedAt: s.PushedAt.Format("2006-01-02"),
Description: s.Description,
Tags: s.Tags,
Notes: s.Notes,
Archived: s.IsArchived,
}
}
return atomicWriteFile(path, func(w io.Writer) error {
return json.NewEncoder(w).Encode(siteData{
UpdatedAt: updatedAt,
Rows: rows,
History: history,
Facets: tagVocabulary,
})
})
}