Repository: compose-spec/compose-go
Pull request #189 (2021-09-23 09:28:33)

Fix project name normalization
- Allow numbers as first char
- Normalize also names coming from options and env

Note that this still strips other chars like '-' and '_' if located at the beginning of the project name to avoid https://github.com/docker/compose-cli/issues/2022

Resolves https://github.com/docker/for-mac/issues/5974


Patch:
diff --git a/cli/options.go b/cli/options.go
index da751fe..7a610df 100644
--- a/cli/options.go
+++ b/cli/options.go
@@ -332,9 +332,9 @@ func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
 		} else if nameFromEnv, ok := options.Environment[ComposeProjectName]; ok && nameFromEnv != "" {
 			opts.Name = nameFromEnv
 		} else {
-			opts.Name = regexp.MustCompile(`(?m)[a-z]+[-_a-z0-9]*`).FindString(strings.ToLower(filepath.Base(absWorkingDir)))
+			opts.Name = filepath.Base(absWorkingDir)
 		}
-		opts.Name = strings.ToLower(opts.Name)
+		opts.Name = regexp.MustCompile(`(?m)[a-z0-9]+[-_a-z0-9]*`).FindString(strings.ToLower(opts.Name))
 	}
 	options.loadOptions = append(options.loadOptions, nameLoadOpt)
 


Test patch:
diff --git a/cli/options_test.go b/cli/options_test.go
index f9a54c6..80b0fcf 100644
--- a/cli/options_test.go
+++ b/cli/options_test.go
@@ -17,6 +17,7 @@
 package cli
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
 	"testing"
@@ -33,6 +34,54 @@ func TestProjectName(t *testing.T) {
 		assert.Equal(t, p.Name, "my_project")
 	})
 
+	t.Run("by name start with number", func(t *testing.T) {
+		opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithName("42my_project_num"))
+		assert.NilError(t, err)
+		p, err := ProjectFromOptions(opts)
+		assert.NilError(t, err)
+		assert.Equal(t, p.Name, "42my_project_num")
+
+		opts, err = NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithEnv([]string{
+			fmt.Sprintf("%s=%s", ComposeProjectName, "42my_project_env"),
+		}))
+		assert.NilError(t, err)
+		p, err = ProjectFromOptions(opts)
+		assert.NilError(t, err)
+		assert.Equal(t, p.Name, "42my_project_env")
+	})
+
+	t.Run("by name start with invalid char '-'", func(t *testing.T) {
+		opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithName("-my_project"))
+		assert.NilError(t, err)
+		p, err := ProjectFromOptions(opts)
+		assert.NilError(t, err)
+		assert.Equal(t, p.Name, "my_project")
+
+		opts, err = NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithEnv([]string{
+			fmt.Sprintf("%s=%s", ComposeProjectName, "-my_project"),
+		}))
+		assert.NilError(t, err)
+		p, err = ProjectFromOptions(opts)
+		assert.NilError(t, err)
+		assert.Equal(t, p.Name, "my_project")
+	})
+
+	t.Run("by name start with invalid char '_'", func(t *testing.T) {
+		opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithName("_my_project"))
+		assert.NilError(t, err)
+		p, err := ProjectFromOptions(opts)
+		assert.NilError(t, err)
+		assert.Equal(t, p.Name, "my_project")
+
+		opts, err = NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithEnv([]string{
+			fmt.Sprintf("%s=%s", ComposeProjectName, "_my_project"),
+		}))
+		assert.NilError(t, err)
+		p, err = ProjectFromOptions(opts)
+		assert.NilError(t, err)
+		assert.Equal(t, p.Name, "my_project")
+	})
+
 	t.Run("by working dir", func(t *testing.T) {
 		opts, err := NewProjectOptions([]string{"testdata/simple/compose.yaml"}, WithWorkingDirectory("."))
 		assert.NilError(t, err)