Skip to content

Commit 830b07c

Browse files
committed
refactor: converge command pipelines onto a typed metadata model + catalog
- New internal/meta: typed model over meta_data.json; owns field normalization + the access-token<->identity bijection (3 duplicate token interpreters merged into one). - New internal/apicatalog: navigation (Resolve/MethodRefs/WalkMethods/ ServiceMethods/Complete); path resolution moved out of internal/schema. - internal/schema is render-only; risk constants moved to internal/core. - internal/registry keeps source + scope policy and walks the catalog; cmd/service param flags extracted to paramflags.go/flaggroups.go. - Add: typed flags for raw API commands; grouped --help with inline enum value=meaning + method affordance; envelope enumDescriptions. - Fix: tolerant Option.Value so one bad value can't blank the catalog; nested-resource completion; auth-hint nested path. - Remove: byte-for-byte envelope golden. Change-Id: I6ecb7f7c7c547a9ad37c0e8561ef1ced8767e612
1 parent 99e314f commit 830b07c

50 files changed

Lines changed: 3788 additions & 2864 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

cmd/auth/login_interactive.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,11 @@ func buildDomainMeta(name, lang string) domainMeta {
9292
Description: desc,
9393
}
9494
}
95-
// Fallback: read from from_meta spec (legacy)
96-
meta := registry.LoadFromMeta(name)
95+
// Fallback: read from the typed service spec (legacy)
9796
dm := domainMeta{Name: name}
98-
if meta != nil {
99-
if t, ok := meta["title"].(string); ok {
100-
dm.Title = t
101-
}
102-
if d, ok := meta["description"].(string); ok {
103-
dm.Description = d
104-
}
97+
if svc, ok := registry.ServiceTyped(name); ok {
98+
dm.Title = svc.Title
99+
dm.Description = svc.Description
105100
}
106101
return dm
107102
}

cmd/command_catalog_path_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
2+
// SPDX-License-Identifier: MIT
3+
4+
package cmd
5+
6+
import (
7+
"reflect"
8+
"testing"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// TestCommandCatalogPath pins that the auth-hint path reconstruction inverts the
14+
// service command tree for any depth — flat dotted resources AND genuinely
15+
// nested resources — so it round-trips through apicatalog.Resolve instead of
16+
// assuming a fixed root->service->resource->method shape.
17+
func TestCommandCatalogPath(t *testing.T) {
18+
chain := func(names ...string) *cobra.Command {
19+
var parent, leaf *cobra.Command
20+
for _, n := range names {
21+
c := &cobra.Command{Use: n}
22+
if parent != nil {
23+
parent.AddCommand(c)
24+
}
25+
parent = c
26+
leaf = c
27+
}
28+
return leaf
29+
}
30+
31+
tests := []struct {
32+
name string
33+
leaf *cobra.Command
34+
want []string
35+
}{
36+
{"flat dotted resource", chain("lark-cli", "im", "chat.members", "create"), []string{"im", "chat.members", "create"}},
37+
{"nested resources", chain("lark-cli", "im", "spaces", "items", "get"), []string{"im", "spaces", "items", "get"}},
38+
{"service level", chain("lark-cli", "im"), []string{"im"}},
39+
}
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
if got := commandCatalogPath(tt.leaf); !reflect.DeepEqual(got, tt.want) {
43+
t.Errorf("commandCatalogPath = %v, want %v", got, tt.want)
44+
}
45+
})
46+
}
47+
48+
// The root command (no parent) has no catalog path.
49+
if got := commandCatalogPath(&cobra.Command{Use: "lark-cli"}); len(got) != 0 {
50+
t.Errorf("root path = %v, want empty", got)
51+
}
52+
}

cmd/error_auth_hint.go

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/spf13/cobra"
1212

1313
"github.com/larksuite/cli/errs"
14+
"github.com/larksuite/cli/internal/apicatalog"
1415
internalauth "github.com/larksuite/cli/internal/auth"
1516
"github.com/larksuite/cli/internal/cmdutil"
1617
"github.com/larksuite/cli/internal/core"
@@ -118,38 +119,37 @@ func resolveDeclaredShortcutScopes(cmd *cobra.Command, identity string) []string
118119
}
119120

120121
// resolveDeclaredServiceMethodScopes returns the scopes declared by a
121-
// service/resource/method command from the embedded from_meta registry.
122+
// service/resource/method command. It reconstructs the catalog path from the
123+
// command ancestry and resolves it through the same navigation Module the
124+
// command tree is built from (apicatalog), so it stays correct for nested
125+
// resources instead of hard-coding a root->service->resource->method depth.
126+
// Non-method commands (services, resources, shortcuts) resolve to a non-method
127+
// target and yield no scopes.
122128
func resolveDeclaredServiceMethodScopes(cmd *cobra.Command, identity string) []string {
123-
// Service-method scope lookup only applies to commands mounted as
124-
// root -> service -> resource -> method. Non-resource/method commands
125-
// intentionally return no scopes here so auth-hint enrichment does not
126-
// change runtime semantics for other command shapes.
127-
if cmd == nil || cmd.Parent() == nil || cmd.Parent().Parent() == nil || cmd.Parent().Parent().Parent() == nil {
129+
if cmd == nil || strings.HasPrefix(cmd.Name(), "+") {
128130
return nil
129131
}
130-
if strings.HasPrefix(cmd.Name(), "+") {
132+
path := commandCatalogPath(cmd)
133+
if len(path) == 0 {
131134
return nil
132135
}
133-
134-
service := cmd.Parent().Parent().Name()
135-
resource := cmd.Parent().Name()
136-
method := cmd.Name()
137-
138-
spec := registry.LoadFromMeta(service)
139-
if spec == nil {
140-
return nil
141-
}
142-
resources, _ := spec["resources"].(map[string]interface{})
143-
resMap, _ := resources[resource].(map[string]interface{})
144-
if resMap == nil {
136+
target, err := registry.RuntimeCatalog().Resolve(path)
137+
if err != nil || target.Kind != apicatalog.TargetMethod {
145138
return nil
146139
}
147-
methods, _ := resMap["methods"].(map[string]interface{})
148-
methodMap, _ := methods[method].(map[string]interface{})
149-
if methodMap == nil {
150-
return nil
151-
}
152-
return registry.DeclaredScopesForMethod(methodMap, identity)
140+
return registry.DeclaredScopesForMethod(target.Method.Method, identity)
141+
}
142+
143+
// commandCatalogPath reconstructs the catalog path [service, resource..., method]
144+
// from a command's ancestry, excluding the root command. It is the inverse of
145+
// the service command tree's construction, so any depth (flat or nested)
146+
// round-trips through apicatalog.Resolve.
147+
func commandCatalogPath(cmd *cobra.Command) []string {
148+
var path []string
149+
for c := cmd; c != nil && c.Parent() != nil; c = c.Parent() {
150+
path = append([]string{c.Name()}, path...)
151+
}
152+
return path
153153
}
154154

155155
// shortcutSupportsIdentity reports whether a shortcut supports the requested

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const rootLong = `lark-cli — Lark/Feishu CLI tool.
3333
USAGE:
3434
lark-cli <command> [subcommand] [method] [options]
3535
lark-cli api <method> <path> [--params <json>] [--data <json>]
36-
lark-cli schema <service.resource.method> [--format pretty]
36+
lark-cli schema <service.resource.method>
3737
3838
EXAMPLES:
3939
# View upcoming events

0 commit comments

Comments
 (0)