Files
larksuite-cli/cmd/config/plugins.go
shanglei eba0d7512e fix(extension/platform): address CodeRabbit review comments + CI gofmt
- hook/install: propagate wrapper-injected ctx to invokeOriginal so
  RunE/Run see context values added by upstream Wrappers
- hook/testing: SetStderrForTesting returns a restore func; tests now
  defer it via t.Cleanup to avoid cross-test sink leakage
- cmdpolicy/active: deep-copy ActivePolicy.Rule on SetActive/GetActive
  so callers can't mutate the stored global through shared slices
- platform/inventory: deep-copy Inventory + nested Plugins / HookEntry
  / RuleView slices on SetActiveInventory / GetActiveInventory
- platform/staging: Restrict clones the plugin-supplied Rule before
  retaining it so the plugin can't mutate it after Install returns
- platform/version: reject RequiredCLIVersion with more than three
  numeric components instead of silently truncating 1.2.3.4 to 1.2.3
- cmd/platform_bootstrap: clear cmdpolicy.SetActive on yaml resolver
  error so config policy show doesn't surface a stale rule
- cmd/platform_bootstrap_test: tmpHome pins LARKSUITE_CLI_CONFIG_DIR
  so host env can't bleed into the policy test fixtures
- cmdpolicy/apply: installDenyStub returns bool; Apply count no longer
  over-reports when strict-mode short-circuits the install
- cmdpolicy/engine: aggregateParents now returns the runnable hybrid's
  own denial status when all children are placeholder branches
- cmdpolicy/resolver_test: use t.TempDir()-rooted missing path instead
  of hardcoded /nonexistent for hermetic missing-file assertion
- cmd/config/plugins: empty-inventory branch emits total: 0 so the
  JSON schema stays stable across populated/empty cases
- cmd/platform_guards_test: select leaf by RunE != nil (not Runnable)
  so the test doesn't nil-deref on Run-only commands
- gofmt run on previously committed cmdpolicy/path*.go (CI fast-gate)
2026-05-16 14:40:49 +08:00

100 lines
3.3 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package config
import (
"github.com/spf13/cobra"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/output"
internalplatform "github.com/larksuite/cli/internal/platform"
)
// NewCmdConfigPlugins exposes the plugin inventory diagnostic command.
//
// `config policy show` is intentionally focused on the user-layer Rule
// (Restrict). Plugins also contribute hooks (Observe / Wrap / Lifecycle)
// that are not policy gates but still mutate the CLI's runtime behaviour.
// This command surfaces both halves so an operator can answer "what is
// this binary doing differently from stock lark-cli?" in one place.
//
// Like config policy show, the dispatch path is exempt from policy
// enforcement (see internal/cmdpolicy/diagnostic.go) so it remains
// usable under any Rule.
func NewCmdConfigPlugins(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "plugins",
Hidden: true, // diagnostic-only; kept callable, omitted from --help so it stays out of AI-agent context
Short: "Inspect installed plugins and their hook contributions",
// Same leaf-level no-op as config policy: the parent `config`
// group's PersistentPreRunE requires builtin credential, but
// this is a read-only diagnostic that must work everywhere.
PersistentPreRunE: func(c *cobra.Command, _ []string) error {
c.SilenceUsage = true
return nil
},
}
cmd.AddCommand(newCmdConfigPluginsShow(f))
return cmd
}
func newCmdConfigPluginsShow(f *cmdutil.Factory) *cobra.Command {
return &cobra.Command{
Use: "show",
Short: "List successfully installed plugins, their rules, and registered hooks",
Long: `Print every plugin that committed during bootstrap, including:
- name / version / capabilities (FailurePolicy, Restricts, RequiredCLIVersion)
- rule (when the plugin called r.Restrict)
- hooks: observers (Before / After), wrappers, lifecycle handlers
Hooks are attributed by their namespaced name -- the framework prepends
the plugin name as the prefix at registration time, so an entry
"secaudit.audit-pre" belongs to plugin "secaudit".`,
RunE: func(cmd *cobra.Command, args []string) error {
return runConfigPluginsShow(f)
},
}
}
func runConfigPluginsShow(f *cmdutil.Factory) error {
inv := internalplatform.GetActiveInventory()
if inv == nil {
// Always emit the same field set as the populated branch so
// AI agents and CI scripts don't have to branch on whether
// `total` is present. `note` makes the unusual state explicit
// for human readers.
output.PrintJson(f.IOStreams.Out, map[string]any{
"plugins": []any{},
"total": 0,
"note": "no inventory recorded; bootstrap did not finish",
})
return nil
}
plugins := make([]map[string]any, 0, len(inv.Plugins))
for _, p := range inv.Plugins {
entry := map[string]any{
"name": p.Name,
"version": p.Version,
"capabilities": p.Capabilities,
}
if p.Rule != nil {
entry["rule"] = p.Rule
}
entry["hooks"] = map[string]any{
"observers": p.Observers,
"wrappers": p.Wrappers,
"lifecycle": p.Lifecycles,
"count": len(p.Observers) + len(p.Wrappers) + len(p.Lifecycles),
}
plugins = append(plugins, entry)
}
output.PrintJson(f.IOStreams.Out, map[string]any{
"plugins": plugins,
"total": len(plugins),
})
return nil
}