diff --git a/.gitignore b/.gitignore index 660ecdae526..d55d25c0270 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ cli/azd/extensions/microsoft.azd.ai.builder/microsoft.azd.ai.builder cli/azd/extensions/microsoft.azd.ai.builder/microsoft.azd.ai.builder.exe cli/azd/extensions/microsoft.azd.demo/microsoft.azd.demo cli/azd/extensions/microsoft.azd.demo/microsoft.azd.demo.exe +cli/azd/azd-test diff --git a/cli/azd/cmd/root.go b/cli/azd/cmd/root.go index c317f7f99ba..c00ef586e31 100644 --- a/cli/azd/cmd/root.go +++ b/cli/azd/cmd/root.go @@ -26,6 +26,7 @@ import ( "github.com/azure/azure-dev/cli/azd/internal/cmd/show" "github.com/azure/azure-dev/cli/azd/internal/telemetry" "github.com/azure/azure-dev/cli/azd/pkg/output" + "github.com/azure/azure-dev/cli/azd/pkg/ux" "github.com/spf13/cobra" ) @@ -80,6 +81,44 @@ func NewRootCmd( prevDir = current + // Check if the directory exists + if _, err := os.Stat(opts.Cwd); os.IsNotExist(err) { + // Directory doesn't exist, prompt user to create it + shouldCreate := true // Default to Yes + + if !opts.NoPrompt { + // Prompt the user + defaultValue := true + confirm := ux.NewConfirm(&ux.ConfirmOptions{ + Message: fmt.Sprintf( + "Directory '%s' does not exist. Would you like to create it?", + opts.Cwd, + ), + DefaultValue: &defaultValue, + }) + + result, err := confirm.Ask(cmd.Context()) + if err != nil { + return fmt.Errorf("failed to prompt for directory creation: %w", err) + } + + if result == nil { + return fmt.Errorf("no response received for directory creation prompt") + } + + shouldCreate = *result + } + + if !shouldCreate { + return fmt.Errorf("directory '%s' does not exist and creation was declined", opts.Cwd) + } + + // Create the directory + if err := os.MkdirAll(opts.Cwd, 0755); err != nil { + return fmt.Errorf("failed to create directory '%s': %w", opts.Cwd, err) + } + } + if err := os.Chdir(opts.Cwd); err != nil { return fmt.Errorf("failed to change directory to %s: %w", opts.Cwd, err) } diff --git a/cli/azd/go.mod b/cli/azd/go.mod index e546991bbfa..f51c6a9552f 100644 --- a/cli/azd/go.mod +++ b/cli/azd/go.mod @@ -52,7 +52,6 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/joho/godotenv v1.5.1 - github.com/magefile/mage v1.15.0 github.com/mark3labs/mcp-go v0.41.1 github.com/mattn/go-colorable v0.1.14 github.com/mattn/go-isatty v0.0.20 diff --git a/cli/azd/go.sum b/cli/azd/go.sum index 9a9a668bb40..b245f675535 100644 --- a/cli/azd/go.sum +++ b/cli/azd/go.sum @@ -284,8 +284,6 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kUL github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mark3labs/mcp-go v0.41.1 h1:w78eWfiQam2i8ICL7AL0WFiq7KHNJQ6UB53ZVtH4KGA= diff --git a/cli/azd/test/functional/init_test.go b/cli/azd/test/functional/init_test.go index b284aabc9b7..f63ecc39857 100644 --- a/cli/azd/test/functional/init_test.go +++ b/cli/azd/test/functional/init_test.go @@ -291,6 +291,56 @@ func Test_CLI_Init_CanUseTemplate(t *testing.T) { require.FileExists(t, filepath.Join(dir, "README.md")) } +// Test_CLI_Init_WithCwdAutoCreate tests the automatic directory creation when using -C/--cwd flag. +func Test_CLI_Init_WithCwdAutoCreate(t *testing.T) { + tests := []struct { + name string + subDir string // subdirectory to create within temp dir (using -C flag) + args []string + }{ + { + name: "single level directory", + subDir: "new-project", + args: []string{"init", "-t", "azure-samples/todo-nodejs-mongo", "--no-prompt"}, + }, + { + name: "nested directory", + subDir: "parent/child/project", + args: []string{"init", "-t", "azure-samples/todo-nodejs-mongo", "--no-prompt"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx, cancel := newTestContext(t) + defer cancel() + + // Create a parent temp directory + parentDir := tempDirWithDiagnostics(t) + cli := azdcli.NewCLI(t) + cli.WorkingDirectory = parentDir + + // Add -C flag with the subdirectory path + targetDir := filepath.Join(parentDir, tt.subDir) + args := append([]string{"-C", tt.subDir}, tt.args...) + + // Directory should not exist before running the command + require.NoDirExists(t, targetDir) + + // Run the command + // Note: We expect an error because --no-prompt will fail on environment name prompt + // but the directory creation should succeed before that + cli.RunCommand(ctx, args...) + + // Verify the directory was created + require.DirExists(t, targetDir) + + // Verify that the template was initialized in the created directory + require.FileExists(t, filepath.Join(targetDir, azdcontext.ProjectFileName)) + }) + } +} + // verifyEnvInitialized is a helper function that returns a verification function. // This avoids duplicating the verification logic in the test table. func verifyEnvInitialized(envName string) func(t *testing.T, dir string) {