Files
aego/engine/docs/component_demos.go
sanspie a46347e2c6
ci-platforms / darwin arm64 smoke (push) Skipped
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m19s
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
github-native / gitea-pending (push) Skipped
github-native / darwin amd64 native (push) Skipped
github-native / linux amd64 native (push) Skipped
github-native / windows amd64 native (push) Skipped
github-native / darwin arm64 native (push) Skipped
github-native / gitea-result (push) Skipped
ci / checks (push) Failing after 23m57s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: failure
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
editor: add art studio and stage 12.5 workflows
2026-11-08 18:47:57 +03:00

126 lines
3.5 KiB
Go

//go:build !aego_runtime
package docs
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"io/fs"
"path"
"sort"
"strings"
"aego/engine/docs/democheck"
"aego/engine/module"
)
// ComponentDemos inventories exactly the public component entries populated
// from schema.Registry by components(), not functions, diagnostics or guides.
// Field identities are full, stable docs/schema IDs (not localized labels).
func (r *Registry) ComponentDemos() []democheck.Component {
var out []democheck.Component
for _, e := range r.entries {
if e.Component == nil {
continue
}
c := democheck.Component{ID: e.ID, DocumentID: e.ID, Scene: e.ID + "/try"}
if strings.TrimSpace(e.Try) == "" {
c.Scene = ""
} else {
data, _ := json.Marshal(struct {
Scene, Select, Script, Code string
Prefabs map[string]string
Artifacts []byte
}{e.Try, e.TrySelect, e.TryScript, e.TryCode, e.TryPrefabs, e.TryArtifacts})
sum := sha256.Sum256(data)
c.Identity = hex.EncodeToString(sum[:])
}
for _, field := range r.entries {
if field.Kind == Field && field.Field != nil && field.Parent == e.ID {
c.Fields = append(c.Fields, field.ID)
}
}
sort.Strings(c.Fields)
out = append(out, c)
}
sort.Slice(out, func(i, j int) bool { return out[i].ID < out[j].ID })
return out
}
// CheckComponentDemos is the behavioral gate for the native/web docs test
// harness. A nil runner fails explicitly; static existence is never coverage.
func (r *Registry) CheckComponentDemos(ctx context.Context, runner democheck.Runner) democheck.Report {
var contracts []democheck.Contract
for _, e := range r.entries {
if e.Demo != nil {
contracts = append(contracts, *e.Demo)
}
}
report := democheck.Check(ctx, r.ComponentDemos(), contracts, runner)
for _, p := range r.problems {
if p.Error {
report.Errors = append(report.Errors, p.ID+": "+p.Message)
}
}
return report
}
// The contract is a sidecar of the existing Try, not a new scene format. The
// same Entry exports it to docs/native clients; prepared pixels remain .aetry.
func (r *Registry) readDemoContract(e *Entry, src module.DocSource, scenePath string) {
// Guides may reuse a component's Try scene, script and artifacts. The
// behavioral sidecar belongs only to the component registry entry; a guide
// is not another owner and must neither attach nor reject that contract.
if e.Component == nil {
return
}
name := strings.TrimSuffix(path.Clean(scenePath), TryExt) + ".demo.json"
f, err := src.FS.Open(name)
if errors.Is(err, fs.ErrNotExist) {
return
}
problem := func(err error) {
r.problems = append(r.problems, Problem{ID: e.ID, Error: true, Message: "demo contract " + name + ": " + err.Error()})
}
if err != nil {
problem(err)
return
}
defer f.Close()
b, err := io.ReadAll(io.LimitReader(f, (1<<20)+1))
if err != nil {
problem(err)
return
}
if len(b) > 1<<20 {
problem(errors.New("contract exceeds 1 MiB"))
return
}
var c democheck.Contract
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields()
if err = dec.Decode(&c); err != nil {
problem(err)
return
}
var extra any
if dec.Decode(&extra) != io.EOF {
problem(errors.New("trailing JSON"))
return
}
if c.ComponentID != e.ID || c.DocumentID != e.ID {
problem(errors.New("component/document identity does not match owning registry entry"))
return
}
if e.Demo != nil {
problem(errors.New("duplicate component contract"))
return
}
e.Demo = &c
}