Files
dingtalk-workspace-cli/pkg/cmdutil/flags.go
T
wxianfeng 93d6fdb17e feat: edition layer for Wukong overlay; promote shared code to pkg/
Introduce a build-time edition hook so downstream overlays (e.g. Wukong)
can customize auth UX, config dir, static server list, visible products,
and extra root commands while keeping the default open-source behavior.

- Add pkg/edition (defaults) and pkg/editiontest contract tests; Makefile
  target edition-test; CI job edition-tests.
- Root: optional RegisterExtraCommands with tool caller adapter; inject
  static servers to skip market discovery when configured; deduplicate
  top-level commands so overlay wins; extend hideNonDirectRuntimeCommands
  with edition VisibleProducts and static commands (recovery/schema/mcp).
- Auth/config: gate login subcommand and auth login hints for embedded
  editions; optional token auto-purge; edition ConfigDir override.
- version: human-readable multi-line output plus JSON with edition,
  architecture, build, commit.
- Relocate internal/{config,convert,validate} to pkg/; add pkg/cli and
  pkg/cmdutil (flags/time helpers).
- CI: optional notify-downstream job (GitLab trigger via secrets) on main
  push; open_source_policy_test adjusted for new layout.

Secrets (repo settings): WUKONG_TRIGGER_TOKEN, WUKONG_TRIGGER_URL — optional;
if unset, downstream step is skipped.

Made-with: Cursor
2026-03-31 15:29:21 +08:00

154 lines
4.9 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 cmdutil provides reusable CLI helper functions for Cobra commands.
// Both the open-source CLI and private overlays import this package to avoid
// duplicating flag validation, time parsing, and UX helpers.
package cmdutil
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
// GroupRunE is a reusable RunE for parent (group) commands that have no
// business logic of their own. With args it returns an error listing available
// subcommands; without args it shows help.
func GroupRunE(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
var names []string
for _, sub := range cmd.Commands() {
if !sub.Hidden {
names = append(names, sub.Name())
}
}
return fmt.Errorf("unknown subcommand %q for %q\n available: %s\n hint: %s --help",
args[0], cmd.CommandPath(), strings.Join(names, ", "), cmd.CommandPath())
}
return cmd.Help()
}
// HintSubCmd creates a hidden subcommand that only prints a disambiguation hint.
func HintSubCmd(use, hint string) *cobra.Command {
return &cobra.Command{
Use: use,
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("ambiguous command %q for %q\n hint: %s",
use, cmd.Parent().CommandPath(), hint)
},
}
}
// MustGetFlag retrieves a string flag value, checking both local and inherited flags.
func MustGetFlag(cmd *cobra.Command, name string) string {
val, _ := cmd.Flags().GetString(name)
if val == "" {
val, _ = cmd.InheritedFlags().GetString(name)
}
return val
}
// FlagOrFallback reads the primary flag; if empty, falls back through alias
// flags in order, returning the first non-empty value.
func FlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) string {
if v, _ := cmd.Flags().GetString(primary); v != "" {
return v
}
for _, alias := range aliases {
if v, _ := cmd.Flags().GetString(alias); v != "" {
return v
}
}
return ""
}
// MustFlagOrFallback works like FlagOrFallback but returns an error when all
// flags are empty.
func MustFlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) (string, error) {
v := FlagOrFallback(cmd, primary, aliases...)
if v == "" {
return "", fmt.Errorf("flag --%s is required\n hint: %s", primary, cmd.Example)
}
return v, nil
}
// MustFlagWithHint returns an error with an explicit usage example when the
// flag is empty.
func MustFlagWithHint(cmd *cobra.Command, name, example string) (string, error) {
v := MustGetFlag(cmd, name)
if v == "" {
return "", fmt.Errorf("flag --%s is required\n hint: %s", name, example)
}
return v, nil
}
// ValidateRequiredFlags checks that all named string flags are non-empty.
// Returns a formatted error listing all missing flags, or nil.
func ValidateRequiredFlags(cmd *cobra.Command, names ...string) error {
var missing []string
for _, name := range names {
v, _ := cmd.Flags().GetString(name)
if v == "" {
missing = append(missing, "--"+name)
}
}
if len(missing) == 0 {
return nil
}
return fmt.Errorf("missing required flag(s): %s\n usage: %s\n example:\n%s",
strings.Join(missing, ", "), cmd.UseLine(), cmd.Example)
}
// ValidateRequiredFlagWithAliases checks that at least one of the primary flag
// or its aliases is non-empty.
func ValidateRequiredFlagWithAliases(cmd *cobra.Command, primary string, aliases ...string) error {
if FlagOrFallback(cmd, primary, aliases...) != "" {
return nil
}
all := append([]string{primary}, aliases...)
return fmt.Errorf("missing required flag: --%s (or %s)\n usage: %s\n example:\n%s",
primary, "--"+strings.Join(all[1:], " / --"), cmd.UseLine(), cmd.Example)
}
// ConfirmDelete asks for interactive confirmation before destructive operations.
// Returns true if --yes/-y flag is set or the user types "yes"/"y".
func ConfirmDelete(cmd *cobra.Command, resourceType, resourceName string) bool {
if yes, _ := cmd.Flags().GetBool("yes"); yes {
return true
}
for _, arg := range os.Args[1:] {
if arg == "--yes" || arg == "-y" {
return true
}
}
fmt.Fprintf(cmd.ErrOrStderr(), "About to delete %s: %s\n", resourceType, resourceName)
fmt.Fprint(cmd.ErrOrStderr(), "Confirm deletion? (yes/no): ")
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
answer = strings.TrimSpace(strings.ToLower(answer))
if answer == "yes" || answer == "y" {
return true
}
fmt.Fprintln(cmd.ErrOrStderr(), "Operation cancelled")
return false
}