Files
修雨 8f4ab176a8 ci: add code admission gate (#53)
* ci: add code admission gate

* ci: fix fork release baseline

(cherry picked from commit 7ff2f3a5f0435209908822e141a6355fc6fa4aa6)
2026-07-16 10:26:18 +08:00

271 lines
8.0 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package interfacesnapshot captures and compares the public Cobra command
// surface without executing commands or contacting runtime services.
package interfacesnapshot
import (
"encoding/json"
"fmt"
"io"
"sort"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
const SchemaVersion = 1
var (
// Framework-owned command subtrees are deliberately excluded. They are
// generated by Cobra or shell-completion plumbing rather than DWS product
// code, so changes to them are not command-surface compatibility changes.
excludedCommandSubtrees = []string{
"dws __complete",
"dws __completeNoDesc",
"dws completion",
"dws help",
}
// Cobra installs --help lazily on every command. Keeping it would make the
// snapshot depend on whether help happened to be rendered before capture.
excludedFlags = map[string]bool{"help": true}
)
// Rules records the noise filtering contract in every snapshot. A future rule
// change is therefore visible instead of silently changing comparison scope.
type Rules struct {
ExcludedCommandSubtrees []string `json:"excluded_command_subtrees"`
ExcludedFlags []string `json:"excluded_flags"`
}
// Snapshot is a deterministic representation of a Cobra command tree.
type Snapshot struct {
SchemaVersion int `json:"schema_version"`
Rules Rules `json:"rules"`
Commands []Command `json:"commands"`
}
// Command contains compatibility-relevant command metadata. Path always
// includes the root command name (for example, "dws chat message send").
type Command struct {
Path string `json:"path"`
Runnable bool `json:"runnable"`
Hidden bool `json:"hidden"`
Deprecated string `json:"deprecated,omitempty"`
Aliases []string `json:"aliases"`
LocalFlags []Flag `json:"local_flags"`
InheritedFlags []Flag `json:"inherited_flags"`
}
// Flag contains the stable pflag contract visible at a command node.
type Flag struct {
Name string `json:"name"`
Shorthand string `json:"shorthand,omitempty"`
Type string `json:"type"`
Default string `json:"default"`
NoOpt string `json:"no_opt,omitempty"`
Required bool `json:"required"`
Hidden bool `json:"hidden"`
Deprecated string `json:"deprecated,omitempty"`
}
// Capture walks root without rendering help or executing any command. Both
// hidden compatibility commands and hidden flags are retained unless they are
// covered by the explicit framework-noise rules above.
func Capture(root *cobra.Command) Snapshot {
snapshot := Snapshot{
SchemaVersion: SchemaVersion,
Rules: Rules{
ExcludedCommandSubtrees: append([]string(nil), excludedCommandSubtrees...),
ExcludedFlags: []string{"help"},
},
Commands: []Command{},
}
if root == nil {
return snapshot
}
// --version is also installed lazily by Cobra, but unlike --help it is a
// real root CLI contract and should be captured.
root.InitDefaultVersionFlag()
var walk func(*cobra.Command)
walk = func(cmd *cobra.Command) {
path := normalizePath(cmd.CommandPath())
if commandExcluded(path) {
return
}
aliases := append([]string{}, cmd.Aliases...)
for i := range aliases {
aliases[i] = strings.TrimSpace(aliases[i])
}
aliases = compactSorted(aliases)
snapshot.Commands = append(snapshot.Commands, Command{
Path: path,
Runnable: cmd.Runnable(),
Hidden: cmd.Hidden,
Deprecated: strings.TrimSpace(cmd.Deprecated),
Aliases: aliases,
LocalFlags: captureFlags(cmd.LocalFlags()),
InheritedFlags: captureFlags(cmd.InheritedFlags()),
})
children := append([]*cobra.Command(nil), cmd.Commands()...)
sort.Slice(children, func(i, j int) bool {
return children[i].Name() < children[j].Name()
})
for _, child := range children {
walk(child)
}
}
walk(root)
sort.Slice(snapshot.Commands, func(i, j int) bool {
return snapshot.Commands[i].Path < snapshot.Commands[j].Path
})
return snapshot
}
// Write emits canonical, indented JSON with a trailing newline.
func Write(w io.Writer, snapshot Snapshot) error {
if err := snapshot.Validate(); err != nil {
return err
}
encoder := json.NewEncoder(w)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")
return encoder.Encode(snapshot)
}
// Read decodes and validates a snapshot. Unknown fields are rejected so a
// comparison never silently ignores a newer contract it does not understand.
func Read(r io.Reader) (Snapshot, error) {
var snapshot Snapshot
decoder := json.NewDecoder(r)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&snapshot); err != nil {
return Snapshot{}, err
}
if err := decoder.Decode(&struct{}{}); err != io.EOF {
if err == nil {
return Snapshot{}, fmt.Errorf("interface snapshot contains multiple JSON values")
}
return Snapshot{}, fmt.Errorf("read trailing interface snapshot data: %w", err)
}
if err := snapshot.Validate(); err != nil {
return Snapshot{}, err
}
return snapshot, nil
}
// Validate checks the invariants needed by the comparison algorithm.
func (s Snapshot) Validate() error {
if s.SchemaVersion != SchemaVersion {
return fmt.Errorf("unsupported interface snapshot schema_version %d (want %d)", s.SchemaVersion, SchemaVersion)
}
seenCommands := make(map[string]bool, len(s.Commands))
for _, command := range s.Commands {
if command.Path == "" {
return fmt.Errorf("interface snapshot contains an empty command path")
}
if seenCommands[command.Path] {
return fmt.Errorf("interface snapshot contains duplicate command path %q", command.Path)
}
seenCommands[command.Path] = true
if err := validateFlags(command.Path, "local", command.LocalFlags); err != nil {
return err
}
if err := validateFlags(command.Path, "inherited", command.InheritedFlags); err != nil {
return err
}
}
return nil
}
func captureFlags(set *pflag.FlagSet) []Flag {
flags := []Flag{}
if set == nil {
return flags
}
set.VisitAll(func(flag *pflag.Flag) {
if flag == nil || excludedFlags[flag.Name] {
return
}
flags = append(flags, Flag{
Name: flag.Name,
Shorthand: flag.Shorthand,
Type: flag.Value.Type(),
Default: flag.DefValue,
NoOpt: flag.NoOptDefVal,
Required: isRequired(flag),
Hidden: flag.Hidden,
Deprecated: strings.TrimSpace(flag.Deprecated),
})
})
sort.Slice(flags, func(i, j int) bool { return flags[i].Name < flags[j].Name })
return flags
}
func isRequired(flag *pflag.Flag) bool {
for _, value := range flag.Annotations[cobra.BashCompOneRequiredFlag] {
if value == "true" {
return true
}
}
return false
}
func commandExcluded(path string) bool {
for _, prefix := range excludedCommandSubtrees {
if path == prefix || strings.HasPrefix(path, prefix+" ") {
return true
}
}
return false
}
func normalizePath(path string) string {
return strings.Join(strings.Fields(path), " ")
}
func compactSorted(values []string) []string {
sort.Strings(values)
out := values[:0]
for _, value := range values {
if value == "" || (len(out) > 0 && out[len(out)-1] == value) {
continue
}
out = append(out, value)
}
return out
}
func validateFlags(path, scope string, flags []Flag) error {
seen := make(map[string]bool, len(flags))
for _, flag := range flags {
if flag.Name == "" {
return fmt.Errorf("command %q contains an empty %s flag name", path, scope)
}
if seen[flag.Name] {
return fmt.Errorf("command %q contains duplicate %s flag %q", path, scope, flag.Name)
}
seen[flag.Name] = true
}
return nil
}