From 7e47feec9b0d3e9f1e328e356475b32e46ca5d84 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Mon, 4 May 2026 16:04:13 -0400 Subject: [PATCH 1/2] fix: use title case for manifest display names during app creation The display_information.name and bot_user.display_name fields in manifest files were set to the kebab-case directory name (e.g. "my-app") instead of a human-readable title case name (e.g. "My App"). --- internal/app/app.go | 22 +++++++++---- internal/app/app_test.go | 44 ++++++++++++++++++-------- test/testdata/manifest-app-name.js | 4 +-- test/testdata/manifest-app-name.json | 4 +-- test/testdata/manifest-app-name.ts | 4 +-- test/testdata/manifest-sdk-app-name.ts | 2 +- 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index da097231..2113d635 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -19,12 +19,15 @@ import ( "fmt" "path/filepath" "regexp" + "strings" "text/template" "github.com/slackapi/slack-cli/internal/api" "github.com/slackapi/slack-cli/internal/config" "github.com/slackapi/slack-cli/internal/shared/types" "github.com/spf13/afero" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // Client to access the app/project @@ -46,18 +49,25 @@ func NewClient( } } +func kebabToTitleCase(s string) string { + return cases.Title(language.English).String(strings.ReplaceAll(s, "-", " ")) +} + // UpdateDefaultProjectFiles should update any project specific files if any func UpdateDefaultProjectFiles(fs afero.Fs, dirPath string, appDirName string) error { + displayName := kebabToTitleCase(appDirName) + // Files and their corresponding app name replacement functions projectFiles := []struct { filename string replacer func([]byte, string) []byte + name string }{ - {"manifest.json", regexReplaceAppNameInManifest}, - {"manifest.js", regexReplaceAppNameInManifest}, - {"manifest.ts", regexReplaceAppNameInManifest}, - {"package.json", regexReplaceAppNameInPackageJSON}, - {"pyproject.toml", regexReplaceAppNameInPyprojectToml}, + {"manifest.json", regexReplaceAppNameInManifest, displayName}, + {"manifest.js", regexReplaceAppNameInManifest, displayName}, + {"manifest.ts", regexReplaceAppNameInManifest, displayName}, + {"package.json", regexReplaceAppNameInPackageJSON, appDirName}, + {"pyproject.toml", regexReplaceAppNameInPyprojectToml, appDirName}, } for _, pf := range projectFiles { @@ -67,7 +77,7 @@ func UpdateDefaultProjectFiles(fs afero.Fs, dirPath string, appDirName string) e continue } - fileData = pf.replacer(fileData, appDirName) + fileData = pf.replacer(fileData, pf.name) if err := afero.WriteFile(fs, filePath, fileData, 0644); err != nil { return err } diff --git a/internal/app/app_test.go b/internal/app/app_test.go index c4720846..573ff7e7 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -114,16 +114,6 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedFiles: map[string]string{}, expectedErrorType: nil, }, - "WriteFile error": { - appDirName: "vibrant-butterfly-1234", - existingFiles: map[string]string{ - "manifest.json": string(testdata.ManifestJSON), - }, - expectedFiles: map[string]string{ - "manifest.json": string(testdata.ManifestJSONAppName), - }, - expectedErrorType: nil, - }, } for name, tc := range tests { @@ -161,6 +151,32 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { } } +func Test_kebabToTitleCase(t *testing.T) { + tests := map[string]struct { + input string + expected string + }{ + "multiple words": { + input: "my-app", + expected: "My App", + }, + "multiple words with numbers": { + input: "vibrant-butterfly-1234", + expected: "Vibrant Butterfly 1234", + }, + "single word": { + input: "hello", + expected: "Hello", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := kebabToTitleCase(tc.input) + require.Equal(t, tc.expected, result) + }) + } +} + func Test_RegexReplaceAppNameInManifest(t *testing.T) { tests := map[string]struct { src []byte @@ -169,22 +185,22 @@ func Test_RegexReplaceAppNameInManifest(t *testing.T) { }{ "manifest.json is validate": { src: testdata.ManifestJSON, - appName: "vibrant-butterfly-1234", + appName: "Vibrant Butterfly 1234", expectedSrc: testdata.ManifestJSONAppName, }, "manifest.js is validate": { src: testdata.ManifestJS, - appName: "vibrant-butterfly-1234", + appName: "Vibrant Butterfly 1234", expectedSrc: testdata.ManifestJSAppName, }, "manifest.ts is validate": { src: testdata.ManifestTS, - appName: "vibrant-butterfly-1234", + appName: "Vibrant Butterfly 1234", expectedSrc: testdata.ManifestTSAppName, }, "manifest.ts with sdk is validate": { src: testdata.ManifestSDKTS, - appName: "vibrant-butterfly-1234", + appName: "Vibrant Butterfly 1234", expectedSrc: testdata.ManifestSDKTSAppName, }, } diff --git a/test/testdata/manifest-app-name.js b/test/testdata/manifest-app-name.js index 6cd7e6c6..43e73aad 100644 --- a/test/testdata/manifest-app-name.js +++ b/test/testdata/manifest-app-name.js @@ -3,7 +3,7 @@ export default { major_version: 2 }, display_information: { - name: 'vibrant-butterfly-1234' + name: 'Vibrant Butterfly 1234' }, // This is a comment features: { @@ -13,7 +13,7 @@ export default { messages_tab_read_only_enabled: false }, bot_user: { - display_name: 'vibrant-butterfly-1234' + display_name: 'Vibrant Butterfly 1234' } }, functions: { diff --git a/test/testdata/manifest-app-name.json b/test/testdata/manifest-app-name.json index f9b9e097..0984465d 100644 --- a/test/testdata/manifest-app-name.json +++ b/test/testdata/manifest-app-name.json @@ -3,7 +3,7 @@ "major_version": 2 }, "display_information": { - "name": "vibrant-butterfly-1234" + "name": "Vibrant Butterfly 1234" }, "features": { "app_home": { @@ -12,7 +12,7 @@ "messages_tab_read_only_enabled": false }, "bot_user": { - "display_name": "vibrant-butterfly-1234" + "display_name": "Vibrant Butterfly 1234" } }, "functions": { diff --git a/test/testdata/manifest-app-name.ts b/test/testdata/manifest-app-name.ts index ca28ee87..c2879228 100644 --- a/test/testdata/manifest-app-name.ts +++ b/test/testdata/manifest-app-name.ts @@ -12,7 +12,7 @@ export default { "major_version": 2 }, "display_information": { - "name": "vibrant-butterfly-1234" + "name": "Vibrant Butterfly 1234" }, // This is a comment "features": { @@ -22,7 +22,7 @@ export default { "messages_tab_read_only_enabled": false }, "bot_user": { - "display_name": "vibrant-butterfly-1234" + "display_name": "Vibrant Butterfly 1234" } }, "functions": { diff --git a/test/testdata/manifest-sdk-app-name.ts b/test/testdata/manifest-sdk-app-name.ts index 1818b2ab..385c5a19 100644 --- a/test/testdata/manifest-sdk-app-name.ts +++ b/test/testdata/manifest-sdk-app-name.ts @@ -11,7 +11,7 @@ const obj = { }; export default Manifest({ - "name": "vibrant-butterfly-1234", + "name": "Vibrant Butterfly 1234", "description": "Reverse a string", // "runtime_environment": "slack", "runtime": "deno1.x", From af8e3f4b92355dc36eedaec4d9d77cef8e25e825 Mon Sep 17 00:00:00 2001 From: Ale Mercado Date: Mon, 4 May 2026 16:53:48 -0400 Subject: [PATCH 2/2] fix: pass original app name as display name instead of title-casing kebab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review feedback — instead of converting the kebab-case directory name to title case (which mangles names like "TIM" or "Translator - Translate Languages"), pass the original user-provided app name through to manifest display fields. This preserves the user's exact casing and formatting. --- internal/app/app.go | 11 +------- internal/app/app_test.go | 51 ++++++++++++----------------------- internal/pkg/create/create.go | 2 +- 3 files changed, 19 insertions(+), 45 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 2113d635..2f5836a6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -19,15 +19,12 @@ import ( "fmt" "path/filepath" "regexp" - "strings" "text/template" "github.com/slackapi/slack-cli/internal/api" "github.com/slackapi/slack-cli/internal/config" "github.com/slackapi/slack-cli/internal/shared/types" "github.com/spf13/afero" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) // Client to access the app/project @@ -49,14 +46,8 @@ func NewClient( } } -func kebabToTitleCase(s string) string { - return cases.Title(language.English).String(strings.ReplaceAll(s, "-", " ")) -} - // UpdateDefaultProjectFiles should update any project specific files if any -func UpdateDefaultProjectFiles(fs afero.Fs, dirPath string, appDirName string) error { - displayName := kebabToTitleCase(appDirName) - +func UpdateDefaultProjectFiles(fs afero.Fs, dirPath string, appDirName string, displayName string) error { // Files and their corresponding app name replacement functions projectFiles := []struct { filename string diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 573ff7e7..17be7ec4 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -28,12 +28,14 @@ import ( func Test_App_UpdateDefaultProjectFiles(t *testing.T) { tests := map[string]struct { appDirName string + displayName string existingFiles map[string]string expectedFiles map[string]string expectedErrorType error }{ "manifest.json file exists": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "manifest.json": string(testdata.ManifestJSON), }, @@ -43,7 +45,8 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedErrorType: nil, }, "manifest.js file exists": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "manifest.js": string(testdata.ManifestJS), }, @@ -53,7 +56,8 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedErrorType: nil, }, "manifest.ts file exists": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "manifest.ts": string(testdata.ManifestTS), }, @@ -63,7 +67,8 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedErrorType: nil, }, "Multiple manifest files exist": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "manifest.json": string(testdata.ManifestJSON), "manifest.ts": string(testdata.ManifestTS), @@ -75,7 +80,8 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedErrorType: nil, }, "package.json file exists": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "package.json": string(testdata.PackageJSON), }, @@ -85,7 +91,8 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedErrorType: nil, }, "pyproject.toml file exists": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "pyproject.toml": string(testdata.PyprojectTOML), }, @@ -95,7 +102,8 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { expectedErrorType: nil, }, "Multiple project files exist": { - appDirName: "vibrant-butterfly-1234", + appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{ "manifest.json": string(testdata.ManifestJSON), "package.json": string(testdata.PackageJSON), @@ -110,6 +118,7 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { }, "No manifest files exist": { appDirName: "vibrant-butterfly-1234", + displayName: "Vibrant Butterfly 1234", existingFiles: map[string]string{}, expectedFiles: map[string]string{}, expectedErrorType: nil, @@ -136,7 +145,7 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { } // Run the tests - err := UpdateDefaultProjectFiles(fs, projectDirPath, tc.appDirName) + err := UpdateDefaultProjectFiles(fs, projectDirPath, tc.appDirName, tc.displayName) // Assertions require.IsType(t, err, tc.expectedErrorType) @@ -151,32 +160,6 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { } } -func Test_kebabToTitleCase(t *testing.T) { - tests := map[string]struct { - input string - expected string - }{ - "multiple words": { - input: "my-app", - expected: "My App", - }, - "multiple words with numbers": { - input: "vibrant-butterfly-1234", - expected: "Vibrant Butterfly 1234", - }, - "single word": { - input: "hello", - expected: "Hello", - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result := kebabToTitleCase(tc.input) - require.Equal(t, tc.expected, result) - }) - } -} - func Test_RegexReplaceAppNameInManifest(t *testing.T) { tests := map[string]struct { src []byte diff --git a/internal/pkg/create/create.go b/internal/pkg/create/create.go index f10e07ec..357785c3 100644 --- a/internal/pkg/create/create.go +++ b/internal/pkg/create/create.go @@ -150,7 +150,7 @@ func Create(ctx context.Context, clients *shared.ClientFactory, createArgs Creat }() // Update default project files' app name, bot name, etc - if err := app.UpdateDefaultProjectFiles(clients.Fs, projectDirPath, appDirName); err != nil { + if err := app.UpdateDefaultProjectFiles(clients.Fs, projectDirPath, appDirName, createArgs.AppName); err != nil { return "", slackerror.Wrap(err, slackerror.ErrProjectFileUpdate) }