← back to Cli Printing Press
feat(generator): add CRUD aliases to generated CLI commands
5b3d281e58a07552df27881e61f15dabe5a88585 · 2026-03-24 16:21:10 -0700 · Matt Van Horn
Computes list/get/create/update/delete aliases from HTTP method + path
patterns. Generated CLIs now support both operationId names and friendly
aliases (e.g., `pet list` alongside `pet find-by-status`).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Files touched
M internal/generator/templates/command.go.tmplM internal/openapi/parser.goM internal/spec/spec.go
Diff
commit 5b3d281e58a07552df27881e61f15dabe5a88585
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Tue Mar 24 16:21:10 2026 -0700
feat(generator): add CRUD aliases to generated CLI commands
Computes list/get/create/update/delete aliases from HTTP method + path
patterns. Generated CLIs now support both operationId names and friendly
aliases (e.g., `pet list` alongside `pet find-by-status`).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
internal/generator/templates/command.go.tmpl | 3 +
internal/openapi/parser.go | 92 ++++++++++++++++++++++++++++
internal/spec/spec.go | 1 +
3 files changed, 96 insertions(+)
diff --git a/internal/generator/templates/command.go.tmpl b/internal/generator/templates/command.go.tmpl
index 6120a2ca..9b43f26e 100644
--- a/internal/generator/templates/command.go.tmpl
+++ b/internal/generator/templates/command.go.tmpl
@@ -39,6 +39,9 @@ func new{{camel $.FuncPrefix}}{{camel $eName}}Cmd(flags *rootFlags) *cobra.Comma
cmd := &cobra.Command{
Use: "{{$eName}}{{positionalArgs $endpoint}}",
+{{- if $endpoint.Alias}}
+ Aliases: []string{"{{$endpoint.Alias}}"},
+{{- end}}
Short: "{{oneline $endpoint.Description}}",
Example: "{{exampleLine $.CommandPath $eName $endpoint}}",
RunE: func(cmd *cobra.Command, args []string) error {
diff --git a/internal/openapi/parser.go b/internal/openapi/parser.go
index 51bf7031..bca323bb 100644
--- a/internal/openapi/parser.go
+++ b/internal/openapi/parser.go
@@ -393,9 +393,101 @@ func mapResources(doc *openapi3.T, out *spec.APISpec, basePath string) {
out.Resources[primaryName] = resource
}
+ assignEndpointAliases(out.Resources)
filterGlobalParams(out.Resources)
}
+func assignEndpointAliases(resources map[string]spec.Resource) {
+ resourceNames := make([]string, 0, len(resources))
+ for name := range resources {
+ resourceNames = append(resourceNames, name)
+ }
+ sort.Strings(resourceNames)
+
+ for _, name := range resourceNames {
+ resource := resources[name]
+ assignAliasesInResource(&resource)
+ resources[name] = resource
+ }
+}
+
+func assignAliasesInResource(resource *spec.Resource) {
+ if resource == nil {
+ return
+ }
+
+ assignAliasesForEndpoints(resource.Endpoints)
+
+ subNames := make([]string, 0, len(resource.SubResources))
+ for name := range resource.SubResources {
+ subNames = append(subNames, name)
+ }
+ sort.Strings(subNames)
+
+ for _, name := range subNames {
+ subResource := resource.SubResources[name]
+ assignAliasesInResource(&subResource)
+ resource.SubResources[name] = subResource
+ }
+}
+
+func assignAliasesForEndpoints(endpoints map[string]spec.Endpoint) {
+ if len(endpoints) == 0 {
+ return
+ }
+
+ endpointNames := make([]string, 0, len(endpoints))
+ nameSet := make(map[string]struct{}, len(endpoints))
+ for name := range endpoints {
+ endpointNames = append(endpointNames, name)
+ nameSet[name] = struct{}{}
+ }
+ sort.Strings(endpointNames)
+
+ usedAliases := map[string]struct{}{}
+ for _, name := range endpointNames {
+ endpoint := endpoints[name]
+ alias := computeAlias(endpoint.Method, endpoint.Path, name)
+ if alias == "" || alias == name {
+ endpoints[name] = endpoint
+ continue
+ }
+ if _, exists := nameSet[alias]; exists {
+ endpoints[name] = endpoint
+ continue
+ }
+ if _, used := usedAliases[alias]; used {
+ endpoints[name] = endpoint
+ continue
+ }
+
+ endpoint.Alias = alias
+ endpoints[name] = endpoint
+ usedAliases[alias] = struct{}{}
+ }
+}
+
+func computeAlias(method, path, endpointName string) string {
+ _ = endpointName
+
+ hasPathParam := strings.Contains(path, "{")
+ switch strings.ToUpper(method) {
+ case "GET":
+ if hasPathParam {
+ return "get"
+ }
+ return "list"
+ case "POST":
+ return "create"
+ case "PUT", "PATCH":
+ return "update"
+ case "DELETE":
+ return "delete"
+ default:
+ return ""
+ }
+}
+
func filterGlobalParams(resources map[string]spec.Resource) {
totalEndpoints := 0
paramCounts := map[string]int{}
diff --git a/internal/spec/spec.go b/internal/spec/spec.go
index dbc83537..c7d98457 100644
--- a/internal/spec/spec.go
+++ b/internal/spec/spec.go
@@ -51,6 +51,7 @@ type Endpoint struct {
Response ResponseDef `yaml:"response"`
Pagination *Pagination `yaml:"pagination"`
ResponsePath string `yaml:"response_path,omitempty"` // path to extract data array from response (e.g., "data", "results.items")
+ Alias string `yaml:"-"` // computed, not from YAML
}
type Param struct {
← 96f0d513 feat(cli): add --force flag to generate command
·
back to Cli Printing Press
·
fix(generator): doctor tries health endpoints before reporti 651d11e1 →