fix(cli): init prompts default to existing plugin.mod values
This commit is contained in:
parent
041a7c2e3f
commit
06cabd6eb9
@ -54,24 +54,48 @@ func newPluginInitCmd() *cobra.Command {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
scanner := bufio.NewScanner(os.Stdin)
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
if scope != "" {
|
// Read existing plugin.mod (if any) so prompts can default to its
|
||||||
|
// values. Missing or unparseable file is fine — we just start from
|
||||||
|
// empty defaults.
|
||||||
|
existing := &core.ModFile{}
|
||||||
|
if data, err := os.ReadFile("plugin.mod"); err == nil {
|
||||||
|
if m, err := core.ParseModFull(data); err == nil && m != nil {
|
||||||
|
existing = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scope precedence: --scope flag > plugin.mod scope > interactive prompt.
|
||||||
|
switch {
|
||||||
|
case scope != "":
|
||||||
scope, err = parseScope(scope)
|
scope, err = parseScope(scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
case existing.Plugin.Scope != "":
|
||||||
|
scope = "@" + strings.TrimPrefix(existing.Plugin.Scope, "@")
|
||||||
|
default:
|
||||||
scope, err = promptScope(ctx, cli, cr, resolvedHost, hc, scanner)
|
scope, err = promptScope(ctx, cli, cr, resolvedHost, hc, scanner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name precedence: --name flag > plugin.mod name > prompt.
|
||||||
if name == "" {
|
if name == "" {
|
||||||
|
if existing.Plugin.Name != "" {
|
||||||
|
fmt.Printf("Plugin name [%s]: ", existing.Plugin.Name)
|
||||||
|
} else {
|
||||||
fmt.Print("Plugin name: ")
|
fmt.Print("Plugin name: ")
|
||||||
|
}
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
return fmt.Errorf("cancelled")
|
return fmt.Errorf("cancelled")
|
||||||
}
|
}
|
||||||
name = strings.TrimSpace(scanner.Text())
|
input := strings.TrimSpace(scanner.Text())
|
||||||
|
if input != "" {
|
||||||
|
name = input
|
||||||
|
} else {
|
||||||
|
name = existing.Plugin.Name
|
||||||
|
}
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return fmt.Errorf("plugin name is required")
|
return fmt.Errorf("plugin name is required")
|
||||||
}
|
}
|
||||||
@ -79,28 +103,37 @@ func newPluginInitCmd() *cobra.Command {
|
|||||||
rawName := name
|
rawName := name
|
||||||
name = strings.ToLower(name)
|
name = strings.ToLower(name)
|
||||||
|
|
||||||
fmt.Printf("Display name [%s]: ", rawName)
|
displayDefault := existing.Plugin.DisplayName
|
||||||
displayName := ""
|
if displayDefault == "" {
|
||||||
if scanner.Scan() {
|
displayDefault = rawName
|
||||||
displayName = strings.TrimSpace(scanner.Text())
|
}
|
||||||
|
fmt.Printf("Display name [%s]: ", displayDefault)
|
||||||
|
displayName := displayDefault
|
||||||
|
if scanner.Scan() {
|
||||||
|
if v := strings.TrimSpace(scanner.Text()); v != "" {
|
||||||
|
displayName = v
|
||||||
}
|
}
|
||||||
if displayName == "" {
|
|
||||||
displayName = rawName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print("Description (one-line summary, optional): ")
|
descLabel := "Description (one-line summary, optional)"
|
||||||
description := ""
|
if existing.Plugin.Description != "" {
|
||||||
|
descLabel = fmt.Sprintf("Description [%s]", truncate(existing.Plugin.Description, 60))
|
||||||
|
}
|
||||||
|
fmt.Printf("%s: ", descLabel)
|
||||||
|
description := existing.Plugin.Description
|
||||||
if scanner.Scan() {
|
if scanner.Scan() {
|
||||||
description = strings.TrimSpace(scanner.Text())
|
if v := strings.TrimSpace(scanner.Text()); v != "" {
|
||||||
|
description = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kind, err := promptKind(scanner)
|
kind, err := promptKindWithDefault(scanner, existing.Plugin.Kind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var cats []string
|
var cats []string
|
||||||
if kind == "plugin" {
|
if kind == "plugin" {
|
||||||
cats, err = promptCategories(ctx, cli, scanner)
|
cats, err = promptCategoriesWithDefault(ctx, cli, scanner, existing.Plugin.Categories)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -233,15 +266,27 @@ func createScopeInline(ctx context.Context, cli *orchclient.Client, scanner *buf
|
|||||||
return slug, nil
|
return slug, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptKind(scanner *bufio.Scanner) (string, error) {
|
// promptKindWithDefault prompts for plugin kind. When current is "plugin" or
|
||||||
|
// "theme", that becomes the empty-input default (and the [n] hint reflects it).
|
||||||
|
func promptKindWithDefault(scanner *bufio.Scanner, current string) (string, error) {
|
||||||
|
defaultIdx := "1"
|
||||||
|
switch current {
|
||||||
|
case "theme":
|
||||||
|
defaultIdx = "2"
|
||||||
|
case "plugin", "":
|
||||||
|
defaultIdx = "1"
|
||||||
|
}
|
||||||
fmt.Println("Kind: 1) plugin 2) theme")
|
fmt.Println("Kind: 1) plugin 2) theme")
|
||||||
fmt.Print("Select [1]: ")
|
fmt.Printf("Select [%s]: ", defaultIdx)
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
return "", fmt.Errorf("cancelled")
|
return "", fmt.Errorf("cancelled")
|
||||||
}
|
}
|
||||||
v := strings.TrimSpace(scanner.Text())
|
v := strings.TrimSpace(scanner.Text())
|
||||||
|
if v == "" {
|
||||||
|
v = defaultIdx
|
||||||
|
}
|
||||||
switch v {
|
switch v {
|
||||||
case "", "1", "plugin":
|
case "1", "plugin":
|
||||||
return "plugin", nil
|
return "plugin", nil
|
||||||
case "2", "theme":
|
case "2", "theme":
|
||||||
return "theme", nil
|
return "theme", nil
|
||||||
@ -250,7 +295,20 @@ func promptKind(scanner *bufio.Scanner) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func promptCategories(ctx context.Context, cli *orchclient.Client, scanner *bufio.Scanner) ([]string, error) {
|
// truncate clips a string to at most max runes, appending "…" if it was cut.
|
||||||
|
// Used to keep prompt labels readable when the existing description is long.
|
||||||
|
func truncate(s string, max int) string {
|
||||||
|
if len([]rune(s)) <= max {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
r := []rune(s)
|
||||||
|
return string(r[:max-1]) + "…"
|
||||||
|
}
|
||||||
|
|
||||||
|
// promptCategoriesWithDefault lists the canonical categories and lets the user
|
||||||
|
// pick by number. When current is non-empty, those slugs are pre-selected:
|
||||||
|
// the prompt shows their numbers as the default, and empty input keeps them.
|
||||||
|
func promptCategoriesWithDefault(ctx context.Context, cli *orchclient.Client, scanner *bufio.Scanner, current []string) ([]string, error) {
|
||||||
resp, err := cli.Reg.ListCategories(ctx, connect.NewRequest(&v1.ListCategoriesRequest{}))
|
resp, err := cli.Reg.ListCategories(ctx, connect.NewRequest(&v1.ListCategoriesRequest{}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("list categories: %w", err)
|
return nil, fmt.Errorf("list categories: %w", err)
|
||||||
@ -259,17 +317,41 @@ func promptCategories(ctx context.Context, cli *orchclient.Client, scanner *bufi
|
|||||||
if len(cats) == 0 {
|
if len(cats) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
fmt.Println("Categories (comma-separated numbers, or blank to skip):")
|
// Build the default indices string from `current` so the prompt shows e.g.
|
||||||
|
// "Select [9]: " when current is ["templates"] and templates is the 9th
|
||||||
|
// entry.
|
||||||
|
slugToIdx := make(map[string]int, len(cats))
|
||||||
for i, c := range cats {
|
for i, c := range cats {
|
||||||
fmt.Printf(" %d. %s — %s\n", i+1, c.Slug, c.DisplayName)
|
slugToIdx[c.Slug] = i + 1
|
||||||
}
|
}
|
||||||
|
var defaultNums []string
|
||||||
|
for _, slug := range current {
|
||||||
|
if i, ok := slugToIdx[slug]; ok {
|
||||||
|
defaultNums = append(defaultNums, strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defaultLabel := strings.Join(defaultNums, ",")
|
||||||
|
|
||||||
|
fmt.Println("Categories (comma-separated numbers, or blank to keep current):")
|
||||||
|
for i, c := range cats {
|
||||||
|
marker := " "
|
||||||
|
if _, picked := pickedSet(current)[c.Slug]; picked {
|
||||||
|
marker = "*"
|
||||||
|
}
|
||||||
|
fmt.Printf(" %d.%s%s — %s\n", i+1, marker, c.Slug, c.DisplayName)
|
||||||
|
}
|
||||||
|
if defaultLabel != "" {
|
||||||
|
fmt.Printf("Select [%s]: ", defaultLabel)
|
||||||
|
} else {
|
||||||
fmt.Print("Select: ")
|
fmt.Print("Select: ")
|
||||||
|
}
|
||||||
if !scanner.Scan() {
|
if !scanner.Scan() {
|
||||||
return nil, fmt.Errorf("cancelled")
|
return nil, fmt.Errorf("cancelled")
|
||||||
}
|
}
|
||||||
raw := strings.TrimSpace(scanner.Text())
|
raw := strings.TrimSpace(scanner.Text())
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
return nil, nil
|
// Empty input → keep current (or none, if there's no current).
|
||||||
|
return current, nil
|
||||||
}
|
}
|
||||||
parts := strings.Split(raw, ",")
|
parts := strings.Split(raw, ",")
|
||||||
out := make([]string, 0, len(parts))
|
out := make([]string, 0, len(parts))
|
||||||
@ -283,6 +365,15 @@ func promptCategories(ctx context.Context, cli *orchclient.Client, scanner *bufi
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pickedSet returns a set of the given slugs for O(1) membership checks.
|
||||||
|
func pickedSet(slugs []string) map[string]struct{} {
|
||||||
|
s := make(map[string]struct{}, len(slugs))
|
||||||
|
for _, x := range slugs {
|
||||||
|
s[x] = struct{}{}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
func newPluginPublishCmd() *cobra.Command {
|
func newPluginPublishCmd() *cobra.Command {
|
||||||
var channel string
|
var channel string
|
||||||
var allowDirty bool
|
var allowDirty bool
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user