Skip to content

Commit 31fdead

Browse files
committed
refactor: simplify project directory name normalization
Preserve original casing and path-safe characters (letters, digits, dashes, underscores, dots) in project directory names. Only spaces are replaced with dashes and shell-unsafe characters are removed.
1 parent c693443 commit 31fdead

2 files changed

Lines changed: 31 additions & 24 deletions

File tree

internal/pkg/create/create.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,21 +171,20 @@ func Create(ctx context.Context, clients *shared.ClientFactory, createArgs Creat
171171
// multiDashRe matches consecutive dashes.
172172
var multiDashRe = regexp.MustCompile(`-{2,}`)
173173

174-
// nonAlphanumericRe matches any character that is not a lowercase letter, digit, or dash.
175-
var nonAlphanumericRe = regexp.MustCompile(`[^a-z0-9-]+`)
174+
// nonPathSafeRe matches characters that are not safe for file paths (not alphanumeric, dash, underscore, or dot).
175+
var nonPathSafeRe = regexp.MustCompile(`[^a-zA-Z0-9._-]+`)
176176

177-
// getAppDirName will validate and return the app's directory name in kebab-case
177+
// getAppDirName will validate and return the app's directory name safe for file paths
178178
func getAppDirName(appName string) (string, error) {
179179
if len(appName) <= 0 {
180180
return "", fmt.Errorf("app name is required")
181181
}
182182

183-
// Normalize to a variation of kebab-case: replace non-alphanumeric with dashes, collapse, and trim
183+
// Normalize: trim whitespace, replace spaces with dashes, remove unsafe characters
184184
appName = strings.TrimSpace(appName)
185-
appName = strings.ToLower(appName)
186-
appName = nonAlphanumericRe.ReplaceAllString(appName, "-")
185+
appName = strings.ReplaceAll(appName, " ", "-")
186+
appName = nonPathSafeRe.ReplaceAllString(appName, "")
187187
appName = multiDashRe.ReplaceAllString(appName, "-")
188-
appName = strings.Trim(appName, "-")
189188

190189
if len(appName) == 0 {
191190
return "", fmt.Errorf("app name must contain at least one alphanumeric character")
@@ -198,7 +197,7 @@ func getAppDirName(appName string) (string, error) {
198197
return appName, nil
199198
}
200199

201-
// parseAppPath splits user input into a directory path (with kebab-cased basename)
200+
// parseAppPath splits user input into a directory path (with path-safe basename)
202201
// and a display name (the raw basename preserving original casing/spacing).
203202
func parseAppPath(input string) (appPath string, displayName string, err error) {
204203
input = strings.TrimSpace(input)

internal/pkg/create/create_test.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,29 +58,37 @@ func TestGetProjectDirectoryName(t *testing.T) {
5858
input: " my-app ",
5959
expected: "my-app",
6060
},
61-
"uppercase converted to lowercase": {
61+
"uppercase preserved with spaces replaced": {
6262
input: "My Slack App",
63-
expected: "my-slack-app",
63+
expected: "My-Slack-App",
6464
},
65-
"mixed case normalized": {
65+
"mixed case preserved": {
6666
input: "My-Slack-App",
67-
expected: "my-slack-app",
67+
expected: "My-Slack-App",
6868
},
69-
"special characters replaced with dashes": {
70-
input: "my_app!@#test",
71-
expected: "my-app-test",
69+
"unsafe characters removed": {
70+
input: "my_App!@#Test",
71+
expected: "my_AppTest",
7272
},
73-
"consecutive special characters collapsed to single dash": {
73+
"consecutive dashes collapsed to single dash": {
7474
input: "my---app",
7575
expected: "my-app",
7676
},
77-
"leading and trailing special characters trimmed": {
77+
"leading and trailing dashes preserved": {
7878
input: "---my-app---",
79-
expected: "my-app",
79+
expected: "-my-app-",
80+
},
81+
"underscores preserved": {
82+
input: "my_app",
83+
expected: "my_app",
84+
},
85+
"dots preserved": {
86+
input: "my.app",
87+
expected: "my.app",
8088
},
81-
"dots converted to dashes": {
89+
"leading dots preserved": {
8290
input: ".my-app",
83-
expected: "my-app",
91+
expected: ".my-app",
8492
},
8593
"only special characters returns error": {
8694
input: "!!!",
@@ -92,7 +100,7 @@ func TestGetProjectDirectoryName(t *testing.T) {
92100
},
93101
"complex mixed input": {
94102
input: " My Cool App! (v2) ",
95-
expected: "my-cool-app-v2",
103+
expected: "My-Cool-App-v2",
96104
},
97105
}
98106
for name, tc := range tests {
@@ -122,7 +130,7 @@ func TestParseAppPath(t *testing.T) {
122130
},
123131
"name with spaces": {
124132
input: "My Cool App",
125-
expectedPath: "my-cool-app",
133+
expectedPath: "My-Cool-App",
126134
expectedDisplay: "My Cool App",
127135
},
128136
"relative path with simple name": {
@@ -132,7 +140,7 @@ func TestParseAppPath(t *testing.T) {
132140
},
133141
"relative path with spaced name": {
134142
input: "path/to/My App",
135-
expectedPath: filepath.Join("path", "to", "my-app"),
143+
expectedPath: filepath.Join("path", "to", "My-App"),
136144
expectedDisplay: "My App",
137145
},
138146
"dot-prefixed path": {
@@ -152,7 +160,7 @@ func TestParseAppPath(t *testing.T) {
152160
},
153161
"uppercase in nested path": {
154162
input: "projects/My Slack App",
155-
expectedPath: filepath.Join("projects", "my-slack-app"),
163+
expectedPath: filepath.Join("projects", "My-Slack-App"),
156164
expectedDisplay: "My Slack App",
157165
},
158166
"trailing slash is trimmed": {

0 commit comments

Comments
 (0)