Files
dingtalk-workspace-cli/pkg/cmdutil/leaf_merge.go
T
xianfeng wang bbca507792 feat(discovery): MCP discovery service abstraction with positional params, examples, defaults, and schema v3 extensions (#156)
* discovery

* discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* wk discovery

* discovery example and default

* position param

* Fix test

* dev auto login

* fix test

* fix conflict
2026-04-24 14:08:17 +08:00

101 lines
3.8 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
import (
"log/slog"
"github.com/spf13/cobra"
)
// MergeHardcodedLeaves grafts leaves from hardcodedRoot onto dynamicRoot when
// the same-named path does not already exist. Groups recurse. On conflicts the
// dynamic side always wins, because the discovery envelope is the runtime
// authority: hardcoded commands are retained only as a leaf-level fallback for
// behaviour the envelope explicitly does not declare. See
// _docs/discovery-overlay-authority.md.
//
// PRECONDITION: dynamicRoot must be envelope-sourced (carry the
// SourceAnnotation=SourceEnvelope marker set by BuildDynamicCommands via
// MarkEnvelopeSource). Callers that might otherwise pass a helper-fallback
// root with the same name are responsible for evicting it upstream —
// otherwise the "envelope is authority" rule silently promotes helper leaves
// over same-named hardcoded leaves and the overlay loses its ability to
// override routing. The wukong overlay's RegisterProducts gates this call on
// IsEnvelopeSourced(dynamicRoot); new callers must do the same.
//
// Conflict resolution table:
//
// dynamic hardcoded → action
// ------- --------- -----------------------------
// absent anything graft hardcoded subtree
// leaf leaf dynamic wins (no-op)
// group group recurse
// leaf group dynamic wins, warn
// group leaf dynamic wins, warn
//
// MergeHardcodedLeaves mutates dynamicRoot in place and returns it so callers
// can chain. hardcodedRoot is treated as a donor: grafted children are
// detached from it so their cobra parent pointer points at the new parent.
func MergeHardcodedLeaves(dynamicRoot, hardcodedRoot *cobra.Command) *cobra.Command {
if dynamicRoot == nil || hardcodedRoot == nil {
return dynamicRoot
}
// Snapshot children before mutating hardcodedRoot — RemoveCommand during
// iteration over hardcodedRoot.Commands() is unsafe because cobra returns
// a slice backed by an internal field that is re-sliced on removal.
children := append([]*cobra.Command(nil), hardcodedRoot.Commands()...)
for _, hc := range children {
dyn := findChildByName(dynamicRoot, hc.Name())
switch {
case dyn == nil:
hardcodedRoot.RemoveCommand(hc)
dynamicRoot.AddCommand(hc)
case IsLeafCmd(hc) && IsLeafCmd(dyn):
// Envelope is authority; hardcoded leaf is ignored.
case !IsLeafCmd(hc) && !IsLeafCmd(dyn):
MergeHardcodedLeaves(dyn, hc)
default:
slog.Warn("overlay: shape mismatch, keeping dynamic",
"name", hc.Name(),
"dynamicIsLeaf", IsLeafCmd(dyn),
"hardcodedIsLeaf", IsLeafCmd(hc))
}
}
return dynamicRoot
}
// IsLeafCmd reports whether a command has no subcommands. Leaves carry a RunE
// and are invocation targets; groups merely organise subcommands.
func IsLeafCmd(cmd *cobra.Command) bool {
if cmd == nil {
return false
}
return !cmd.HasSubCommands()
}
// findChildByName scans parent's direct children for a matching Name(). A
// local helper so pkg/cmdutil stays independent of internal/cobracmd.
func findChildByName(parent *cobra.Command, name string) *cobra.Command {
if parent == nil {
return nil
}
for _, child := range parent.Commands() {
if child.Name() == name {
return child
}
}
return nil
}