Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement quick start templates for cli bootstrap #2076

Merged
merged 12 commits into from
Mar 26, 2024
4 changes: 0 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,3 @@ issues:
- linters:
- stylecheck
text: "ST1003:"
- linters:
- staticcheck
- stylecheck
text: "ST1005:"
87 changes: 87 additions & 0 deletions cmd/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package cmd

import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/supabase/cli/internal/bootstrap"
"github.com/supabase/cli/internal/utils"
)

var (
templateUrl string

bootstrapCmd = &cobra.Command{
GroupID: groupQuickStart,
Use: "bootstrap [template]",
Short: "Bootstrap a Supabase project from a starter template",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if !viper.IsSet("WORKDIR") {
title := fmt.Sprintf("Enter a directory to bootstrap your project (or leave blank to use %s): ", utils.Bold(utils.CurrentDirAbs))
workdir, err := utils.PromptText(title, os.Stdin)
if err != nil {
return err
}
if len(workdir) == 0 {
workdir = utils.CurrentDirAbs
} else if !filepath.IsAbs(workdir) {
workdir = filepath.Join(utils.CurrentDirAbs, workdir)
}
viper.Set("WORKDIR", workdir)
}
ctx, _ := signal.NotifyContext(cmd.Context(), os.Interrupt)
client := bootstrap.GetGtihubClient(ctx)
templates, err := bootstrap.ListSamples(ctx, client)
if err != nil {
return err
}
if len(args) > 0 {
name := strings.ToLower(args[0])
for _, t := range templates {
if t.Name == name {
templateUrl = t.Url
}
}
}
if len(templateUrl) == 0 {
if err := promptStarterTemplate(ctx, templates); err != nil {
return err
}
}
return bootstrap.Run(ctx, templateUrl, afero.NewOsFs())
},
}
)

func init() {
bootstrapFlags := bootstrapCmd.Flags()
bootstrapFlags.StringP("password", "p", "", "Password to your remote Postgres database.")
cobra.CheckErr(viper.BindPFlag("DB_PASSWORD", bootstrapFlags.Lookup("password")))
rootCmd.AddCommand(bootstrapCmd)
}

func promptStarterTemplate(ctx context.Context, templates []bootstrap.StarterTemplate) error {
items := make([]utils.PromptItem, len(templates))
for i, t := range templates {
items[i] = utils.PromptItem{
Index: i,
Summary: t.Name,
Details: t.Description,
}
}
title := "Which starter template do you want to use?"
choice, err := utils.PromptChoice(ctx, title, items)
if err != nil {
return err
}
templateUrl = templates[choice.Index].Url
return nil
}
2 changes: 1 addition & 1 deletion cmd/gen.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package cmd

import (
"errors"
"os"
"os/signal"

env "github.com/Netflix/go-env"
"github.com/go-errors/errors"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/supabase/cli/internal/gen/keys"
Expand Down
17 changes: 1 addition & 16 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"io"
"os"
"os/user"
"strings"
"time"

Expand All @@ -22,20 +21,6 @@ var (
ErrMissingToken = errors.Errorf("Cannot use automatic login flow inside non-TTY environments. Please provide %s flag or set the %s environment variable.", utils.Aqua("--token"), utils.Aqua("SUPABASE_ACCESS_TOKEN"))
)

func generateTokenName() (string, error) {
user, err := user.Current()
if err != nil {
return "", errors.Errorf("cannot retrieve username: %w", err)
}

hostname, err := os.Hostname()
if err != nil {
return "", errors.Errorf("cannot retrieve hostname: %w", err)
}

return fmt.Sprintf("cli_%s@%s_%d", user.Username, hostname, time.Now().Unix()), nil
}

var (
loginCmd = &cobra.Command{
GroupID: groupLocalDev,
Expand Down Expand Up @@ -86,7 +71,7 @@ var (
}
params.TokenName = name
} else {
name, err := generateTokenName()
name, err := login.GenerateTokenName()
if err != nil {
params.TokenName = fmt.Sprintf("cli_%d", time.Now().Unix())
} else {
Expand Down
67 changes: 0 additions & 67 deletions cmd/projects.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package cmd

import (
"errors"
"fmt"
"os"
"sort"
"strings"

"github.com/spf13/afero"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -58,9 +55,6 @@ var (
if len(args) > 0 {
projectName = args[0]
}
if interactive {
cobra.CheckErr(PromptCreateFlags(cmd))
}
return create.Run(cmd.Context(), api.CreateProjectBody{
Name: projectName,
OrganizationId: orgId,
Expand Down Expand Up @@ -141,64 +135,3 @@ func init() {
projectsCmd.AddCommand(projectsApiKeysCmd)
rootCmd.AddCommand(projectsCmd)
}

func PromptCreateFlags(cmd *cobra.Command) error {
ctx := cmd.Context()
if len(projectName) > 0 {
fmt.Fprintln(os.Stderr, printKeyValue("Creating project", projectName))
} else {
name, err := utils.PromptText("Enter your project name: ", os.Stdin)
if err != nil {
return err
}
if len(name) == 0 {
return errors.New("project name cannot be empty")
}
projectName = name
}
if !cmd.Flags().Changed("org-id") {
title := "Which organisation do you want to create the project for?"
resp, err := utils.GetSupabase().GetOrganizationsWithResponse(ctx)
if err != nil {
return err
}
if resp.JSON200 == nil {
return errors.New("Unexpected error retrieving organizations: " + string(resp.Body))
}
items := make([]utils.PromptItem, len(*resp.JSON200))
for i, org := range *resp.JSON200 {
items[i] = utils.PromptItem{Summary: org.Name, Details: org.Id}
}
choice, err := utils.PromptChoice(ctx, title, items)
if err != nil {
return err
}
orgId = choice.Details
}
fmt.Fprintln(os.Stderr, printKeyValue("Selected org-id", orgId))
if !cmd.Flags().Changed("region") {
title := "Which region do you want to host the project in?"
items := make([]utils.PromptItem, len(utils.RegionMap))
i := 0
for k, v := range utils.RegionMap {
items[i] = utils.PromptItem{Summary: k, Details: v}
i++
}
choice, err := utils.PromptChoice(ctx, title, items)
if err != nil {
return err
}
region.Value = choice.Summary
}
fmt.Fprintln(os.Stderr, printKeyValue("Selected region", region.Value))
if dbPassword == "" {
dbPassword = flags.PromptPassword(os.Stdin)
}
return nil
}

func printKeyValue(key, value string) string {
indent := 20 - len(key)
spaces := strings.Repeat(" ", indent)
return key + ":" + spaces + value
}
17 changes: 4 additions & 13 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"errors"
"fmt"
"net"
"net/url"
Expand All @@ -11,6 +10,7 @@ import (
"time"

"github.com/getsentry/sentry-go"
"github.com/go-errors/errors"
"github.com/mitchellh/mapstructure"
"github.com/spf13/afero"
"github.com/spf13/cobra"
Expand All @@ -21,6 +21,7 @@ import (
)

const (
groupQuickStart = "quick-start"
groupLocalDev = "local-dev"
groupManagementAPI = "management-api"
)
Expand Down Expand Up @@ -92,7 +93,7 @@ var (
cmd.SilenceUsage = true
// Change workdir
fsys := afero.NewOsFs()
if err := changeWorkDir(fsys); err != nil {
if err := utils.ChangeWorkDir(fsys); err != nil {
return err
}
// Add common flags
Expand Down Expand Up @@ -198,6 +199,7 @@ func init() {
cobra.CheckErr(viper.BindPFlags(flags))

rootCmd.SetVersionTemplate("{{.Version}}\n")
rootCmd.AddGroup(&cobra.Group{ID: groupQuickStart, Title: "Quick Start:"})
rootCmd.AddGroup(&cobra.Group{ID: groupLocalDev, Title: "Local Development:"})
rootCmd.AddGroup(&cobra.Group{ID: groupManagementAPI, Title: "Management APIs:"})
}
Expand All @@ -208,17 +210,6 @@ func GetRootCmd() *cobra.Command {
return rootCmd
}

func changeWorkDir(fsys afero.Fs) error {
workdir := viper.GetString("WORKDIR")
if workdir == "" {
var err error
if workdir, err = utils.GetProjectRoot(fsys); err != nil {
return err
}
}
return os.Chdir(workdir)
}

func addSentryScope(scope *sentry.Scope) {
serviceImages := services.GetServiceImages()
imageToVersion := make(map[string]interface{}, len(serviceImages))
Expand Down
Loading