Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@ package cmd

import (
"fmt"
"os"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/spf13/viper"
_init "github.com/supabase/cli/internal/init"
"github.com/supabase/cli/internal/utils"
"golang.org/x/term"
)

var (
createVscodeSettings = new(bool)
createIntellijSettings = new(bool)
initInteractive bool
createVscodeSettings bool
createIntellijSettings bool
initParams = utils.InitParams{}

initCmd = &cobra.Command{
Expand All @@ -32,15 +35,24 @@ var (
}
},
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
fsys := afero.NewOsFs()
if !cmd.Flags().Changed("with-vscode-settings") && !cmd.Flags().Changed("with-vscode-workspace") {
createVscodeSettings = nil
interactive := initInteractive && term.IsTerminal(int(os.Stdin.Fd()))
if err := _init.Run(ctx, fsys, interactive, initParams); err != nil {
return err
}

if !cmd.Flags().Changed("with-intellij-settings") {
createIntellijSettings = nil
// Handle backwards compatibility flags
if createVscodeSettings {
if err := _init.WriteVscodeConfig(fsys); err != nil {
return err
}
}
if createIntellijSettings {
if err := _init.WriteIntelliJConfig(fsys); err != nil {
return err
}
}
return _init.Run(fsys, createVscodeSettings, createIntellijSettings, initParams)
return nil
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("Finished " + utils.Aqua("supabase init") + ".")
Expand All @@ -50,11 +62,15 @@ var (

func init() {
flags := initCmd.Flags()
flags.BoolVar(createVscodeSettings, "with-vscode-workspace", false, "Generate VS Code workspace.")
cobra.CheckErr(flags.MarkHidden("with-vscode-workspace"))
flags.BoolVar(createVscodeSettings, "with-vscode-settings", false, "Generate VS Code settings for Deno.")
flags.BoolVar(createIntellijSettings, "with-intellij-settings", false, "Generate IntelliJ IDEA settings for Deno.")
flags.BoolVarP(&initInteractive, "interactive", "i", false, "Enables interactive mode to configure IDE settings.")
flags.BoolVar(&initParams.UseOrioleDB, "use-orioledb", false, "Use OrioleDB storage engine for Postgres.")
flags.BoolVar(&initParams.Overwrite, "force", false, "Overwrite existing "+utils.ConfigPath+".")
// Backwards compatibility flags (hidden)
flags.BoolVar(&createVscodeSettings, "with-vscode-workspace", false, "Generate VS Code workspace.")
cobra.CheckErr(flags.MarkHidden("with-vscode-workspace"))
flags.BoolVar(&createVscodeSettings, "with-vscode-settings", false, "Generate VS Code settings for Deno.")
cobra.CheckErr(flags.MarkHidden("with-vscode-settings"))
flags.BoolVar(&createIntellijSettings, "with-intellij-settings", false, "Generate IntelliJ IDEA settings for Deno.")
cobra.CheckErr(flags.MarkHidden("with-intellij-settings"))
rootCmd.AddCommand(initCmd)
}
2 changes: 1 addition & 1 deletion internal/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func Run(ctx context.Context, starter StarterTemplate, fsys afero.Fs, options ..
if err := downloadSample(ctx, client, starter.Url, fsys); err != nil {
return err
}
} else if err := initBlank.Run(fsys, nil, nil, utils.InitParams{Overwrite: true}); err != nil {
} else if err := initBlank.Run(ctx, fsys, false, utils.InitParams{Overwrite: true}); err != nil {
return err
}
// 1. Login
Expand Down
42 changes: 8 additions & 34 deletions internal/functions/new/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/go-errors/errors"
"github.com/spf13/afero"
"github.com/supabase/cli/internal/functions/deploy"
_init "github.com/supabase/cli/internal/init"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/internal/utils/flags"
Expand Down Expand Up @@ -39,7 +40,12 @@ func Run(ctx context.Context, slug string, fsys afero.Fs) error {
if err := utils.ValidateFunctionSlug(slug); err != nil {
return err
}
isFirstFunction := isFirstFunctionCreation(fsys)
// Check if this is the first function being created
existingSlugs, err := deploy.GetFunctionSlugs(fsys)
if err != nil {
fmt.Fprintln(utils.GetDebugLogger(), err)
}
isFirstFunction := len(existingSlugs) == 0

// 2. Create new function.
funcDir := filepath.Join(utils.FunctionsDir, slug)
Expand All @@ -66,45 +72,13 @@ func Run(ctx context.Context, slug string, fsys afero.Fs) error {
fmt.Println("Created new Function at " + utils.Bold(funcDir))

if isFirstFunction {
if err := promptForIDESettings(ctx, fsys); err != nil {
if err := _init.PromptForIDESettings(ctx, fsys); err != nil {
return err
}
}
return nil
}

// Checks if this is the first function being created.
// Returns true if the functions directory doesn't exist or is empty.
func isFirstFunctionCreation(fsys afero.Fs) bool {
entries, err := afero.ReadDir(fsys, utils.FunctionsDir)
if err != nil {
// Directory doesn't exist, this is the first function
return true
}
// Check if there are any subdirectories (existing functions)
for _, entry := range entries {
if entry.IsDir() {
return false
}
}
return true
}

func promptForIDESettings(ctx context.Context, fsys afero.Fs) error {
console := utils.NewConsole()
if isVscode, err := console.PromptYesNo(ctx, "Generate VS Code settings for Deno?", true); err != nil {
return err
} else if isVscode {
return _init.WriteVscodeConfig(fsys)
}
if isIntelliJ, err := console.PromptYesNo(ctx, "Generate IntelliJ IDEA settings for Deno?", false); err != nil {
return err
} else if isIntelliJ {
return _init.WriteIntelliJConfig(fsys)
}
return nil
}

func createEntrypointFile(slug string, fsys afero.Fs) error {
entrypointPath := filepath.Join(utils.FunctionsDir, slug, "index.ts")
f, err := fsys.OpenFile(entrypointPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
Expand Down
49 changes: 0 additions & 49 deletions internal/functions/new/new_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,52 +60,3 @@ func TestNewCommand(t *testing.T) {
assert.Error(t, Run(context.Background(), "test-func", fsys))
})
}

func TestIsFirstFunctionCreation(t *testing.T) {
t.Run("returns true when functions directory does not exist", func(t *testing.T) {
// Setup in-memory fs without functions directory
fsys := afero.NewMemMapFs()
// Run test
assert.True(t, isFirstFunctionCreation(fsys))
})

t.Run("returns true when functions directory is empty", func(t *testing.T) {
// Setup in-memory fs with empty functions directory
fsys := afero.NewMemMapFs()
require.NoError(t, fsys.MkdirAll(utils.FunctionsDir, 0755))
// Run test
assert.True(t, isFirstFunctionCreation(fsys))
})

t.Run("returns true when functions directory has only files", func(t *testing.T) {
// Setup in-memory fs with functions directory containing only files
fsys := afero.NewMemMapFs()
require.NoError(t, fsys.MkdirAll(utils.FunctionsDir, 0755))
// Create files (not directories) in functions directory
require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.FunctionsDir, "import_map.json"), []byte("{}"), 0644))
require.NoError(t, afero.WriteFile(fsys, filepath.Join(utils.FunctionsDir, ".env"), []byte(""), 0644))
// Run test
assert.True(t, isFirstFunctionCreation(fsys))
})

t.Run("returns false when functions directory has subdirectories", func(t *testing.T) {
// Setup in-memory fs with existing function
fsys := afero.NewMemMapFs()
existingFuncDir := filepath.Join(utils.FunctionsDir, "existing-func")
require.NoError(t, fsys.MkdirAll(existingFuncDir, 0755))
require.NoError(t, afero.WriteFile(fsys, filepath.Join(existingFuncDir, "index.ts"), []byte(""), 0644))
// Run test
assert.False(t, isFirstFunctionCreation(fsys))
})

t.Run("returns false when multiple functions exist", func(t *testing.T) {
// Setup in-memory fs with multiple existing functions
fsys := afero.NewMemMapFs()
func1Dir := filepath.Join(utils.FunctionsDir, "func1")
func2Dir := filepath.Join(utils.FunctionsDir, "func2")
require.NoError(t, fsys.MkdirAll(func1Dir, 0755))
require.NoError(t, fsys.MkdirAll(func2Dir, 0755))
// Run test
assert.False(t, isFirstFunctionCreation(fsys))
})
}
28 changes: 22 additions & 6 deletions internal/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package init

import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -32,7 +33,7 @@ var (
intelliJDeno string
)

func Run(fsys afero.Fs, createVscodeSettings, createIntellijSettings *bool, params utils.InitParams) error {
func Run(ctx context.Context, fsys afero.Fs, interactive bool, params utils.InitParams) error {
// 1. Write `config.toml`.
if err := utils.InitConfig(params, fsys); err != nil {
if errors.Is(err, os.ErrExist) {
Expand All @@ -48,11 +49,26 @@ func Run(fsys afero.Fs, createVscodeSettings, createIntellijSettings *bool, para
}
}

// 3. Generate VS Code or IntelliJ settings if explicitly requested via flags.
if createVscodeSettings != nil && *createVscodeSettings {
// 3. Prompt for IDE settings in interactive mode.
if interactive {
if err := PromptForIDESettings(ctx, fsys); err != nil {
return err
}
}
return nil
}

// PromptForIDESettings prompts the user to generate IDE settings for Deno.
func PromptForIDESettings(ctx context.Context, fsys afero.Fs) error {
console := utils.NewConsole()
if isVscode, err := console.PromptYesNo(ctx, "Generate VS Code settings for Deno?", true); err != nil {
return err
} else if isVscode {
return WriteVscodeConfig(fsys)
}
if createIntellijSettings != nil && *createIntellijSettings {
if isIntelliJ, err := console.PromptYesNo(ctx, "Generate IntelliJ IDEA settings for Deno?", false); err != nil {
return err
} else if isIntelliJ {
return WriteIntelliJConfig(fsys)
}
return nil
Expand Down Expand Up @@ -142,7 +158,7 @@ func WriteVscodeConfig(fsys afero.Fs) error {
return err
}
fmt.Println("Generated VS Code settings in " + utils.Bold(settingsPath) + ".")
fmt.Println("Please install the Deno extension for VS Code: " + utils.Aqua("https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno"))
fmt.Println("Please install the Deno extension for VS Code: " + utils.Bold("https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno"))
return nil
}

Expand All @@ -151,6 +167,6 @@ func WriteIntelliJConfig(fsys afero.Fs) error {
return err
}
fmt.Println("Generated IntelliJ settings in " + utils.Bold(denoPath) + ".")
fmt.Println("Please install the Deno plugin for IntelliJ: " + utils.Aqua("https://plugins.jetbrains.com/plugin/14382-deno"))
fmt.Println("Please install the Deno plugin for IntelliJ: " + utils.Bold("https://plugins.jetbrains.com/plugin/14382-deno"))
return nil
}
62 changes: 6 additions & 56 deletions internal/init/init_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package init

import (
"context"
"encoding/json"
"os"
"testing"
Expand All @@ -10,16 +11,15 @@ import (
"github.com/stretchr/testify/require"
"github.com/supabase/cli/internal/testing/fstest"
"github.com/supabase/cli/internal/utils"
"github.com/supabase/cli/pkg/cast"
)

func TestInitCommand(t *testing.T) {
t.Run("creates config file", func(t *testing.T) {
// Setup in-memory fs
fsys := &afero.MemMapFs{}
require.NoError(t, fsys.Mkdir(".git", 0755))
// Run test
assert.NoError(t, Run(fsys, nil, nil, utils.InitParams{}))
// Run test (non-interactive mode)
assert.NoError(t, Run(context.Background(), fsys, false, utils.InitParams{}))
// Validate generated config.toml
exists, err := afero.Exists(fsys, utils.ConfigPath)
assert.NoError(t, err)
Expand Down Expand Up @@ -47,14 +47,14 @@ func TestInitCommand(t *testing.T) {
_, err := fsys.Create(utils.ConfigPath)
require.NoError(t, err)
// Run test
assert.Error(t, Run(fsys, nil, nil, utils.InitParams{}))
assert.Error(t, Run(context.Background(), fsys, false, utils.InitParams{}))
})

t.Run("throws error on permission denied", func(t *testing.T) {
// Setup in-memory fs
fsys := &fstest.OpenErrorFs{DenyPath: utils.ConfigPath}
// Run test
err := Run(fsys, nil, nil, utils.InitParams{})
err := Run(context.Background(), fsys, false, utils.InitParams{})
// Check error
assert.ErrorIs(t, err, os.ErrPermission)
})
Expand All @@ -63,57 +63,7 @@ func TestInitCommand(t *testing.T) {
// Setup read-only fs
fsys := afero.NewReadOnlyFs(afero.NewMemMapFs())
// Run test
assert.Error(t, Run(fsys, nil, nil, utils.InitParams{}))
})

t.Run("creates vscode settings file", func(t *testing.T) {
// Setup in-memory fs
fsys := &afero.MemMapFs{}
// Run test
assert.NoError(t, Run(fsys, cast.Ptr(true), nil, utils.InitParams{}))
// Validate generated vscode settings
exists, err := afero.Exists(fsys, settingsPath)
assert.NoError(t, err)
assert.True(t, exists)
exists, err = afero.Exists(fsys, extensionsPath)
assert.NoError(t, err)
assert.True(t, exists)
})

t.Run("does not create vscode settings file", func(t *testing.T) {
// Setup in-memory fs
fsys := &afero.MemMapFs{}
// Run test
assert.NoError(t, Run(fsys, cast.Ptr(false), nil, utils.InitParams{}))
// Validate vscode settings file isn't generated
exists, err := afero.Exists(fsys, settingsPath)
assert.NoError(t, err)
assert.False(t, exists)
exists, err = afero.Exists(fsys, extensionsPath)
assert.NoError(t, err)
assert.False(t, exists)
})

t.Run("creates intellij deno file", func(t *testing.T) {
// Setup in-memory fs
fsys := &afero.MemMapFs{}
// Run test
assert.NoError(t, Run(fsys, nil, cast.Ptr(true), utils.InitParams{}))
// Validate generated intellij deno config
exists, err := afero.Exists(fsys, denoPath)
assert.NoError(t, err)
assert.True(t, exists)
})

t.Run("does not create intellij deno file", func(t *testing.T) {
// Setup in-memory fs
fsys := &afero.MemMapFs{}
// Run test
assert.NoError(t, Run(fsys, nil, cast.Ptr(false), utils.InitParams{}))
// Validate intellij deno config file isn't generated
exists, err := afero.Exists(fsys, denoPath)
assert.NoError(t, err)
assert.False(t, exists)
assert.Error(t, Run(context.Background(), fsys, false, utils.InitParams{}))
})
}

Expand Down