From f6e21105182354a747599e602aa4c539e9f9640a Mon Sep 17 00:00:00 2001 From: vickydotbat Date: Sun, 21 Jun 2026 14:30:16 +0200 Subject: [PATCH] feat(project): add cdn_channel AutogenSourceConfig + validation --- internal/project/project.go | 36 ++++++++++++++++++++++++++++++++ internal/project/project_test.go | 25 ++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/internal/project/project.go b/internal/project/project.go index 9aaccec..8ad602a 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -366,6 +366,7 @@ type AutogenConsumerConfig struct { Manifest AutogenManifestConfig `json:"manifest" yaml:"manifest"` LocalOverrideRoot string `json:"local_override_root,omitempty" yaml:"local_override_root,omitempty"` ManifestFile string `json:"manifest_file,omitempty" yaml:"manifest_file,omitempty"` + Source AutogenSourceConfig `json:"source,omitempty" yaml:"source,omitempty"` } type AccessoryVisualeffectsConfig struct { @@ -416,6 +417,17 @@ type AutogenDeriveConfig struct { GroupFrom string `json:"group_from,omitempty" yaml:"group_from,omitempty"` } +type AutogenSourceConfig struct { + Kind string `json:"kind,omitempty" yaml:"kind,omitempty"` + CDNBase string `json:"cdn_base,omitempty" yaml:"cdn_base,omitempty"` + CDNBaseEnv string `json:"cdn_base_env,omitempty" yaml:"cdn_base_env,omitempty"` + ChannelsPath string `json:"channels_path,omitempty" yaml:"channels_path,omitempty"` + ManifestPath string `json:"manifest_path,omitempty" yaml:"manifest_path,omitempty"` + ReleaseMarkerPath string `json:"release_marker_path,omitempty" yaml:"release_marker_path,omitempty"` + ChannelEnv string `json:"channel_env,omitempty" yaml:"channel_env,omitempty"` + OfflineOverrideEnv string `json:"offline_override_env,omitempty" yaml:"offline_override_env,omitempty"` +} + type AutogenManifestConfig struct { ReleaseTag string `json:"release_tag" yaml:"release_tag"` AssetName string `json:"asset_name" yaml:"asset_name"` @@ -641,6 +653,7 @@ func (p *Project) ValidateLayout() error { failures = append(failures, validateTopDataRowGeneration(effective.TopData.RowGeneration)...) failures = append(failures, validateTopDataRowExtensions(effective.TopData.RowExtensions)...) failures = append(failures, validateTopDataClassFeatInjections(effective.TopData.ClassFeatInjections)...) + failures = append(failures, validateAutogenConsumerSources(effective.Autogen.Consumers)...) failures = append(failures, validateRelativePath("topdata.compiled_2da_dir", effective.TopData.Compiled2DADir)...) failures = append(failures, validateRelativePath("topdata.compiled_tlk", effective.TopData.CompiledTLK)...) failures = append(failures, validateRelativePath("topdata.wiki.output_root", effective.TopData.Wiki.OutputRoot)...) @@ -1688,6 +1701,29 @@ func validateAutogenConfig(cfg AutogenConfig) []error { return failures } +func validateAutogenConsumerSources(consumers []AutogenConsumerConfig) []error { + var failures []error + for _, c := range consumers { + if strings.TrimSpace(c.Source.Kind) != "cdn_channel" { + continue + } + label := strings.TrimSpace(c.ID) + if label == "" { + label = "" + } + if strings.TrimSpace(c.Source.ChannelsPath) == "" { + failures = append(failures, fmt.Errorf("autogen consumer %s: source.channels_path is required for kind cdn_channel", label)) + } + manifestPath := strings.TrimSpace(c.Source.ManifestPath) + if manifestPath == "" { + failures = append(failures, fmt.Errorf("autogen consumer %s: source.manifest_path is required for kind cdn_channel", label)) + } else if !strings.Contains(manifestPath, "{tag}") { + failures = append(failures, fmt.Errorf("autogen consumer %s: source.manifest_path must contain {tag}", label)) + } + } + return failures +} + func validateGeneratedConfig(cfg GeneratedConfig) []error { var failures []error seen := map[string]struct{}{} diff --git a/internal/project/project_test.go b/internal/project/project_test.go index ca1b8f9..73f40a4 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -1796,6 +1796,31 @@ func TestCloneWithHAKNamesRejectsUnknownHAKs(t *testing.T) { } } +func TestValidateAutogenConsumerSourcesCDNChannel(t *testing.T) { + cases := []struct { + name string + source AutogenSourceConfig + wantFail bool + }{ + {"valid", AutogenSourceConfig{Kind: "cdn_channel", ChannelsPath: "releases/haks/channels.json", ManifestPath: "releases/haks/{tag}/vfxs.yml"}, false}, + {"missing channels_path", AutogenSourceConfig{Kind: "cdn_channel", ManifestPath: "releases/haks/{tag}/vfxs.yml"}, true}, + {"missing manifest_path", AutogenSourceConfig{Kind: "cdn_channel", ChannelsPath: "releases/haks/channels.json"}, true}, + {"manifest_path lacks {tag}", AutogenSourceConfig{Kind: "cdn_channel", ChannelsPath: "releases/haks/channels.json", ManifestPath: "releases/haks/vfxs.yml"}, true}, + {"empty kind is ignored", AutogenSourceConfig{}, false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + failures := validateAutogenConsumerSources([]AutogenConsumerConfig{{ID: "x", Source: tc.source}}) + if tc.wantFail && len(failures) == 0 { + t.Fatalf("expected validation failure, got none") + } + if !tc.wantFail && len(failures) != 0 { + t.Fatalf("expected no failure, got %v", failures) + } + }) + } +} + func loadAndValidate(root string) error { proj, err := Load(root) if err != nil {