feat(template): add search, get --raw, and help --all#170
Conversation
Allow users to export raw YAML spec from a template so they can save it locally, modify it, and redeploy with `template deploy`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds raw YAML output to Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant CLI as CLI
participant HTTP as HTTP Server
participant Stdout as Stdout
User->>CLI: run `zeabur template get --code <code> --raw`
CLI->>CLI: parse flags (opts.raw == true)
CLI->>HTTP: GET /templates/<code>/raw
HTTP-->>CLI: 200 + YAML stream
CLI->>Stdout: stream YAML to stdout
CLI-->>User: exit
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
internal/cmd/template/get/get.go (1)
84-90: Consider handling emptyRawSpecYamlwhen--rawis specified.If a template exists but
RawSpecYamlis empty (e.g., older templates or API edge cases), the user will receive no output, which could be confusing—especially when piping to a file.💡 Suggested improvement
if template == nil || template.Code == "" { fmt.Println("Template not found") } else if opts.raw { + if template.RawSpecYaml == "" { + return fmt.Errorf("no raw YAML spec available for template %q", opts.code) + } fmt.Print(template.RawSpecYaml) } else { f.Printer.Table([]string{"Code", "Name", "Description"}, [][]string{{template.Code, template.Name, template.Description}}) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cmd/template/get/get.go` around lines 84 - 90, When opts.raw is true but template.RawSpecYaml may be empty, detect that case and provide a clear fallback instead of printing nothing: inside the existing conditional branch that checks opts.raw (in the get command handling where template and template.Code are checked), add a check for template.RawSpecYaml == "" and either print a message like "Template has no raw spec available" (and exit nonzero if appropriate) or fall back to displaying the formatted table via f.Printer.Table; reference the variables template, opts.raw, and template.RawSpecYaml so the change is applied in the same conditional block that currently prints template.RawSpecYaml.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@internal/cmd/template/get/get.go`:
- Around line 84-90: When opts.raw is true but template.RawSpecYaml may be
empty, detect that case and provide a clear fallback instead of printing
nothing: inside the existing conditional branch that checks opts.raw (in the get
command handling where template and template.Code are checked), add a check for
template.RawSpecYaml == "" and either print a message like "Template has no raw
spec available" (and exit nonzero if appropriate) or fall back to displaying the
formatted table via f.Printer.Table; reference the variables template, opts.raw,
and template.RawSpecYaml so the change is applied in the same conditional block
that currently prints template.RawSpecYaml.
The Template GraphQL type doesn't have a rawSpecYaml field. Fetch raw YAML from https://zeabur.com/templates/{code}.yaml instead. Also skip interactive prompt when --code flag is already provided. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/cmd/template/get/get.go`:
- Around line 96-109: The getTemplateRaw function currently uses http.Get and
concatenates code into the URL; change it to build the URL with
url.PathEscape(code) to escape slashes and special chars, and use an http.Client
with a sensible timeout (e.g., 10s) instead of http.Get so the call won’t hang
indefinitely; call client.Get on the escaped URL, keep the same response
handling and error returns, and ensure resp.Body is deferred closed as before.
- Add `template search` command to filter templates by keyword and sort by deployment count - Add `help --all` flag to print all commands and flags at once - Add CLAUDE.md with project conventions for AI-assisted development - Fix interactive prompt skipping when --code flag is provided Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/cmd/help/help.go (1)
71-76: Consider moving blank line inside the flags check.Line 75 always prints a blank line, even when there are no flags displayed. This creates extra spacing between commands that have no local flags.
Proposed fix
if len(flags) > 0 { fmt.Println(strings.Join(flags, "\n")) + fmt.Println() } - - fmt.Println() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cmd/help/help.go` around lines 71 - 76, The trailing blank line is printed unconditionally after printing flags; change the logic so the blank line is only printed when flags are shown by moving the fmt.Println() into the existing if len(flags) > 0 { ... } block (i.e., when printing flags via strings.Join(flags, "\n")). Update the block around the flags variable in help.go so no extra newline is emitted when len(flags) == 0.internal/cmd/template/search/search.go (1)
28-36: Keyword from positional arg and--keywordflag may conflict.When both a positional argument and the
--keywordflag are provided, the positional argument (Line 30) overwrites whatever was set by the flag binding (Line 36). This can lead to confusing behavior since cobra processes flags beforeRunE, meaningargs[0]always wins. Consider either:
- Removing the flag and using only the positional argument, or
- Checking if the flag was explicitly set and erroring on conflict.
Option 1: Use only positional argument (simpler)
func NewCmdSearch(f *cmdutil.Factory) *cobra.Command { opts := Options{} cmd := &cobra.Command{ Use: "search [keyword]", Short: "Search templates by keyword", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if len(args) > 0 { opts.keyword = args[0] } return runSearch(f, opts) }, } - cmd.Flags().StringVarP(&opts.keyword, "keyword", "k", "", "Keyword to search in template name and description") - return cmd }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/cmd/template/search/search.go` around lines 28 - 36, The command currently lets a positional arg override the bound flag because RunE sets opts.keyword = args[0] unconditionally; update the command to detect and resolve conflicts between the positional argument and the flag binding: either remove the flag binding (cmd.Flags().StringVarP & opts.keyword) and use only args[0] as the search keyword, or keep the flag and in RunE check whether the flag was explicitly set via cmd.Flags().Changed("keyword") and if both a flag and a positional arg are provided return an error (or prefer one behavior consistently) before calling runSearch(f, opts); modify the RunE handler and/or the flag registration accordingly to ensure opts.keyword is set deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@internal/cmd/help/help.go`:
- Around line 71-76: The trailing blank line is printed unconditionally after
printing flags; change the logic so the blank line is only printed when flags
are shown by moving the fmt.Println() into the existing if len(flags) > 0 { ...
} block (i.e., when printing flags via strings.Join(flags, "\n")). Update the
block around the flags variable in help.go so no extra newline is emitted when
len(flags) == 0.
In `@internal/cmd/template/search/search.go`:
- Around line 28-36: The command currently lets a positional arg override the
bound flag because RunE sets opts.keyword = args[0] unconditionally; update the
command to detect and resolve conflicts between the positional argument and the
flag binding: either remove the flag binding (cmd.Flags().StringVarP &
opts.keyword) and use only args[0] as the search keyword, or keep the flag and
in RunE check whether the flag was explicitly set via
cmd.Flags().Changed("keyword") and if both a flag and a positional arg are
provided return an error (or prefer one behavior consistently) before calling
runSearch(f, opts); modify the RunE handler and/or the flag registration
accordingly to ensure opts.keyword is set deterministically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/cmd/template/get/get.go`:
- Around line 48-56: After prompting for Template Code when opts.code is empty,
validate the entered value and reject empty input instead of letting it fall
through; call the existing paramCheck (or the same validation used for
non-interactive paths) on opts.code after f.Prompter.Input returns (or loop
re-prompt until non-empty) before calling getTemplate(f, opts) so interactive
and non-interactive flows share the same validation logic (refer to opts.code,
f.Prompter.Input, paramCheck, and getTemplate).
- Validate empty input in interactive mode for template get - Only print blank line after flags in help --all - Remove --keyword flag from search, use only positional argument Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@internal/cmd/template/search/search.go`:
- Around line 39-50: runSearch currently accepts an empty keyword when in
interactive mode because it doesn't validate the value returned by
f.Prompter.Input; after calling f.Prompter.Input in runSearch, check if the
returned keyword (opts.keyword) is empty and return an error (same message used
in non-interactive path, e.g. "keyword is required") instead of
proceeding—update the branch that sets opts.keyword from f.Prompter.Input in
runSearch to validate and return an error when the user presses Enter with no
input.
| func runSearch(f *cmdutil.Factory, opts Options) error { | ||
| if opts.keyword == "" { | ||
| if f.Interactive { | ||
| keyword, err := f.Prompter.Input("Search keyword: ", "") | ||
| if err != nil { | ||
| return err | ||
| } | ||
| opts.keyword = keyword | ||
| } else { | ||
| return fmt.Errorf("keyword is required") | ||
| } | ||
| } |
There was a problem hiding this comment.
Validate empty keyword after interactive prompt.
In interactive mode, if the user presses Enter without typing a keyword, the search proceeds with an empty string which matches all templates (since strings.Contains(x, "") is always true). This is inconsistent with non-interactive mode which requires a keyword.
Consider adding validation after the prompt, similar to how template get handles it:
💡 Suggested fix
func runSearch(f *cmdutil.Factory, opts Options) error {
if opts.keyword == "" {
if f.Interactive {
keyword, err := f.Prompter.Input("Search keyword: ", "")
if err != nil {
return err
}
opts.keyword = keyword
+ if opts.keyword == "" {
+ return fmt.Errorf("keyword is required")
+ }
} else {
return fmt.Errorf("keyword is required")
}
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| func runSearch(f *cmdutil.Factory, opts Options) error { | |
| if opts.keyword == "" { | |
| if f.Interactive { | |
| keyword, err := f.Prompter.Input("Search keyword: ", "") | |
| if err != nil { | |
| return err | |
| } | |
| opts.keyword = keyword | |
| } else { | |
| return fmt.Errorf("keyword is required") | |
| } | |
| } | |
| func runSearch(f *cmdutil.Factory, opts Options) error { | |
| if opts.keyword == "" { | |
| if f.Interactive { | |
| keyword, err := f.Prompter.Input("Search keyword: ", "") | |
| if err != nil { | |
| return err | |
| } | |
| opts.keyword = keyword | |
| if opts.keyword == "" { | |
| return fmt.Errorf("keyword is required") | |
| } | |
| } else { | |
| return fmt.Errorf("keyword is required") | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@internal/cmd/template/search/search.go` around lines 39 - 50, runSearch
currently accepts an empty keyword when in interactive mode because it doesn't
validate the value returned by f.Prompter.Input; after calling f.Prompter.Input
in runSearch, check if the returned keyword (opts.keyword) is empty and return
an error (same message used in non-interactive path, e.g. "keyword is required")
instead of proceeding—update the branch that sets opts.keyword from
f.Prompter.Input in runSearch to validate and return an error when the user
presses Enter with no input.
Summary
template get --rawflag to export raw YAML spec fromhttps://zeabur.com/templates/{code}.yaml, so users can save, modify, and redeploy templatestemplate searchcommand to filter templates by keyword (name/description) and sort by deployment counthelp --allflag to print all commands and flags at once, making it easy for AI agents to discover available CLI featurestemplate getinteractive prompt not being skipped when--codeflag is already providedCLAUDE.mdwith project conventions for AI-assisted developmentUsage
Test plan
zeabur template get -c <code>— shows table as beforezeabur template get -c <code> --raw— outputs raw YAMLzeabur template search <keyword>— filters and sorts by deployment countzeabur help --all— prints all commands with flagszeabur help— default help still works🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
--rawto view templates as streamed raw YAML.--alloption to print all commands and flags.Usability
Documentation