package manifest import ( "fmt" "os" "strings" "github.com/goccy/go-yaml" ) type GitSource struct { Type string `yaml:"type"` URL string `yaml:"url"` Branch string `yaml:"branch"` } type PostInstall struct { Shell string `yaml:"shell"` Script string `yaml:"script"` ShellAlt string `yaml:"shell_alt"` ScriptAlt string `yaml:"script_alt"` } type HealthCheck struct { Type string `yaml:"type"` Path string `yaml:"path"` Tool string `yaml:"tool"` Command string `yaml:"command"` } type Component struct { Name string `yaml:"name"` DisplayName string `yaml:"display_name"` Source *GitSource `yaml:"source"` Target string `yaml:"target"` Deps []string `yaml:"deps"` SetupSkill string `yaml:"setup_skill"` PostInstall *PostInstall `yaml:"post_install"` HealthCheck *HealthCheck `yaml:"health_check"` Optional bool `yaml:"optional"` Clients []string `yaml:"clients"` } type Manifest struct { Version string `yaml:"version"` GitHost string `yaml:"git_host"` GitOrg string `yaml:"git_org"` Components []Component `yaml:"components"` } func Load(path string, vars map[string]string) (*Manifest, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read manifest: %w", err) } // Substitute variables content := string(data) for k, v := range vars { content = strings.ReplaceAll(content, "{"+k+"}", v) } var m Manifest if err := yaml.Unmarshal([]byte(content), &m); err != nil { return nil, fmt.Errorf("failed to parse manifest: %w", err) } // Set defaults if m.GitHost == "" { m.GitHost = "https://github.com" } return &m, nil } func (m *Manifest) GetComponent(name string) (*Component, bool) { for i := range m.Components { if m.Components[i].Name == name { return &m.Components[i], true } } return nil, false } func (m *Manifest) GetDependencyOrder() []string { // Topological sort for component installation order visited := make(map[string]bool) order := []string{} var visit func(string) visit = func(name string) { if visited[name] { return } visited[name] = true c, ok := m.GetComponent(name) if ok { for _, dep := range c.Deps { visit(dep) } } order = append(order, name) } for _, c := range m.Components { visit(c.Name) } return order } func (c *Component) IsApplicable(client string) bool { if len(c.Clients) == 0 { return true } for _, cl := range c.Clients { if cl == client { return true } } return false }