Files
sanspie fac6fa35a4
ci-platforms / darwin arm64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
ci-platforms / darwin 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 7m29s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 14m13s
ci / release (push) Skipped
GitHub / windows amd64 native GitHub-hosted native CI: failure
GitHub / darwin arm64 native GitHub-hosted native CI: error
GitHub / darwin amd64 native GitHub-hosted native CI: failure
GitHub / linux amd64 native GitHub-hosted native CI: success
fix: stabilize CI and prepare docs release
Keep recovery copies private on Windows, make cross-platform test fixtures deterministic, and preserve the updated Art and documentation contracts.

Refresh workspace screenshot references for the intentional factory layouts and restore the earlier Russian palette reference after before/after comparison. Keep the existing screenshot tolerances.
2026-11-09 16:24:24 +03:00

160 lines
5.7 KiB
Go

package site
import (
"crypto/sha256"
"encoding/json"
"fmt"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"unicode/utf8"
"aego/asset"
"aego/project"
"aego/project/remoteexample"
)
type tryExample struct {
Manifest, Archive, Link string
}
var exampleSlug = regexp.MustCompile(`[^a-z0-9._-]+`)
func (b *builder) writeTryExamples() error {
if b.opt.ExamplesURL == "" {
if b.opt.ExamplesProduction {
return fmt.Errorf("official example publication cannot disable the examples URL")
}
return nil
}
u, err := url.Parse(b.opt.ExamplesURL)
if err != nil || u.RawQuery != "" || u.Fragment != "" || !strings.HasSuffix(u.Path, "/") {
return fmt.Errorf("examples URL must be an HTTPS site root ending in /")
}
origin, err := remoteexample.Origin(b.opt.ExamplesURL)
if err != nil {
return err
}
version := b.opt.ExamplesVersion
if version == "" {
version = "1.0.0"
}
license := strings.TrimSpace(b.opt.ExamplesLicense)
if license == "" {
license = "NOASSERTION"
}
if b.opt.ExamplesProduction && strings.EqualFold(license, "NOASSERTION") {
return fmt.Errorf("official example publication requires an explicit verified --examples-license")
}
if b.opt.ExamplesProduction && strings.TrimSpace(b.opt.ExamplesLicenseText) == "" {
return fmt.Errorf("official example publication requires the reviewed license text")
}
if text := b.opt.ExamplesLicenseText; text != "" && (len(text) > 64<<10 || !utf8.ValidString(text) || strings.TrimSpace(text) == "") {
return fmt.Errorf("example license text is empty, invalid UTF-8, or larger than 64 KiB")
}
b.examples = map[string]tryExample{}
for _, e := range b.exp.Entries {
if e.Try == "" {
continue
}
if e.TrySource == nil || len(e.TrySource.Files) == 0 {
return fmt.Errorf("%s has no complete Try source project; export the current registry", e.ID)
}
s := e.TrySource
identity := e.ID
if e.Demo != nil && e.Demo.DemoID != "" {
identity = e.Demo.DemoID
}
hash := sha256.Sum256([]byte(identity))
slug := strings.Trim(exampleSlug.ReplaceAllString(strings.ToLower(identity), "-"), ".-_")
if len(slug) > 58 {
slug = slug[:58]
}
name := fmt.Sprintf("%s-%x", slug, hash[:8])
if slug == "" {
name = fmt.Sprintf("example-%x", hash[:8])
}
rel := "examples/" + name + "/"
manifestURL := u.ResolveReference(&url.URL{Path: rel + "manifest.json"}).String()
archiveURL := u.ResolveReference(&url.URL{Path: rel + "project.zip"}).String()
files := make(map[string][]byte, len(s.Files)+1)
for name, data := range s.Files {
files[name] = data
}
if b.opt.ExamplesLicenseText != "" {
const licenseFile = "AEGO-EXAMPLE-LICENSE.txt"
for name := range files {
if strings.EqualFold(name, licenseFile) {
return fmt.Errorf("%s reserves %s", identity, licenseFile)
}
}
files[licenseFile] = []byte(b.opt.ExamplesLicenseText)
}
if _, exists := files[project.FileName]; !exists {
meta, err := asset.ParseSourceMeta(files[s.EntryScene+".meta"], s.EntryScene+".meta")
if err != nil {
return fmt.Errorf("%s: entry scene metadata: %w", identity, err)
}
p := project.Project{Name: e.Title, Module: "game", MainScene: meta.ID,
Window: project.WindowSettings{Width: 960, Height: 540, Title: e.Title}}
files[project.FileName] = p.Encode()
}
// Archive provenance does not grant a license that the source checkout
// did not contain. Snapshot builds state this uncertainty explicitly.
revision := b.opt.Commit
if revision == "" {
revision = "not provided"
}
notice := fmt.Sprintf("Aego documentation example: %s\nSource revision: %s\nDocumentation build: %s\nRegistry SHA-256: %s\nExample version: %s\nLicense: %s\nOrigin: %s\n", identity, revision, b.buildID, b.registrySHA, version, license, origin)
notice += "Documentation fingerprints identify the exported inputs, not a Git worktree or a runtime acceptance result.\n"
if license == "NOASSERTION" {
notice += "No license was declared in this source snapshot. This package does not add or change any permissions.\n"
}
if b.opt.ExamplesLicenseText != "" {
notice += "Reviewed license text: AEGO-EXAMPLE-LICENSE.txt\n"
}
if _, exists := files["EXAMPLE-NOTICE.txt"]; exists {
return fmt.Errorf("%s reserves EXAMPLE-NOTICE.txt", identity)
}
files["EXAMPLE-NOTICE.txt"] = []byte(notice)
m := remoteexample.Manifest{
Format: remoteexample.FormatVersion, ExampleID: name, Version: version,
Engine: remoteexample.Compatibility{Min: "0.12.1", MaxExclusive: "0.13.0"},
EntryScene: s.EntryScene, Modules: s.Modules, AssetRefs: s.AssetRefs,
Archive: remoteexample.Archive{URL: archiveURL}, Origin: origin, License: license,
}
m, _, archive, err := remoteexample.BuildPackage(m, files, manifestURL, remoteexample.Limits{})
if err != nil {
return fmt.Errorf("%s: %w", identity, err)
}
archiveRel := rel + "project-" + m.Archive.SHA256 + ".zip"
m.Archive.URL = u.ResolveReference(&url.URL{Path: archiveRel}).String()
manifest, err := json.MarshalIndent(m, "", " ")
if err != nil {
return err
}
manifest = append(manifest, '\n')
if _, err := remoteexample.ParseManifest(manifest, manifestURL, remoteexample.Limits{}); err != nil {
return err
}
link, err := remoteexample.MakeLink(manifestURL, "scene")
if err != nil {
return err
}
dir := filepath.Join(b.out, filepath.FromSlash(rel))
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
if err := os.WriteFile(filepath.Join(b.out, filepath.FromSlash(archiveRel)), archive, 0644); err != nil {
return err
}
if err := os.WriteFile(filepath.Join(dir, "manifest.json"), manifest, 0644); err != nil {
return err
}
b.examples[e.ID] = tryExample{Manifest: rel + "manifest.json", Archive: archiveRel, Link: link}
}
return nil
}