autogen activation
This commit is contained in:
@@ -365,6 +365,7 @@ type AutogenConsumerConfig struct {
|
|||||||
HeadVisualeffects HeadVisualeffectsConfig `json:"accessory_visualeffects" yaml:"accessory_visualeffects"`
|
HeadVisualeffects HeadVisualeffectsConfig `json:"accessory_visualeffects" yaml:"accessory_visualeffects"`
|
||||||
Manifest AutogenManifestConfig `json:"manifest" yaml:"manifest"`
|
Manifest AutogenManifestConfig `json:"manifest" yaml:"manifest"`
|
||||||
LocalOverrideRoot string `json:"local_override_root,omitempty" yaml:"local_override_root,omitempty"`
|
LocalOverrideRoot string `json:"local_override_root,omitempty" yaml:"local_override_root,omitempty"`
|
||||||
|
ManifestFile string `json:"manifest_file,omitempty" yaml:"manifest_file,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeadVisualeffectsConfig struct {
|
type HeadVisualeffectsConfig struct {
|
||||||
|
|||||||
@@ -225,6 +225,9 @@ func autogenConsumerTargetsDataset(dataset nativeCollectedDataset, consumer proj
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolveAutogenConsumerManifest(p *project.Project, consumer project.AutogenConsumerConfig, progress func(string)) (*autogenManifest, error) {
|
func resolveAutogenConsumerManifest(p *project.Project, consumer project.AutogenConsumerConfig, progress func(string)) (*autogenManifest, error) {
|
||||||
|
if manifestFile := strings.TrimSpace(consumer.ManifestFile); manifestFile != "" {
|
||||||
|
return readAutogenConsumerManifestFile(p, consumer, manifestFile, progress)
|
||||||
|
}
|
||||||
overrideRoot, err := resolveAutogenLocalOverrideRoot(p, consumer)
|
overrideRoot, err := resolveAutogenLocalOverrideRoot(p, consumer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -242,6 +245,37 @@ func resolveAutogenConsumerManifest(p *project.Project, consumer project.Autogen
|
|||||||
return resolveReleasedAutogenManifest(p, consumer, progress)
|
return resolveReleasedAutogenManifest(p, consumer, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readAutogenConsumerManifestFile reads a pre-resolved autogenManifest JSON from
|
||||||
|
// a local file (relative paths resolve against the project root). A missing file
|
||||||
|
// is an ignorable-optional condition — it returns errAutogenManifestUnavailable
|
||||||
|
// so an optional consumer fails open and preserves its pinned lock IDs. A present
|
||||||
|
// but malformed file, or an id that disagrees with the consumer's producer, is a
|
||||||
|
// hard error.
|
||||||
|
func readAutogenConsumerManifestFile(p *project.Project, consumer project.AutogenConsumerConfig, manifestFile string, progress func(string)) (*autogenManifest, error) {
|
||||||
|
path := manifestFile
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
path = filepath.Join(p.Root, path)
|
||||||
|
}
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, unavailableAutogenManifest(fmt.Errorf("manifest file %s not found", path))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("read autogen manifest file %s: %w", path, err)
|
||||||
|
}
|
||||||
|
var manifest autogenManifest
|
||||||
|
if err := json.Unmarshal(raw, &manifest); err != nil {
|
||||||
|
return nil, fmt.Errorf("parse autogen manifest file %s: %w", path, err)
|
||||||
|
}
|
||||||
|
if manifest.ID != "" && manifest.ID != consumer.Producer {
|
||||||
|
return nil, fmt.Errorf("autogen manifest file %s declared id %q, expected %q", path, manifest.ID, consumer.Producer)
|
||||||
|
}
|
||||||
|
if progress != nil {
|
||||||
|
progress(fmt.Sprintf("Reading local autogen manifest for %s from %s...", consumer.ID, path))
|
||||||
|
}
|
||||||
|
return &manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
func resolveAutogenLocalOverrideRoot(p *project.Project, consumer project.AutogenConsumerConfig) (string, error) {
|
func resolveAutogenLocalOverrideRoot(p *project.Project, consumer project.AutogenConsumerConfig) (string, error) {
|
||||||
root := strings.TrimSpace(consumer.LocalOverrideRoot)
|
root := strings.TrimSpace(consumer.LocalOverrideRoot)
|
||||||
if root != "" {
|
if root != "" {
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package topdata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/project"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The manifest_file branch reads a local autogenManifest JSON, augments the
|
||||||
|
// visualeffects dataset from its entries, and assigns lock IDs — no network.
|
||||||
|
func TestApplyAutogenConsumersReadsManifestFile(t *testing.T) {
|
||||||
|
root := testProjectRoot(t)
|
||||||
|
manifestPath := filepath.Join(root, ".cache", "sow-accessory-vfx-manifest.json")
|
||||||
|
mkdirAll(t, filepath.Join(root, ".cache"))
|
||||||
|
writeFile(t, manifestPath, `{
|
||||||
|
"id": "accessory_visualeffects",
|
||||||
|
"repo": "ShadowsOverWestgate/sow-assets-manifest",
|
||||||
|
"ref": "v1.2.3",
|
||||||
|
"generated_at": "2026-06-21T00:00:00Z",
|
||||||
|
"entries": [
|
||||||
|
{"source": "head_accessories/hat/hfx_bandana.mdl", "model_stem": "hfx_bandana", "group": "head_accessories", "subgroup": "hat"}
|
||||||
|
]
|
||||||
|
}`+"\n")
|
||||||
|
|
||||||
|
p := testProject(root)
|
||||||
|
p.Config.Autogen.Consumers = []project.AutogenConsumerConfig{
|
||||||
|
{
|
||||||
|
ID: "accessory_visualeffects",
|
||||||
|
Producer: "accessory_visualeffects",
|
||||||
|
Dataset: "visualeffects",
|
||||||
|
Mode: "accessory_visualeffects",
|
||||||
|
Optional: true,
|
||||||
|
ManifestFile: ".cache/sow-accessory-vfx-manifest.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
collected := []nativeCollectedDataset{
|
||||||
|
{
|
||||||
|
Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase},
|
||||||
|
Columns: []string{"Label", "Type_FD", "OrientWithGround", "Imp_HeadCon_Node", "OrientWithObject"},
|
||||||
|
Rows: nil,
|
||||||
|
LockData: map[string]int{
|
||||||
|
"visualeffects:existing": 17,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := applyAutogenConsumers(p, collected, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("applyAutogenConsumers failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(got) != 1 || len(got[0].Rows) != 1 {
|
||||||
|
t.Fatalf("expected one autogenerated row, got %#v", got)
|
||||||
|
}
|
||||||
|
row := got[0].Rows[0]
|
||||||
|
if row["key"] != "visualeffects:head_accessories/hat/bandana" || row["Imp_HeadCon_Node"] != "hfx_bandana" {
|
||||||
|
t.Fatalf("unexpected generated row: %#v", row)
|
||||||
|
}
|
||||||
|
if _, ok := got[0].LockData["visualeffects:head_accessories/hat/bandana"]; !ok {
|
||||||
|
t.Fatalf("expected lock id for generated accessory, got %#v", got[0].LockData)
|
||||||
|
}
|
||||||
|
// Authored key untouched.
|
||||||
|
if got[0].LockData["visualeffects:existing"] != 17 {
|
||||||
|
t.Fatalf("expected authored lock id retained at 17, got %#v", got[0].LockData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A missing manifest_file is an ignorable-optional condition: the optional
|
||||||
|
// consumer fails open (no rows) and preserves existing lock IDs.
|
||||||
|
func TestApplyAutogenConsumersMissingManifestFileFailsOpen(t *testing.T) {
|
||||||
|
root := testProjectRoot(t)
|
||||||
|
lockPath := filepath.Join(root, "visualeffects-lock.json")
|
||||||
|
writeFile(t, lockPath, `{"visualeffects:head_accessories/hat/bandana":10101}`+"\n")
|
||||||
|
|
||||||
|
p := testProject(root)
|
||||||
|
p.Config.Autogen.Consumers = []project.AutogenConsumerConfig{
|
||||||
|
{
|
||||||
|
ID: "accessory_visualeffects",
|
||||||
|
Producer: "accessory_visualeffects",
|
||||||
|
Dataset: "visualeffects",
|
||||||
|
Mode: "accessory_visualeffects",
|
||||||
|
Optional: true,
|
||||||
|
ManifestFile: ".cache/does-not-exist.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
collected := []nativeCollectedDataset{
|
||||||
|
{
|
||||||
|
Dataset: nativeDataset{Name: "visualeffects", Kind: nativeDatasetBase, LockPath: lockPath},
|
||||||
|
Columns: []string{"Label"},
|
||||||
|
Rows: nil,
|
||||||
|
LockData: map[string]int{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := applyAutogenConsumers(p, collected, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected fail-open, got error: %v", err)
|
||||||
|
}
|
||||||
|
if id, ok := got[0].LockData["visualeffects:head_accessories/hat/bandana"]; !ok || id != 10101 {
|
||||||
|
t.Fatalf("expected preserved lock id 10101, got %v (present=%v)", id, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user