Files
wehub-resource-sync bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:22:54 +08:00

234 lines
7.0 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package manifest
import (
"fmt"
"strings"
)
type Source string
const (
SourceBuiltin Source = "builtin"
SourceShortcut Source = "shortcut"
SourceService Source = "service"
)
type Manifest struct {
SchemaVersion int `json:"schema_version"`
Commands []Command `json:"commands"`
}
type Command struct {
Path string `json:"path"`
CanonicalPath string `json:"canonical_path,omitempty"`
Domain string `json:"domain,omitempty"`
Use string `json:"use"`
Short string `json:"short,omitempty"`
Example string `json:"example,omitempty"`
Hidden bool `json:"hidden,omitempty"`
Runnable bool `json:"runnable"`
Source Source `json:"source"`
Generated bool `json:"generated,omitempty"`
Risk string `json:"risk,omitempty"`
Identities []string `json:"identities,omitempty"`
Flags []Flag `json:"flags,omitempty"`
DefaultFields []string `json:"default_fields,omitempty"`
}
type Flag struct {
Name string `json:"name"`
Shorthand string `json:"shorthand,omitempty"`
Usage string `json:"usage,omitempty"`
Hidden bool `json:"hidden,omitempty"`
Required bool `json:"required,omitempty"`
TakesValue bool `json:"takes_value"`
DefValue string `json:"default,omitempty"`
NoOptValue string `json:"no_opt_value,omitempty"`
Annotations map[string][]string `json:"annotations,omitempty"`
}
const (
KindCommandManifest = "command-manifest"
KindCommandIndex = "command-index"
MaxManifestBytes = 16 * 1024 * 1024
MaxCommandsPerManifest = 10000
MaxFlagsPerCommand = 200
MaxManifestStringBytes = 8192
MaxAnnotationValues = 100
MaxAnnotationValueBytes = 8192
)
func (m Manifest) Validate(kind string) error {
if kind != KindCommandManifest && kind != KindCommandIndex {
return fmt.Errorf("unknown manifest kind %q", kind)
}
if m.SchemaVersion != 1 {
return fmt.Errorf("%s schema_version must be 1", kind)
}
if len(m.Commands) == 0 {
return fmt.Errorf("%s must contain at least one command", kind)
}
if len(m.Commands) > MaxCommandsPerManifest {
return fmt.Errorf("%s has too many commands: %d", kind, len(m.Commands))
}
seenCommands := make(map[string]struct{}, len(m.Commands))
hasService := false
for i, cmd := range m.Commands {
if err := validateCommand(kind, i, cmd); err != nil {
return err
}
if _, ok := seenCommands[cmd.Path]; ok {
return fmt.Errorf("%s command path is duplicated: %s", kind, cmd.Path)
}
seenCommands[cmd.Path] = struct{}{}
if cmd.Source == SourceService {
hasService = true
}
}
switch kind {
case KindCommandManifest:
if hasService {
return fmt.Errorf("%s must not contain generated service commands", kind)
}
case KindCommandIndex:
if !hasService {
return fmt.Errorf("%s must contain service commands", kind)
}
}
return nil
}
func validateCommand(kind string, i int, cmd Command) error {
prefix := fmt.Sprintf("%s commands[%d]", kind, i)
if err := validateString(prefix+".path", cmd.Path, true); err != nil {
return err
}
if err := validateString(prefix+".canonical_path", cmd.CanonicalPath, false); err != nil {
return err
}
if cmd.CanonicalPath != "" && cmd.CanonicalPath != CanonicalCommandPath(cmd.Path) {
return fmt.Errorf("%s.canonical_path = %q, want %q", prefix, cmd.CanonicalPath, CanonicalCommandPath(cmd.Path))
}
if err := validateString(prefix+".domain", cmd.Domain, false); err != nil {
return err
}
if err := validateString(prefix+".use", cmd.Use, false); err != nil {
return err
}
if err := validateString(prefix+".short", cmd.Short, false); err != nil {
return err
}
if err := validateString(prefix+".example", cmd.Example, false); err != nil {
return err
}
if err := validateString(prefix+".risk", cmd.Risk, false); err != nil {
return err
}
switch cmd.Source {
case SourceBuiltin, SourceShortcut, SourceService:
default:
return fmt.Errorf("%s.source is invalid: %q", prefix, cmd.Source)
}
if cmd.Source == SourceService && !cmd.Generated {
return fmt.Errorf("%s.generated must be true for service commands", prefix)
}
if cmd.Generated && cmd.Source != SourceService {
return fmt.Errorf("%s.generated can only be true for service commands", prefix)
}
if len(cmd.Flags) > MaxFlagsPerCommand {
return fmt.Errorf("%s has too many flags: %d", prefix, len(cmd.Flags))
}
for j, identity := range cmd.Identities {
if err := validateString(fmt.Sprintf("%s.identities[%d]", prefix, j), identity, true); err != nil {
return err
}
}
for j, field := range cmd.DefaultFields {
if err := validateString(fmt.Sprintf("%s.default_fields[%d]", prefix, j), field, true); err != nil {
return err
}
}
seenFlags := make(map[string]struct{}, len(cmd.Flags))
for j, flag := range cmd.Flags {
if err := validateFlag(prefix, j, flag); err != nil {
return err
}
if _, ok := seenFlags[flag.Name]; ok {
return fmt.Errorf("%s flags[%d].name is duplicated: %s", prefix, j, flag.Name)
}
seenFlags[flag.Name] = struct{}{}
}
return nil
}
func validateFlag(commandPrefix string, i int, flag Flag) error {
prefix := fmt.Sprintf("%s.flags[%d]", commandPrefix, i)
if err := validateString(prefix+".name", flag.Name, true); err != nil {
return err
}
if strings.ContainsAny(flag.Name, " \t\r\n") {
return fmt.Errorf("%s.name must not contain whitespace", prefix)
}
for _, item := range []struct {
name string
value string
}{
{name: "shorthand", value: flag.Shorthand},
{name: "usage", value: flag.Usage},
{name: "default", value: flag.DefValue},
{name: "no_opt_value", value: flag.NoOptValue},
} {
if err := validateString(prefix+"."+item.name, item.value, false); err != nil {
return err
}
}
for key, values := range flag.Annotations {
if err := validateString(prefix+".annotations key", key, true); err != nil {
return err
}
if len(values) > MaxAnnotationValues {
return fmt.Errorf("%s.annotations[%q] has too many values: %d", prefix, key, len(values))
}
for j, value := range values {
if value == "" {
return fmt.Errorf("%s.annotations[%q][%d] must not be empty", prefix, key, j)
}
if len(value) > MaxAnnotationValueBytes {
return fmt.Errorf("%s.annotations[%q][%d] is too large", prefix, key, j)
}
}
}
return nil
}
func validateString(label, value string, required bool) error {
if required && value == "" {
return fmt.Errorf("%s must not be empty", label)
}
if len(value) > MaxManifestStringBytes {
return fmt.Errorf("%s is too large", label)
}
return nil
}
func CanonicalCommandPath(path string) string {
parts := strings.Fields(path)
for i, part := range parts {
prefix := ""
if strings.HasPrefix(part, "+") {
prefix = "+"
part = strings.TrimPrefix(part, "+")
}
part = strings.ReplaceAll(part, ".", "-")
part = strings.ReplaceAll(part, "_", "-")
parts[i] = prefix + part
}
return strings.Join(parts, " ")
}