Files
玉澜 7762abc1ae refactor(helpers): drop unused LeafInt64 kind (CR C1)
No production LeafSpec uses LeafInt64; devapp only needs LeafInt
(non-zero-only putInt semantics). The default MCP dispatch and Server
routing stay — they are the framework's documented main path for
future MCP-direct products.
2026-07-29 00:29:21 +08:00

332 lines
11 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 helpers
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/spf13/cobra"
)
// leaf.go 是叶子命令的统一构建框架。
//
// 现状问题:每个叶子命令手写 cobra.Command,required 校验、别名回退、环境
// 变量回退、值转换、参数装配、派发调用在各个产品文件里各写一份,行为难以
// 保持一致。LeafSpec 把这些共性收敛为声明式结构:命令只声明「flag 集合 +
// 绑定关系」,框架统一执行。默认派发走 MCP 直连(callMCPTool);非 MCP
// 命令(如 devapp 走 executor.Runner)通过 LeafSpec.Call 注入自己的派发器,
// 复用同一套 flag/校验/装配逻辑。复杂命令可通过 LeafSpec.RunE 完全自定义
// (逃生舱),不在框架适用范围内强行套用。
//
// 迁移纪律:从手写命令迁移到 LeafSpec 时,flag 名、默认值、usage 文案、
// MarkFlagRequired、required 错误格式、toolArgs 键与值必须逐字保持一致,
// 由 check-generated-drift(catalog 零漂移)与命令兼容性检查兜底证明等价。
// LeafFlagKind 是 flag 的值类型。
type LeafFlagKind int
const (
// LeafString 字符串 flag(默认)。
LeafString LeafFlagKind = iota
// LeafInt 整型 flag(注册为 cobra Int);仅在值 != 0 时进入 toolArgs,
// 对应手写「putInt 仅在非零才入参」语义(如 devapp app-group-id)。
LeafInt
)
// LeafFlag 声明一个 flag 的注册方式与到 MCP toolArgs 的绑定。
type LeafFlag struct {
Name string // flag 名(kebab-case)
Usage string // 注册 usage 文案
Kind LeafFlagKind // 值类型,默认 LeafString
Default string // 注册默认值(cobra 注册仅 LeafString 使用;回退链链尾对所有 Kind 生效,不遮蔽别名/env)
// Required 为 true 时在 RunE 期校验有效值非空。普通 Required 汇聚为
// cmdutil.ValidateRequiredFlags 兼容的统一报错;配置 EnvVar 时回退读
// 环境变量,仍为空则报 RequiredHint(或默认文案)。
Required bool
RequiredHint string
// MarkRequired 为 true 时调用 cobra MarkFlagRequired(catalog required
// 投影的硬下限),cobra 会在 RunE 之前先行报错。
MarkRequired bool
Aliases []string // 隐藏别名,按主 flag 的 Kind 注册;主 flag 未显式提供时按序回退
EnvVar string // 有效值为空时回退读取的环境变量(整型 flag 的 env 值必须可解析)
// ArgDefault:注册默认值为空、但 toolArgs 需要兜底的场景(如 list type
// 注册默认 "ALL" 之外的旧命令);有效值为空时以 ArgDefault 入参。
ArgDefault string
// Bind 是 toolArgs 的键;为空时使用 Name。
Bind string
// Transform 把字符串有效值转为入参值;nil 时原样入参。返回
// (nil, nil) 表示跳过该键(用于「可空数值:为空或解析失败都不入参」
// 的手写语义)。
Transform func(raw string) (any, error)
// OmitEmpty 为 true 时有效值为空则不进入 toolArgs(LeafInt 恒为
// 「非零才入参」,忽略此字段)。
OmitEmpty bool
// Trim 为 true 时对有效值做 strings.TrimSpace(主 flag/别名/env 统一),
// 对应手写 devAppStringFlag 恒 trim 的语义;亦使「纯空白」值在 required
// 校验中视为空。
Trim bool
}
// LeafSpec 声明一个 MCP 直连叶子命令。
type LeafSpec struct {
Use string
Short string
Long string
Example string
// Server 非空时走 callMCPToolOnServer(显式 server 路由),否则走
// callMCPTool(按 product 路由)。Call 非空时两者都被忽略。
Server string
Tool string
Flags []LeafFlag
// Call 是可插拔派发函数,非空时替代默认的 callMCPTool/callMCPToolOnServer。
// 供非 MCP 直连命令(如 devapp 走 executor.Runner)复用本框架:调用方用
// 闭包捕获自己的 runner/派发器即可。签名与默认路径一致——收到框架装配好
// 的 toolArgs,自行派发。
Call func(cmd *cobra.Command, tool string, args map[string]any) error
// Validate 是跨 flag 校验钩子(如时间区间、互斥关系),在 required
// 校验之后、toolArgs 装配之前执行;nil 时跳过。单 flag 的格式转换
// 应放在 LeafFlag.Transform,不要放进 Validate。
Validate func(cmd *cobra.Command, args []string) error
// RunE 非空时完全自定义执行体(逃生舱),框架只负责注册 flag。
RunE func(cmd *cobra.Command, args []string) error
// PostMount 在 flag 注册完成之后、RunE 设定之前对构建好的 cmd 做最终
// 调整(设置 Args/DisableAutoGenTag、调用 annotate/preferLegacy 等)。
// 无论是否使用 RunE 逃生舱都会执行。对标 lark shortcut 的 PostMount。
PostMount func(cmd *cobra.Command)
}
// NewLeafCommand 按 LeafSpec 构建叶子命令。
func NewLeafCommand(spec LeafSpec) *cobra.Command {
cmd := &cobra.Command{
Use: spec.Use,
Short: spec.Short,
Long: spec.Long,
Example: spec.Example,
}
for _, flag := range spec.Flags {
switch flag.Kind {
case LeafInt:
cmd.Flags().Int(flag.Name, 0, flag.Usage)
default:
cmd.Flags().String(flag.Name, flag.Default, flag.Usage)
}
// 别名按主 flag 的 Kind 注册,否则整型别名的值永远读不到(静默丢弃)。
for _, alias := range flag.Aliases {
switch flag.Kind {
case LeafInt:
cmd.Flags().Int(alias, 0, flag.Usage+" (alias)")
default:
cmd.Flags().String(alias, "", flag.Usage+" (alias)")
}
_ = cmd.Flags().MarkHidden(alias)
}
if flag.MarkRequired {
_ = cmd.MarkFlagRequired(flag.Name)
}
}
if spec.PostMount != nil {
spec.PostMount(cmd)
}
if spec.RunE != nil {
cmd.RunE = spec.RunE
return cmd
}
cmd.RunE = func(cmd *cobra.Command, args []string) error {
if err := leafValidateRequired(cmd, spec); err != nil {
return err
}
if spec.Validate != nil {
if err := spec.Validate(cmd, args); err != nil {
return err
}
}
toolArgs, err := leafArgs(cmd, spec)
if err != nil {
return err
}
if spec.Call != nil {
return spec.Call(cmd, spec.Tool, toolArgs)
}
if spec.Server != "" {
return callMCPToolOnServer(spec.Server, spec.Tool, toolArgs)
}
return callMCPTool(spec.Tool, toolArgs)
}
return cmd
}
// leafValidateRequired 复现手写命令的 required 语义:普通 Required 统一报
// 「missing required flag(s)」;带 EnvVar 的 Required 单独报 RequiredHint。
// 普通组先于环境变量组校验,保持与手写顺序一致。两组都按声明的
// 「主 flag → 别名 → env」回退取有效值:只传兼容别名同样视为已提供。
func leafValidateRequired(cmd *cobra.Command, spec LeafSpec) error {
var plain []string
for _, flag := range spec.Flags {
if flag.Required && flag.EnvVar == "" && flag.RequiredHint == "" && !leafHasEffectiveValue(cmd, flag) {
plain = append(plain, flag.Name)
}
}
if err := missingRequiredFlagsError(cmd, plain...); err != nil {
return err
}
for _, flag := range spec.Flags {
if !flag.Required || (flag.EnvVar == "" && flag.RequiredHint == "") {
continue
}
if !leafHasEffectiveValue(cmd, flag) {
hint := flag.RequiredHint
if hint == "" {
hint = fmt.Sprintf("flag --%s is required", flag.Name)
}
return fmt.Errorf("%s", hint)
}
}
return nil
}
// leafHasEffectiveValue 判定 Required 是否满足,标准与 leafArgs 的入参判定
// 一致(LeafInt 需非零、字符串需非空):否则会出现校验声称有效、toolArgs
// 却缺参的分裂。整型解析失败视为已提供,让 leafArgs 报出更精确的
// invalid integer 错误。
func leafHasEffectiveValue(cmd *cobra.Command, flag LeafFlag) bool {
if flag.Kind == LeafInt {
v, err := leafIntegerValue(cmd, flag)
if err != nil {
return true
}
return v != 0
}
return leafEffectiveValue(cmd, flag) != ""
}
// leafArgs 按绑定关系装配 toolArgs。
func leafArgs(cmd *cobra.Command, spec LeafSpec) (map[string]any, error) {
toolArgs := map[string]any{}
for _, flag := range spec.Flags {
bind := flag.Bind
if bind == "" {
bind = flag.Name
}
if flag.Kind == LeafInt {
v, err := leafIntegerValue(cmd, flag)
if err != nil {
return nil, err
}
// 保持「非零才入参」(putInt 语义)。
if v != 0 {
toolArgs[bind] = int(v)
}
continue
}
effective := leafEffectiveValue(cmd, flag)
if effective == "" && flag.ArgDefault != "" {
effective = flag.ArgDefault
}
if effective == "" && flag.OmitEmpty {
continue
}
if flag.Transform != nil {
value, err := flag.Transform(effective)
if err != nil {
return nil, err
}
if value == nil {
continue
}
toolArgs[bind] = value
continue
}
toolArgs[bind] = effective
}
return toolArgs, nil
}
// leafEffectiveValue 按「显式主 flag → 别名 → 环境变量 → 注册默认值」顺序取
// 有效值(字符串形态,整型 flag 统一格式化);Trim 为 true 时对结果统一
// TrimSpace。
func leafEffectiveValue(cmd *cobra.Command, flag LeafFlag) string {
v := leafRawValue(cmd, flag)
if flag.Trim {
v = strings.TrimSpace(v)
}
return v
}
// leafRawValue 取未 trim 的原始有效值。主 flag 仅在用户显式提供(Changed)
// 且非空时命中;注册默认值降级为链尾兜底,不再遮蔽别名与环境变量。Trim 为
// true 时候选值按 trim 后判空,纯空白与空串同样落入下一级回退。
func leafRawValue(cmd *cobra.Command, flag LeafFlag) string {
usable := func(v string) bool {
if flag.Trim {
v = strings.TrimSpace(v)
}
return v != ""
}
if cmd.Flags().Changed(flag.Name) {
if v := leafFlagString(cmd, flag.Kind, flag.Name); usable(v) {
return v
}
}
for _, alias := range flag.Aliases {
if !cmd.Flags().Changed(alias) {
continue
}
if v := leafFlagString(cmd, flag.Kind, alias); usable(v) {
return v
}
}
if flag.EnvVar != "" {
if v := os.Getenv(flag.EnvVar); usable(v) {
return v
}
}
return flag.Default
}
// leafFlagString 按注册类型读取 flag 并统一为字符串形态,使整型 flag 能复用
// 同一条回退链(required 校验、别名、env)。
func leafFlagString(cmd *cobra.Command, kind LeafFlagKind, name string) string {
switch kind {
case LeafInt:
v, _ := cmd.Flags().GetInt(name)
return strconv.Itoa(v)
default:
return mustGetFlag(cmd, name)
}
}
// leafIntegerValue 按回退链取整型 flag 的有效值;环境变量提供的字符串必须
// 可解析,否则报错而非静默丢弃。
func leafIntegerValue(cmd *cobra.Command, flag LeafFlag) (int64, error) {
raw := leafEffectiveValue(cmd, flag)
if raw == "" {
return 0, nil
}
v, err := strconv.ParseInt(raw, 10, 64)
if err != nil {
return 0, fmt.Errorf("flag --%s: invalid integer value %q", flag.Name, raw)
}
return v, nil
}