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
148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
package managedruntime
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
func validateManagedAppRuntimeCatalogEntry(platform string, entry appRuntimeCatalogEntry) error {
|
|
if strings.TrimSpace(platform) == "" {
|
|
return fmt.Errorf("managed app runtime catalog contains an empty platform")
|
|
}
|
|
if strings.TrimSpace(entry.Version) == "" {
|
|
return fmt.Errorf("managed app runtime catalog platform %q version is required", platform)
|
|
}
|
|
if len(entry.Components) == 0 {
|
|
return fmt.Errorf("managed app runtime catalog platform %q has no components", platform)
|
|
}
|
|
for name, component := range entry.Components {
|
|
if strings.TrimSpace(name) == "" {
|
|
return fmt.Errorf("managed app runtime catalog platform %q contains an empty component", platform)
|
|
}
|
|
if err := validateManagedAppRuntimeCatalogComponent(platform, name, component); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := appRuntimeProfileComponentNames(entry, appRuntimeBaselineProfile); err != nil {
|
|
return fmt.Errorf("managed app runtime catalog platform %q: %w", platform, err)
|
|
}
|
|
for profileName := range entry.Profiles {
|
|
if _, err := appRuntimeProfileComponentNames(entry, profileName); err != nil {
|
|
return fmt.Errorf("managed app runtime catalog platform %q: %w", platform, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func appRuntimeProfileComponentNames(entry appRuntimeCatalogEntry, profile string) ([]string, error) {
|
|
profile = strings.TrimSpace(profile)
|
|
if profile == "" {
|
|
return nil, fmt.Errorf("managed app runtime profile is required")
|
|
}
|
|
components, ok := entry.Profiles[profile]
|
|
if !ok || len(components) == 0 {
|
|
if profile == appRuntimeNodeStaticProfile {
|
|
if _, ok := entry.Components["node"]; ok {
|
|
return []string{"node"}, nil
|
|
}
|
|
}
|
|
if profile != appRuntimeBaselineProfile {
|
|
return nil, fmt.Errorf("managed app runtime profile %q is required", profile)
|
|
}
|
|
return nil, fmt.Errorf("managed app runtime baseline profile is required")
|
|
}
|
|
seen := map[string]struct{}{}
|
|
names := make([]string, 0, len(components))
|
|
for _, componentName := range components {
|
|
name := strings.TrimSpace(componentName)
|
|
if name == "" {
|
|
return nil, fmt.Errorf("managed app runtime profile %q contains an empty component", profile)
|
|
}
|
|
if _, ok := seen[name]; ok {
|
|
return nil, fmt.Errorf("managed app runtime profile %q contains duplicate component %q", profile, name)
|
|
}
|
|
seen[name] = struct{}{}
|
|
if _, ok := entry.Components[name]; !ok {
|
|
return nil, fmt.Errorf("managed app runtime profile %q references missing component %q", profile, name)
|
|
}
|
|
names = append(names, name)
|
|
}
|
|
return names, nil
|
|
}
|
|
|
|
func validateManagedAppRuntimeCatalogComponent(platform string, name string, component appRuntimeCatalogComponent) error {
|
|
if strings.TrimSpace(component.Version) == "" {
|
|
return fmt.Errorf("managed app runtime catalog platform %q component %q version is required", platform, name)
|
|
}
|
|
if strings.TrimSpace(component.ArtifactURL) == "" || strings.TrimSpace(component.ArtifactSHA256) == "" {
|
|
return fmt.Errorf("managed app runtime catalog platform %q component %q artifact url and sha256 are required", platform, name)
|
|
}
|
|
if !isSHA256Hex(component.ArtifactSHA256) {
|
|
return fmt.Errorf("managed app runtime catalog platform %q component %q artifact sha256 is invalid", platform, name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isSHA256Hex(value string) bool {
|
|
trimmed := strings.TrimSpace(value)
|
|
if len(trimmed) != 64 {
|
|
return false
|
|
}
|
|
for _, char := range trimmed {
|
|
if !unicode.IsDigit(char) && (char < 'a' || char > 'f') && (char < 'A' || char > 'F') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func safeAppRuntimeComponentName(value string) string {
|
|
var builder strings.Builder
|
|
for _, char := range strings.TrimSpace(value) {
|
|
switch {
|
|
case unicode.IsLetter(char), unicode.IsDigit(char), char == '-', char == '_':
|
|
builder.WriteRune(char)
|
|
default:
|
|
builder.WriteByte('-')
|
|
}
|
|
}
|
|
if builder.Len() == 0 {
|
|
return "component"
|
|
}
|
|
return builder.String()
|
|
}
|
|
|
|
func envValue(env []string, key string) string {
|
|
for i := len(env) - 1; i >= 0; i-- {
|
|
candidateKey, value, ok := strings.Cut(env[i], "=")
|
|
if ok && strings.EqualFold(candidateKey, key) {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func mergeAppPathDirs(dirs []string) []string {
|
|
result := make([]string, 0, len(dirs))
|
|
seen := map[string]struct{}{}
|
|
for _, dir := range dirs {
|
|
trimmed := strings.TrimSpace(dir)
|
|
if trimmed == "" {
|
|
continue
|
|
}
|
|
key := filepath.Clean(trimmed)
|
|
if runtime.GOOS == "windows" {
|
|
key = strings.ToLower(key)
|
|
}
|
|
if _, ok := seen[key]; ok {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
result = append(result, trimmed)
|
|
}
|
|
return result
|
|
}
|