From bb55fbb3285e4c821796e79e9644b8112f6bfa2f Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Mon, 9 Mar 2026 05:45:58 +0100 Subject: [PATCH 1/2] feat: Enhance project creation UX with improved option descriptions and spinner support --- cmd/project/project_create.go | 77 +++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/cmd/project/project_create.go b/cmd/project/project_create.go index 730f32c8..5ae8a6bf 100644 --- a/cmd/project/project_create.go +++ b/cmd/project/project_create.go @@ -16,6 +16,7 @@ import ( "text/template" "charm.land/huh/v2" + "charm.land/huh/v2/spinner" "charm.land/lipgloss/v2" "github.com/shyim/go-version" "github.com/spf13/cobra" @@ -127,7 +128,7 @@ var projectCreateCmd = &cobra.Command{ huh.NewOption("None", packagist.DeploymentNone), huh.NewOption("PaaS powered by Shopware", packagist.DeploymentShopwarePaaS), huh.NewOption("PaaS powered by Platform.sh", packagist.DeploymentPlatformSH), - huh.NewOption("Deployer (SSH based zero-downtime)", packagist.DeploymentDeployer), + huh.NewOption("Deployer (SSH-based)", packagist.DeploymentDeployer), } ciOptions := []huh.Option[string]{ @@ -264,17 +265,17 @@ var projectCreateCmd = &cobra.Command{ } var optionalOptions []huh.Option[string] - if !cmd.PersistentFlags().Changed("git") { - optionalOptions = append(optionalOptions, huh.NewOption("Initialize Git Repository", optionGit).Selected(true)) - } if !cmd.PersistentFlags().Changed("docker") { - optionalOptions = append(optionalOptions, huh.NewOption("Local Docker Setup", optionDocker).Selected(true)) + optionalOptions = append(optionalOptions, huh.NewOption("Install Shopware with local Docker setup", optionDocker).Selected(true)) + } + if !cmd.PersistentFlags().Changed("git") { + optionalOptions = append(optionalOptions, huh.NewOption("Initialize Git repository", optionGit).Selected(true)) } if !cmd.PersistentFlags().Changed("with-amqp") { - optionalOptions = append(optionalOptions, huh.NewOption("AMQP Queue Support", optionAMQP).Selected(true)) + optionalOptions = append(optionalOptions, huh.NewOption("AMQP queue support (for background jobs and messaging)", optionAMQP).Selected(true)) } if !cmd.PersistentFlags().Changed("with-elasticsearch") { - optionalOptions = append(optionalOptions, huh.NewOption("Setup Elasticsearch/OpenSearch support", optionElasticsearch)) + optionalOptions = append(optionalOptions, huh.NewOption("Set up OpenSearch (for large catalogs and advanced search)", optionElasticsearch)) } if len(optionalOptions) > 0 { @@ -415,7 +416,13 @@ var projectCreateCmd = &cobra.Command{ return err } - if err := os.WriteFile(filepath.Join(projectFolder, ".env.local"), []byte(""), os.ModePerm); err != nil { + envLocalContent := "" + + if useDocker { + envLocalContent += "APP_ENV=dev\n" + } + + if err := os.WriteFile(filepath.Join(projectFolder, ".env.local"), []byte(envLocalContent), os.ModePerm); err != nil { return err } @@ -447,7 +454,10 @@ var projectCreateCmd = &cobra.Command{ logging.FromContext(cmd.Context()).Infof("Installing dependencies") - if err := runComposerInstall(cmd.Context(), projectFolder, useDocker); err != nil { + isVerbose, _ := cmd.Flags().GetBool("verbose") + showSpinner := system.IsInteractionEnabled(cmd.Context()) && !isVerbose + + if err := runComposerInstall(cmd.Context(), projectFolder, useDocker, showSpinner); err != nil { return err } @@ -471,6 +481,12 @@ var projectCreateCmd = &cobra.Command{ fmt.Printf(" %s %s\n", color.GreenText.Render("Setup Shopware:"), cmdStyle.Render("make setup")) fmt.Printf(" %s %s\n", color.GreenText.Render("Stop containers:"), cmdStyle.Render("make down")) fmt.Println() + fmt.Println(sectionStyle.Render("Access your shop")) + fmt.Println() + fmt.Printf(" %s %s\n", color.GreenText.Render("Storefront:"), cmdStyle.Render("http://127.0.0.1:8000")) + fmt.Printf(" %s %s\n", color.GreenText.Render("Admin:"), cmdStyle.Render("http://127.0.0.1:8000/admin")) + fmt.Printf(" %s %s / %s\n", color.GreenText.Render("Credentials:"), cmdStyle.Render("admin"), cmdStyle.Render("shopware")) + fmt.Println() } return nil @@ -564,7 +580,7 @@ func setupCI(projectFolder, ciSystem, deploymentMethod string) error { return nil } -func runComposerInstall(ctx context.Context, projectFolder string, useDocker bool) error { +func runComposerInstall(ctx context.Context, projectFolder string, useDocker bool, showSpinner bool) error { var cmdInstall *exec.Cmd if useDocker && !system.IsInsideContainer() { @@ -593,31 +609,40 @@ func runComposerInstall(ctx context.Context, projectFolder string, useDocker boo "composer", "install", "--no-interaction") cmdInstall = exec.CommandContext(ctx, "docker", dockerArgs...) + } else { + composerBinary, err := exec.LookPath("composer") + if err != nil { + return err + } + + phpBinary := os.Getenv("PHP_BINARY") + + if phpBinary != "" { + cmdInstall = exec.CommandContext(ctx, phpBinary, composerBinary, "install", "--no-interaction") + } else { + cmdInstall = exec.CommandContext(ctx, "composer", "install", "--no-interaction") + } + + cmdInstall.Dir = projectFolder + } + + if !showSpinner { + cmdInstall.Stdin = os.Stdin cmdInstall.Stdout = os.Stdout cmdInstall.Stderr = os.Stderr return cmdInstall.Run() } - composerBinary, err := exec.LookPath("composer") - if err != nil { - return err - } - - phpBinary := os.Getenv("PHP_BINARY") + var runErr error - if phpBinary != "" { - cmdInstall = exec.CommandContext(ctx, phpBinary, composerBinary, "install", "--no-interaction") - } else { - cmdInstall = exec.CommandContext(ctx, "composer", "install", "--no-interaction") + if err := spinner.New().Title("Installing dependencies").Action(func() { + runErr = cmdInstall.Run() + }).Run(); err != nil { + return err } - cmdInstall.Dir = projectFolder - cmdInstall.Stdin = os.Stdin - cmdInstall.Stdout = os.Stdout - cmdInstall.Stderr = os.Stderr - - return cmdInstall.Run() + return runErr } func getFilteredInstallVersions(ctx context.Context) ([]*version.Version, error) { From 5ebba2b45d92b79460411a2eae3e19661b40dbf9 Mon Sep 17 00:00:00 2001 From: Soner Sayakci Date: Mon, 9 Mar 2026 06:11:01 +0100 Subject: [PATCH 2/2] feat: Improve project creation UX by clarifying access instructions and enhancing error reporting during dependency installation --- cmd/project/project_create.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/project/project_create.go b/cmd/project/project_create.go index 5ae8a6bf..df4d4819 100644 --- a/cmd/project/project_create.go +++ b/cmd/project/project_create.go @@ -481,7 +481,7 @@ var projectCreateCmd = &cobra.Command{ fmt.Printf(" %s %s\n", color.GreenText.Render("Setup Shopware:"), cmdStyle.Render("make setup")) fmt.Printf(" %s %s\n", color.GreenText.Render("Stop containers:"), cmdStyle.Render("make down")) fmt.Println() - fmt.Println(sectionStyle.Render("Access your shop")) + fmt.Println(sectionStyle.Render("Access your shop (after make setup)")) fmt.Println() fmt.Printf(" %s %s\n", color.GreenText.Render("Storefront:"), cmdStyle.Render("http://127.0.0.1:8000")) fmt.Printf(" %s %s\n", color.GreenText.Render("Admin:"), cmdStyle.Render("http://127.0.0.1:8000/admin")) @@ -634,15 +634,26 @@ func runComposerInstall(ctx context.Context, projectFolder string, useDocker boo return cmdInstall.Run() } + var stdErr bytes.Buffer + cmdInstall.Stderr = &stdErr + var runErr error - if err := spinner.New().Title("Installing dependencies").Action(func() { + if err := spinner.New().Context(ctx).Title("Installing dependencies").Action(func() { runErr = cmdInstall.Run() }).Run(); err != nil { return err } - return runErr + if runErr != nil { + if stdErr.Len() > 0 { + fmt.Fprint(os.Stderr, stdErr.String()) + } + + return runErr + } + + return nil } func getFilteredInstallVersions(ctx context.Context) ([]*version.Version, error) {