Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
106 lines
2.9 KiB
Go
106 lines
2.9 KiB
Go
package runtimeprep
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func mergeYAMLStringList(config string, keyPath []string, values []string) (string, error) {
|
|
dirs := dedupeStringList(values)
|
|
if len(dirs) == 0 {
|
|
return config, nil
|
|
}
|
|
if !slices.Equal(keyPath, []string{"skills", "external_dirs"}) &&
|
|
!slices.Equal(keyPath, []string{"plugins", "enabled"}) {
|
|
return "", errors.New("extension runtime YAML list key is unsupported")
|
|
}
|
|
|
|
var doc yaml.Node
|
|
if strings.TrimSpace(config) == "" {
|
|
doc = yaml.Node{Kind: yaml.DocumentNode, Content: []*yaml.Node{{Kind: yaml.MappingNode}}}
|
|
} else if err := yaml.Unmarshal([]byte(config), &doc); err != nil {
|
|
return "", fmt.Errorf("parse extension runtime YAML config: %w", err)
|
|
}
|
|
root := yamlDocumentRoot(&doc)
|
|
if root.Kind != yaml.MappingNode {
|
|
return "", errors.New("extension runtime YAML config must be a mapping")
|
|
}
|
|
sectionName := keyPath[0]
|
|
listName := keyPath[1]
|
|
section := yamlMappingValue(root, sectionName)
|
|
if section == nil {
|
|
section = &yaml.Node{Kind: yaml.MappingNode}
|
|
yamlSetMappingValue(root, sectionName, section)
|
|
}
|
|
if section.Kind != yaml.MappingNode {
|
|
return "", fmt.Errorf("extension runtime YAML %s must be a mapping", sectionName)
|
|
}
|
|
list := yamlMappingValue(section, listName)
|
|
if list == nil {
|
|
list = &yaml.Node{Kind: yaml.SequenceNode}
|
|
yamlSetMappingValue(section, listName, list)
|
|
}
|
|
if list.Kind != yaml.SequenceNode {
|
|
return "", fmt.Errorf("extension runtime YAML %s.%s must be a list", sectionName, listName)
|
|
}
|
|
existing := yamlStringSequenceValues(list)
|
|
for _, dir := range dirs {
|
|
if slices.Contains(existing, dir) {
|
|
continue
|
|
}
|
|
list.Content = append(list.Content, &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: dir})
|
|
existing = append(existing, dir)
|
|
}
|
|
out, err := yaml.Marshal(&doc)
|
|
if err != nil {
|
|
return "", fmt.Errorf("write extension runtime YAML config: %w", err)
|
|
}
|
|
return string(out), nil
|
|
}
|
|
|
|
func dedupeStringList(paths []string) []string {
|
|
result := make([]string, 0, len(paths))
|
|
for _, path := range paths {
|
|
result = appendUniquePath(result, path)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func yamlDocumentRoot(doc *yaml.Node) *yaml.Node {
|
|
if doc.Kind == yaml.DocumentNode && len(doc.Content) > 0 {
|
|
return doc.Content[0]
|
|
}
|
|
return doc
|
|
}
|
|
|
|
func yamlMappingValue(mapping *yaml.Node, key string) *yaml.Node {
|
|
for index := 0; index+1 < len(mapping.Content); index += 2 {
|
|
if mapping.Content[index].Value == key {
|
|
return mapping.Content[index+1]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func yamlSetMappingValue(mapping *yaml.Node, key string, value *yaml.Node) {
|
|
mapping.Content = append(mapping.Content,
|
|
&yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: key},
|
|
value,
|
|
)
|
|
}
|
|
|
|
func yamlStringSequenceValues(sequence *yaml.Node) []string {
|
|
result := []string{}
|
|
for _, item := range sequence.Content {
|
|
if item.Kind != yaml.ScalarNode || item.Tag != "!!str" {
|
|
continue
|
|
}
|
|
result = appendUniquePath(result, item.Value)
|
|
}
|
|
return result
|
|
}
|