// 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. // interface-snapshot is an internal CI helper. It is intentionally a separate // binary so it can be copied into a temporary worktree and compiled against an // older revision's real Cobra root. package main import ( "bytes" "encoding/json" "flag" "fmt" "io" "os" "path/filepath" "strings" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/i18n" "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/interfacesnapshot" "github.com/spf13/cobra" ) var newRootCommand = func() *cobra.Command { return app.NewRootCommand() } func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) } func run(args []string, stdout, stderr io.Writer) int { if len(args) == 0 { printUsage(stderr) return 2 } switch args[0] { case "generate": if err := runGenerate(args[1:], stdout, stderr); err != nil { fmt.Fprintln(stderr, err) return 2 } return 0 case "compare": compatible, err := runCompare(args[1:], stdout, stderr) if err != nil { fmt.Fprintln(stderr, err) return 2 } if !compatible { return 1 } return 0 default: fmt.Fprintf(stderr, "unknown command %q\n", args[0]) printUsage(stderr) return 2 } } func runGenerate(args []string, stdout, stderr io.Writer) error { flags := flag.NewFlagSet("generate", flag.ContinueOnError) flags.SetOutput(stderr) output := flags.String("output", "-", "snapshot output path, or - for stdout") if err := flags.Parse(args); err != nil { return err } if flags.NArg() != 0 { return fmt.Errorf("generate accepts no positional arguments") } home, err := os.MkdirTemp("", "dws-interface-snapshot-*") if err != nil { return fmt.Errorf("create isolated home: %w", err) } defer os.RemoveAll(home) environment := map[string]string{ "DWS_CONFIG_DIR": home, "DWS_LANG": "en", "HOME": home, "NO_COLOR": "1", "USERPROFILE": home, } type previousEnv struct { value string set bool } previous := make(map[string]previousEnv, len(environment)) for key, value := range environment { oldValue, wasSet := os.LookupEnv(key) previous[key] = previousEnv{value: oldValue, set: wasSet} if err := os.Setenv(key, value); err != nil { return fmt.Errorf("set %s: %w", key, err) } } defer func() { for key, old := range previous { if old.set { _ = os.Setenv(key, old.value) } else { _ = os.Unsetenv(key) } } }() previousLang := i18n.Lang() defer i18n.SetLang(previousLang) i18n.SetLang("en") root := newRootCommand() snapshot := interfacesnapshot.Capture(root) if err := validateHelpRendering(root, snapshot); err != nil { return err } if *output == "-" { return interfacesnapshot.Write(stdout, snapshot) } file, err := os.Create(filepath.Clean(*output)) if err != nil { return fmt.Errorf("create snapshot %q: %w", *output, err) } writeErr := interfacesnapshot.Write(file, snapshot) closeErr := file.Close() if writeErr != nil { return fmt.Errorf("write snapshot %q: %w", *output, writeErr) } if closeErr != nil { return fmt.Errorf("close snapshot %q: %w", *output, closeErr) } return nil } func runCompare(args []string, stdout, stderr io.Writer) (bool, error) { flags := flag.NewFlagSet("compare", flag.ContinueOnError) flags.SetOutput(stderr) currentPath := flags.String("current", "", "candidate snapshot path") basePath := flags.String("base", "", "target main/development baseline snapshot path") stablePath := flags.String("stable", "", "latest stable GA snapshot path") approvedMigrationsPath := flags.String( "approved-flag-migrations", "", "merge-base-owned approved flag migration manifest", ) candidateMigrationsPath := flags.String( "candidate-flag-migrations", "", "candidate flag migration manifest", ) approvedCommandMigrationsPath := flags.String( "approved-command-migrations", "", "merge-base-owned approved command migration manifest", ) candidateCommandMigrationsPath := flags.String( "candidate-command-migrations", "", "candidate command migration manifest", ) if err := flags.Parse(args); err != nil { return false, err } if flags.NArg() != 0 { return false, fmt.Errorf("compare accepts no positional arguments") } if *currentPath == "" { return false, fmt.Errorf("compare requires --current") } if *basePath == "" && *stablePath == "" { return false, fmt.Errorf("compare requires --base, --stable, or both") } if (*approvedMigrationsPath == "") != (*candidateMigrationsPath == "") { return false, fmt.Errorf( "--approved-flag-migrations and --candidate-flag-migrations must be provided together", ) } if (*approvedCommandMigrationsPath == "") != (*candidateCommandMigrationsPath == "") { return false, fmt.Errorf( "--approved-command-migrations and --candidate-command-migrations must be provided together", ) } if (*approvedMigrationsPath != "" || *approvedCommandMigrationsPath != "") && (*basePath == "" || *stablePath == "") { return false, fmt.Errorf("migration compare requires both --base and --stable") } current, err := readSnapshot(*currentPath) if err != nil { return false, fmt.Errorf("read current snapshot: %w", err) } references := make(map[string]interfacesnapshot.Snapshot, 2) if *basePath != "" { references["main"], err = readSnapshot(*basePath) if err != nil { return false, fmt.Errorf("read main/development baseline snapshot: %w", err) } } if *stablePath != "" { references["stable"], err = readSnapshot(*stablePath) if err != nil { return false, fmt.Errorf("read stable snapshot: %w", err) } } report := interfacesnapshot.CompareAll(current, references) if *approvedCommandMigrationsPath != "" { flagApproved := interfacesnapshot.FlagMigrationManifest{Version: interfacesnapshot.FlagMigrationManifestVersion, Migrations: []interfacesnapshot.FlagMigration{}} flagCandidate := flagApproved if *approvedMigrationsPath != "" { flagApproved, err = readFlagMigrationManifest(*approvedMigrationsPath) if err != nil { return false, fmt.Errorf("read approved flag migrations: %w", err) } flagCandidate, err = readFlagMigrationManifest(*candidateMigrationsPath) if err != nil { return false, fmt.Errorf("read candidate flag migrations: %w", err) } } commandApproved, readErr := readCommandMigrationManifest(*approvedCommandMigrationsPath) if readErr != nil { return false, fmt.Errorf("read approved command migrations: %w", readErr) } commandCandidate, readErr := readCommandMigrationManifest(*candidateCommandMigrationsPath) if readErr != nil { return false, fmt.Errorf("read candidate command migrations: %w", readErr) } report, err = interfacesnapshot.CompareAllWithInterfaceMigrations( current, references, flagApproved, flagCandidate, commandApproved, commandCandidate, ) if err != nil { return false, fmt.Errorf("validate interface migration lifecycle: %w", err) } } else if *approvedMigrationsPath != "" { approved, readErr := readFlagMigrationManifest(*approvedMigrationsPath) if readErr != nil { return false, fmt.Errorf("read approved flag migrations: %w", readErr) } candidate, readErr := readFlagMigrationManifest(*candidateMigrationsPath) if readErr != nil { return false, fmt.Errorf("read candidate flag migrations: %w", readErr) } report, err = interfacesnapshot.CompareAllWithFlagMigrations( current, references, approved, candidate, ) if err != nil { return false, fmt.Errorf("validate flag migration lifecycle: %w", err) } } encoder := json.NewEncoder(stdout) encoder.SetEscapeHTML(false) encoder.SetIndent("", " ") if err := encoder.Encode(report); err != nil { return false, fmt.Errorf("write comparison report: %w", err) } return report.Compatible, nil } func readFlagMigrationManifest(path string) (interfacesnapshot.FlagMigrationManifest, error) { file, err := os.Open(filepath.Clean(path)) if err != nil { return interfacesnapshot.FlagMigrationManifest{}, err } defer file.Close() return interfacesnapshot.ReadFlagMigrationManifest(file) } func readCommandMigrationManifest(path string) (interfacesnapshot.CommandMigrationManifest, error) { file, err := os.Open(filepath.Clean(path)) if err != nil { return interfacesnapshot.CommandMigrationManifest{}, err } defer file.Close() return interfacesnapshot.ReadCommandMigrationManifest(file) } func validateHelpRendering(root *cobra.Command, snapshot interfacesnapshot.Snapshot) error { for _, command := range snapshot.Commands { path := strings.TrimPrefix(command.Path, "dws") resolved, remaining, err := root.Find(strings.Fields(path)) if err != nil || len(remaining) != 0 || resolved == nil { return fmt.Errorf("resolve %q before help rendering: remaining=%v error=%v", command.Path, remaining, err) } if err := renderCommandHelp(resolved); err != nil { return fmt.Errorf("render %q help: %w", command.Path, err) } } return nil } func renderCommandHelp(command *cobra.Command) (err error) { var stdout, stderr bytes.Buffer command.InitDefaultHelpFlag() command.SetOut(&stdout) command.SetErr(&stderr) defer func() { if recovered := recover(); recovered != nil { err = fmt.Errorf("help renderer panicked: %v", recovered) } }() command.HelpFunc()(command, []string{}) if stderr.Len() > 0 { return fmt.Errorf("help renderer wrote an error: %s", strings.TrimSpace(stderr.String())) } if stdout.Len() == 0 { return fmt.Errorf("help renderer produced empty output") } return nil } func readSnapshot(path string) (interfacesnapshot.Snapshot, error) { file, err := os.Open(filepath.Clean(path)) if err != nil { return interfacesnapshot.Snapshot{}, err } defer file.Close() return interfacesnapshot.Read(file) } func printUsage(w io.Writer) { fmt.Fprintln(w, "usage:") fmt.Fprintln(w, " interface-snapshot generate [--output FILE]") fmt.Fprintln(w, " interface-snapshot compare --current FILE [--base FILE] [--stable FILE] [--approved-flag-migrations FILE --candidate-flag-migrations FILE] [--approved-command-migrations FILE --candidate-command-migrations FILE]") }