Pre-existing CLI improvements ahead of the tarball-publish refactor: - New top-level `ninja scope` command (create, list, set-default). - `init` accepts no --scope: prompts from ListMyScopes or uses creds default. - Plugin name prompted if not provided. - `plugin bump <major|minor|patch>` writes the bumped version into plugin.mod. - `plugin version` prints the current plugin.mod version. - `login` prints a URL with ?user_code= so the link is one click. - creds: HostCreds gains optional default_scope. - plugin/version: ParseBaseSemver + BumpVersion helpers, with tests.
231 lines
5.6 KiB
Go
231 lines
5.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"connectrpc.com/connect"
|
|
"github.com/spf13/cobra"
|
|
|
|
"git.dev.alexdunmow.com/block/core/cmd/ninja/internal/creds"
|
|
"git.dev.alexdunmow.com/block/core/cmd/ninja/internal/orchclient"
|
|
v1 "git.dev.alexdunmow.com/block/core/internal/api/orchestrator/v1"
|
|
)
|
|
|
|
func newScopeCmd() *cobra.Command {
|
|
c := &cobra.Command{Use: "scope", Short: "Manage plugin scopes"}
|
|
c.AddCommand(newScopeCreateCmd(), newScopeListCmd(), newScopeDefaultCmd())
|
|
return c
|
|
}
|
|
|
|
func newScopeCreateCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "create [scope]",
|
|
Short: "Create a new scope (organisation namespace for plugins)",
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: func(c *cobra.Command, args []string) error {
|
|
host, _ := c.Flags().GetString("host")
|
|
cr, err := creds.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resolvedHost, hc, err := cr.Resolve(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cli := orchclient.New(resolvedHost, hc.Token)
|
|
ctx := context.Background()
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
var slug string
|
|
if len(args) > 0 {
|
|
slug, err = parseScope(args[0])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
slug, err = promptScopeSlug(scanner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Display name [%s]: ", scopeAPISlug(slug))
|
|
displayName := scopeAPISlug(slug)
|
|
if scanner.Scan() {
|
|
if v := strings.TrimSpace(scanner.Text()); v != "" {
|
|
displayName = v
|
|
}
|
|
}
|
|
|
|
_, err = cli.Scope.CreateScope(ctx, connect.NewRequest(&v1.CreateScopeRequest{
|
|
Slug: scopeAPISlug(slug),
|
|
DisplayName: displayName,
|
|
}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Created scope %s\n", slug)
|
|
|
|
fmt.Printf("Set %s as your default scope? [Y/n]: ", slug)
|
|
if scanner.Scan() {
|
|
ans := strings.ToLower(strings.TrimSpace(scanner.Text()))
|
|
if ans == "" || ans == "y" || ans == "yes" {
|
|
hc.DefaultScope = slug
|
|
cr.Hosts[resolvedHost] = hc
|
|
if err := cr.Save(); err != nil {
|
|
fmt.Fprintf(os.Stderr, "warning: could not save default scope: %v\n", err)
|
|
} else {
|
|
fmt.Println("Default scope saved.")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
return cmd
|
|
}
|
|
|
|
func promptScopeSlug(scanner *bufio.Scanner) (string, error) {
|
|
fmt.Println("A scope is an organisation namespace for your plugins (e.g. @acme).")
|
|
fmt.Println("It appears in plugin names like @acme/my-plugin.")
|
|
fmt.Println()
|
|
fmt.Print("Scope slug (lowercase letters, numbers, dashes): ")
|
|
if !scanner.Scan() {
|
|
return "", fmt.Errorf("cancelled")
|
|
}
|
|
return parseScope(scanner.Text())
|
|
}
|
|
|
|
func newScopeDefaultCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "default",
|
|
Short: "Show or change the default scope",
|
|
RunE: func(c *cobra.Command, _ []string) error {
|
|
host, _ := c.Flags().GetString("host")
|
|
cr, err := creds.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, hc, err := cr.Resolve(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if hc.DefaultScope == "" {
|
|
fmt.Println("No default scope set. Run: ninja scope default set")
|
|
} else {
|
|
fmt.Println(hc.DefaultScope)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
cmd.AddCommand(newScopeDefaultSetCmd())
|
|
return cmd
|
|
}
|
|
|
|
func newScopeDefaultSetCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "set",
|
|
Short: "Pick a default scope from your scopes",
|
|
RunE: func(c *cobra.Command, _ []string) error {
|
|
host, _ := c.Flags().GetString("host")
|
|
cr, err := creds.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resolvedHost, hc, err := cr.Resolve(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cli := orchclient.New(resolvedHost, hc.Token)
|
|
ctx := context.Background()
|
|
|
|
scopes, err := cli.Scope.ListMyScopes(ctx, connect.NewRequest(&v1.ListMyScopesRequest{}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(scopes.Msg.Scopes) == 0 {
|
|
fmt.Println("No scopes yet. Create one with: ninja scope create")
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("Your scopes:")
|
|
for i, s := range scopes.Msg.Scopes {
|
|
marker := ""
|
|
if "@"+s.Slug == hc.DefaultScope {
|
|
marker = " (current)"
|
|
}
|
|
fmt.Printf(" %d. @%s — %s%s\n", i+1, s.Slug, s.DisplayName, marker)
|
|
}
|
|
fmt.Println()
|
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
fmt.Print("Select a scope: ")
|
|
if !scanner.Scan() {
|
|
return fmt.Errorf("cancelled")
|
|
}
|
|
input := strings.TrimSpace(scanner.Text())
|
|
if input == "" {
|
|
return fmt.Errorf("cancelled")
|
|
}
|
|
|
|
var scope string
|
|
if n, err := strconv.Atoi(input); err == nil && n >= 1 && n <= len(scopes.Msg.Scopes) {
|
|
scope = "@" + scopes.Msg.Scopes[n-1].Slug
|
|
} else {
|
|
return fmt.Errorf("invalid selection: %s", input)
|
|
}
|
|
|
|
hc.DefaultScope = scope
|
|
cr.Hosts[resolvedHost] = hc
|
|
if err := cr.Save(); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Default scope set to %s\n", scope)
|
|
return nil
|
|
},
|
|
}
|
|
}
|
|
|
|
func newScopeListCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "list",
|
|
Short: "List your scopes",
|
|
RunE: func(c *cobra.Command, _ []string) error {
|
|
host, _ := c.Flags().GetString("host")
|
|
cr, err := creds.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resolvedHost, hc, err := cr.Resolve(host)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cli := orchclient.New(resolvedHost, hc.Token)
|
|
ctx := context.Background()
|
|
|
|
scopes, err := cli.Scope.ListMyScopes(ctx, connect.NewRequest(&v1.ListMyScopesRequest{}))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(scopes.Msg.Scopes) == 0 {
|
|
fmt.Println("No scopes yet. Create one with: ninja scope create")
|
|
return nil
|
|
}
|
|
for _, s := range scopes.Msg.Scopes {
|
|
marker := ""
|
|
if "@"+s.Slug == hc.DefaultScope {
|
|
marker = " (default)"
|
|
}
|
|
fmt.Printf("@%s — %s%s\n", s.Slug, s.DisplayName, marker)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|