generated from mathias/template-go-web
feat(atlas): Phase B — data-driven /api/atlas.json, CI stage generated from cd.yml
The atlas no longer hand-maintains its content. Authored data lives in one place (internal/atlas/atlas.json); the Go layer overlays sourced facts and serves the result at /api/atlas.json; the frontend fetches + renders (inline arrays kept only as an offline fallback). First generated source: the CI/CD stage's nodes are parsed from the repo's own .gitea/workflows/cd.yml — so the viz shows the pipeline that actually runs (guard/check/build/deploy), dropping the aspirational var-go/oath-gate node that isn't wired yet. That's the point: it can't drift from the real pipeline. New internal/atlas package (JobsFromWorkflow, Build) built test-first. Adds gopkg.in/yaml.v3 (justified: parsing the workflow YAML; stdlib has no YAML). Verified: go build/vet/lint(0)/test green; /api/atlas.json → 9 stages, CI = real jobs; frontend renders from the fetch (screenshot). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
// Package atlas builds the CAD Atlas data model, deriving parts from real
|
||||
// sources (the repo's own CI workflow, infra manifests, the live cluster)
|
||||
// so the visualization can't drift from reality.
|
||||
package atlas
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// JobsFromWorkflow extracts the job names, in document order, from a Gitea
|
||||
// Actions / GitHub Actions workflow YAML. Used to generate the CI/CD stage of
|
||||
// the atlas from the pipeline that actually runs, rather than hand-authoring it.
|
||||
func JobsFromWorkflow(workflow []byte) ([]string, error) {
|
||||
var doc yaml.Node
|
||||
if err := yaml.Unmarshal(workflow, &doc); err != nil {
|
||||
return nil, fmt.Errorf("parse workflow: %w", err)
|
||||
}
|
||||
if len(doc.Content) == 0 || doc.Content[0].Kind != yaml.MappingNode {
|
||||
return nil, fmt.Errorf("workflow: expected a top-level mapping")
|
||||
}
|
||||
jobs := mappingValue(doc.Content[0], "jobs")
|
||||
if jobs == nil {
|
||||
return nil, fmt.Errorf("workflow: no jobs block")
|
||||
}
|
||||
if jobs.Kind != yaml.MappingNode {
|
||||
return nil, fmt.Errorf("workflow: jobs is not a mapping")
|
||||
}
|
||||
|
||||
// A mapping node stores keys and values as alternating Content entries;
|
||||
// keys are the even indices, in document order.
|
||||
names := make([]string, 0, len(jobs.Content)/2)
|
||||
for i := 0; i+1 < len(jobs.Content); i += 2 {
|
||||
names = append(names, jobs.Content[i].Value)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// mappingValue returns the value node for key in a YAML mapping node, or nil.
|
||||
func mappingValue(m *yaml.Node, key string) *yaml.Node {
|
||||
for i := 0; i+1 < len(m.Content); i += 2 {
|
||||
if m.Content[i].Value == key {
|
||||
return m.Content[i+1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user