diff --git a/internal/app/app.go b/internal/app/app.go index 9ed01bf5..d912e2dc 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -48,16 +48,26 @@ func NewClient( // UpdateDefaultProjectFiles should update any project specific files if any func UpdateDefaultProjectFiles(fs afero.Fs, dirPath string, appDirName string) error { - var filenames = []string{"manifest.json", "manifest.js", "manifest.ts"} + // Files and their corresponding app name replacement functions + projectFiles := []struct { + filename string + replacer func([]byte, string) []byte + }{ + {"manifest.json", regexReplaceAppNameInManifest}, + {"manifest.js", regexReplaceAppNameInManifest}, + {"manifest.ts", regexReplaceAppNameInManifest}, + {"package.json", regexReplaceAppNameInPackageJSON}, + {"pyproject.toml", regexReplaceAppNameInPyprojectToml}, + } - for _, filename := range filenames { - filePath := filepath.Join(dirPath, filename) + for _, pf := range projectFiles { + filePath := filepath.Join(dirPath, pf.filename) fileData, err := afero.ReadFile(fs, filePath) if err != nil { continue } - fileData = regexReplaceAppNameInManifest(fileData, appDirName) + fileData = pf.replacer(fileData, appDirName) if err := afero.WriteFile(fs, filePath, fileData, 0644); err != nil { return err } @@ -149,3 +159,17 @@ func regexReplaceAppNameInManifest(src []byte, appName string) []byte { return srcUpdated } + +// regexReplaceAppNameInPackageJSON replaces the top-level "name" field in a package.json file +func regexReplaceAppNameInPackageJSON(src []byte, appName string) []byte { + re := regexp.MustCompile(`(?m)^(\s*"name"\s*:\s*")([^"]*)(")`) + repl := fmt.Sprintf("${1}%s${3}", appName) + return re.ReplaceAll(src, []byte(repl)) +} + +// regexReplaceAppNameInPyprojectToml replaces the "name" field under the [project] section in a pyproject.toml file +func regexReplaceAppNameInPyprojectToml(src []byte, appName string) []byte { + re := regexp.MustCompile(`(\[project\][^\[]*?name\s*=\s*")([^"]*)(")`) + repl := fmt.Sprintf("${1}%s${3}", appName) + return re.ReplaceAll(src, []byte(repl)) +} diff --git a/internal/app/app_test.go b/internal/app/app_test.go index eae92335..54b92667 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -74,6 +74,40 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) { }, expectedErrorType: nil, }, + "package.json file exists": { + appDirName: "vibrant-butterfly-1234", + existingFiles: map[string]string{ + "package.json": string(testdata.PackageJSON), + }, + expectedFiles: map[string]string{ + "package.json": string(testdata.PackageJSONAppName), + }, + expectedErrorType: nil, + }, + "pyproject.toml file exists": { + appDirName: "vibrant-butterfly-1234", + existingFiles: map[string]string{ + "pyproject.toml": string(testdata.PyprojectTOML), + }, + expectedFiles: map[string]string{ + "pyproject.toml": string(testdata.PyprojectTOMLAppName), + }, + expectedErrorType: nil, + }, + "Multiple project files exist": { + appDirName: "vibrant-butterfly-1234", + existingFiles: map[string]string{ + "manifest.json": string(testdata.ManifestJSON), + "package.json": string(testdata.PackageJSON), + "pyproject.toml": string(testdata.PyprojectTOML), + }, + expectedFiles: map[string]string{ + "manifest.json": string(testdata.ManifestJSONAppName), + "package.json": string(testdata.PackageJSONAppName), + "pyproject.toml": string(testdata.PyprojectTOMLAppName), + }, + expectedErrorType: nil, + }, "No manifest files exist": { appDirName: "vibrant-butterfly-1234", existingFiles: map[string]string{}, @@ -161,3 +195,43 @@ func Test_RegexReplaceAppNameInManifest(t *testing.T) { }) } } + +func Test_RegexReplaceAppNameInPackageJSON(t *testing.T) { + tests := map[string]struct { + src []byte + appName string + expectedSrc []byte + }{ + "package.json name is replaced": { + src: testdata.PackageJSON, + appName: "vibrant-butterfly-1234", + expectedSrc: testdata.PackageJSONAppName, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + actualSrc := regexReplaceAppNameInPackageJSON(tc.src, tc.appName) + require.Equal(t, tc.expectedSrc, actualSrc) + }) + } +} + +func Test_RegexReplaceAppNameInPyprojectToml(t *testing.T) { + tests := map[string]struct { + src []byte + appName string + expectedSrc []byte + }{ + "pyproject.toml name is replaced": { + src: testdata.PyprojectTOML, + appName: "vibrant-butterfly-1234", + expectedSrc: testdata.PyprojectTOMLAppName, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + actualSrc := regexReplaceAppNameInPyprojectToml(tc.src, tc.appName) + require.Equal(t, tc.expectedSrc, actualSrc) + }) + } +} diff --git a/test/testdata/package-app-name.json b/test/testdata/package-app-name.json new file mode 100644 index 00000000..765a3bec --- /dev/null +++ b/test/testdata/package-app-name.json @@ -0,0 +1,12 @@ +{ + "name": "vibrant-butterfly-1234", + "version": "1.0.0", + "description": "A Slack app built with Bolt", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "@slack/bolt": "^4.0.0" + } +} diff --git a/test/testdata/package.json b/test/testdata/package.json new file mode 100644 index 00000000..816f3564 --- /dev/null +++ b/test/testdata/package.json @@ -0,0 +1,12 @@ +{ + "name": "bolt-app-template", + "version": "1.0.0", + "description": "A Slack app built with Bolt", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "@slack/bolt": "^4.0.0" + } +} diff --git a/test/testdata/pyproject-app-name.toml b/test/testdata/pyproject-app-name.toml new file mode 100644 index 00000000..d062ba47 --- /dev/null +++ b/test/testdata/pyproject-app-name.toml @@ -0,0 +1,16 @@ +[project] +name = "vibrant-butterfly-1234" +version = "0.1.0" +requires-python = ">=3.9" +dependencies = [ + "slack-sdk==3.40.0", + "slack-bolt==1.27.0", + "slack-cli-hooks<1.0.0", +] + +[tool.ruff] +[tool.ruff.lint] +[tool.ruff.format] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/test/testdata/pyproject.toml b/test/testdata/pyproject.toml new file mode 100644 index 00000000..6227e88b --- /dev/null +++ b/test/testdata/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "bolt-python-ai-agent-template" +version = "0.1.0" +requires-python = ">=3.9" +dependencies = [ + "slack-sdk==3.40.0", + "slack-bolt==1.27.0", + "slack-cli-hooks<1.0.0", +] + +[tool.ruff] +[tool.ruff.lint] +[tool.ruff.format] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/test/testdata/testdata.go b/test/testdata/testdata.go index 06985b67..5f818a3c 100644 --- a/test/testdata/testdata.go +++ b/test/testdata/testdata.go @@ -27,3 +27,15 @@ var ManifestSDKTS []byte //go:embed manifest-sdk-app-name.ts var ManifestSDKTSAppName []byte + +//go:embed package.json +var PackageJSON []byte + +//go:embed package-app-name.json +var PackageJSONAppName []byte + +//go:embed pyproject.toml +var PyprojectTOML []byte + +//go:embed pyproject-app-name.toml +var PyprojectTOMLAppName []byte