Skip to content

Commit 0c3d780

Browse files
authored
Merge branch 'main' into zimeg-fix-install-windows
2 parents d1c3429 + cf7214c commit 0c3d780

7 files changed

Lines changed: 297 additions & 4 deletions

File tree

internal/app/app.go

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,26 @@ func NewClient(
4848

4949
// UpdateDefaultProjectFiles should update any project specific files if any
5050
func UpdateDefaultProjectFiles(fs afero.Fs, dirPath string, appDirName string) error {
51-
var filenames = []string{"manifest.json", "manifest.js", "manifest.ts"}
51+
// Files and their corresponding app name replacement functions
52+
projectFiles := []struct {
53+
filename string
54+
replacer func([]byte, string) []byte
55+
}{
56+
{"manifest.json", regexReplaceAppNameInManifest},
57+
{"manifest.js", regexReplaceAppNameInManifest},
58+
{"manifest.ts", regexReplaceAppNameInManifest},
59+
{"package.json", regexReplaceAppNameInPackageJSON},
60+
{"pyproject.toml", regexReplaceAppNameInPyprojectToml},
61+
}
5262

53-
for _, filename := range filenames {
54-
filePath := filepath.Join(dirPath, filename)
63+
for _, pf := range projectFiles {
64+
filePath := filepath.Join(dirPath, pf.filename)
5565
fileData, err := afero.ReadFile(fs, filePath)
5666
if err != nil {
5767
continue
5868
}
5969

60-
fileData = regexReplaceAppNameInManifest(fileData, appDirName)
70+
fileData = pf.replacer(fileData, appDirName)
6171
if err := afero.WriteFile(fs, filePath, fileData, 0644); err != nil {
6272
return err
6373
}
@@ -149,3 +159,25 @@ func regexReplaceAppNameInManifest(src []byte, appName string) []byte {
149159

150160
return srcUpdated
151161
}
162+
163+
// regexReplaceAppNameInPackageJSON replaces the top-level "name" field in a package.json file
164+
func regexReplaceAppNameInPackageJSON(src []byte, appName string) []byte {
165+
re := regexp.MustCompile(`(?m)^(\s{2}"name"\s*:\s*")([^"]*)(")`)
166+
loc := re.FindSubmatchIndex(src)
167+
if loc == nil {
168+
return src
169+
}
170+
// loc[4]:loc[5] is capture group 2 — the name value to replace
171+
result := make([]byte, 0, len(src))
172+
result = append(result, src[:loc[4]]...)
173+
result = append(result, []byte(appName)...)
174+
result = append(result, src[loc[5]:]...)
175+
return result
176+
}
177+
178+
// regexReplaceAppNameInPyprojectToml replaces the "name" field under the [project] section in a pyproject.toml file
179+
func regexReplaceAppNameInPyprojectToml(src []byte, appName string) []byte {
180+
re := regexp.MustCompile(`(\[project\][^\[]*?name\s*=\s*")([^"]*)(")`)
181+
repl := fmt.Sprintf("${1}%s${3}", appName)
182+
return re.ReplaceAll(src, []byte(repl))
183+
}

internal/app/app_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,40 @@ func Test_App_UpdateDefaultProjectFiles(t *testing.T) {
7474
},
7575
expectedErrorType: nil,
7676
},
77+
"package.json file exists": {
78+
appDirName: "vibrant-butterfly-1234",
79+
existingFiles: map[string]string{
80+
"package.json": string(testdata.PackageJSON),
81+
},
82+
expectedFiles: map[string]string{
83+
"package.json": string(testdata.PackageJSONAppName),
84+
},
85+
expectedErrorType: nil,
86+
},
87+
"pyproject.toml file exists": {
88+
appDirName: "vibrant-butterfly-1234",
89+
existingFiles: map[string]string{
90+
"pyproject.toml": string(testdata.PyprojectTOML),
91+
},
92+
expectedFiles: map[string]string{
93+
"pyproject.toml": string(testdata.PyprojectTOMLAppName),
94+
},
95+
expectedErrorType: nil,
96+
},
97+
"Multiple project files exist": {
98+
appDirName: "vibrant-butterfly-1234",
99+
existingFiles: map[string]string{
100+
"manifest.json": string(testdata.ManifestJSON),
101+
"package.json": string(testdata.PackageJSON),
102+
"pyproject.toml": string(testdata.PyprojectTOML),
103+
},
104+
expectedFiles: map[string]string{
105+
"manifest.json": string(testdata.ManifestJSONAppName),
106+
"package.json": string(testdata.PackageJSONAppName),
107+
"pyproject.toml": string(testdata.PyprojectTOMLAppName),
108+
},
109+
expectedErrorType: nil,
110+
},
77111
"No manifest files exist": {
78112
appDirName: "vibrant-butterfly-1234",
79113
existingFiles: map[string]string{},
@@ -161,3 +195,160 @@ func Test_RegexReplaceAppNameInManifest(t *testing.T) {
161195
})
162196
}
163197
}
198+
199+
func Test_RegexReplaceAppNameInPackageJSON(t *testing.T) {
200+
tests := map[string]struct {
201+
src []byte
202+
appName string
203+
expectedSrc []byte
204+
}{
205+
"package.json name is replaced": {
206+
src: testdata.PackageJSON,
207+
appName: "vibrant-butterfly-1234",
208+
expectedSrc: testdata.PackageJSONAppName,
209+
},
210+
"only top-level name is replaced not nested config name": {
211+
src: []byte(`{
212+
"name": "bolt-app-template",
213+
"version": "1.0.0",
214+
"description": "A Slack app built with Bolt",
215+
"main": "app.js",
216+
"scripts": {
217+
"start": "node app.js"
218+
},
219+
"dependencies": {
220+
"@slack/bolt": "^4.0.0"
221+
},
222+
"config": {
223+
"name": "local-server-name",
224+
"host": "localhost",
225+
"port": "8080"
226+
}
227+
}
228+
`),
229+
appName: "vibrant-butterfly-1234",
230+
expectedSrc: []byte(`{
231+
"name": "vibrant-butterfly-1234",
232+
"version": "1.0.0",
233+
"description": "A Slack app built with Bolt",
234+
"main": "app.js",
235+
"scripts": {
236+
"start": "node app.js"
237+
},
238+
"dependencies": {
239+
"@slack/bolt": "^4.0.0"
240+
},
241+
"config": {
242+
"name": "local-server-name",
243+
"host": "localhost",
244+
"port": "8080"
245+
}
246+
}
247+
`),
248+
},
249+
"no name field leaves input unchanged": {
250+
src: []byte(`{
251+
"version": "1.0.0"
252+
}
253+
`),
254+
appName: "my-app",
255+
expectedSrc: []byte(`{
256+
"version": "1.0.0"
257+
}
258+
`),
259+
},
260+
"empty name value is replaced": {
261+
src: []byte("{\n \"name\": \"\"\n}\n"),
262+
appName: "my-app",
263+
expectedSrc: []byte("{\n \"name\": \"my-app\"\n}\n"),
264+
},
265+
}
266+
for name, tc := range tests {
267+
t.Run(name, func(t *testing.T) {
268+
actualSrc := regexReplaceAppNameInPackageJSON(tc.src, tc.appName)
269+
require.Equal(t, tc.expectedSrc, actualSrc)
270+
})
271+
}
272+
}
273+
274+
func Test_RegexReplaceAppNameInPyprojectToml(t *testing.T) {
275+
tests := map[string]struct {
276+
src []byte
277+
appName string
278+
expectedSrc []byte
279+
}{
280+
"pyproject.toml name is replaced": {
281+
src: testdata.PyprojectTOML,
282+
appName: "vibrant-butterfly-1234",
283+
expectedSrc: testdata.PyprojectTOMLAppName,
284+
},
285+
"only project section name is replaced not project.scripts name": {
286+
src: []byte(`[project]
287+
name = "bolt-python-ai-agent-template"
288+
version = "0.1.0"
289+
requires-python = ">=3.9"
290+
dependencies = [
291+
"slack-sdk==3.40.0",
292+
"slack-bolt==1.27.0",
293+
"slack-cli-hooks<1.0.0",
294+
]
295+
296+
[tool.ruff]
297+
[tool.ruff.lint]
298+
[tool.ruff.format]
299+
300+
[tool.pytest.ini_options]
301+
testpaths = ["tests"]
302+
303+
[project.scripts]
304+
name = "my_package.name:main_function"
305+
`),
306+
appName: "vibrant-butterfly-1234",
307+
expectedSrc: []byte(`[project]
308+
name = "vibrant-butterfly-1234"
309+
version = "0.1.0"
310+
requires-python = ">=3.9"
311+
dependencies = [
312+
"slack-sdk==3.40.0",
313+
"slack-bolt==1.27.0",
314+
"slack-cli-hooks<1.0.0",
315+
]
316+
317+
[tool.ruff]
318+
[tool.ruff.lint]
319+
[tool.ruff.format]
320+
321+
[tool.pytest.ini_options]
322+
testpaths = ["tests"]
323+
324+
[project.scripts]
325+
name = "my_package.name:main_function"
326+
`),
327+
},
328+
"no project section leaves input unchanged": {
329+
src: []byte(`[tool.ruff]
330+
name = "should-not-change"
331+
`),
332+
appName: "my-app",
333+
expectedSrc: []byte(`[tool.ruff]
334+
name = "should-not-change"
335+
`),
336+
},
337+
"empty name value is replaced": {
338+
src: []byte(`[project]` + "\n" + `name = ""` + "\n"),
339+
appName: "my-app",
340+
expectedSrc: []byte(`[project]` + "\n" + `name = "my-app"` + "\n"),
341+
},
342+
"extra whitespace around equals sign": {
343+
src: []byte(`[project]` + "\n" + `name = "old-name"` + "\n"),
344+
appName: "new-name",
345+
expectedSrc: []byte(`[project]` + "\n" + `name = "new-name"` + "\n"),
346+
},
347+
}
348+
for name, tc := range tests {
349+
t.Run(name, func(t *testing.T) {
350+
actualSrc := regexReplaceAppNameInPyprojectToml(tc.src, tc.appName)
351+
require.Equal(t, tc.expectedSrc, actualSrc)
352+
})
353+
}
354+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "vibrant-butterfly-1234",
3+
"version": "1.0.0",
4+
"description": "A Slack app built with Bolt",
5+
"main": "app.js",
6+
"scripts": {
7+
"start": "node app.js"
8+
},
9+
"dependencies": {
10+
"@slack/bolt": "^4.0.0"
11+
}
12+
}

test/testdata/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "bolt-app-template",
3+
"version": "1.0.0",
4+
"description": "A Slack app built with Bolt",
5+
"main": "app.js",
6+
"scripts": {
7+
"start": "node app.js"
8+
},
9+
"dependencies": {
10+
"@slack/bolt": "^4.0.0"
11+
}
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[project]
2+
name = "vibrant-butterfly-1234"
3+
version = "0.1.0"
4+
requires-python = ">=3.9"
5+
dependencies = [
6+
"slack-sdk==3.40.0",
7+
"slack-bolt==1.27.0",
8+
"slack-cli-hooks<1.0.0",
9+
]
10+
11+
# This comment appears before more details
12+
[tool.ruff]
13+
[tool.ruff.lint]
14+
[tool.ruff.format]
15+
16+
[tool.pytest.ini_options]
17+
testpaths = ["tests"]

test/testdata/pyproject.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[project]
2+
name = "bolt-python-ai-agent-template"
3+
version = "0.1.0"
4+
requires-python = ">=3.9"
5+
dependencies = [
6+
"slack-sdk==3.40.0",
7+
"slack-bolt==1.27.0",
8+
"slack-cli-hooks<1.0.0",
9+
]
10+
11+
# This comment appears before more details
12+
[tool.ruff]
13+
[tool.ruff.lint]
14+
[tool.ruff.format]
15+
16+
[tool.pytest.ini_options]
17+
testpaths = ["tests"]

test/testdata/testdata.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,15 @@ var ManifestSDKTS []byte
2727

2828
//go:embed manifest-sdk-app-name.ts
2929
var ManifestSDKTSAppName []byte
30+
31+
//go:embed package.json
32+
var PackageJSON []byte
33+
34+
//go:embed package-app-name.json
35+
var PackageJSONAppName []byte
36+
37+
//go:embed pyproject.toml
38+
var PyprojectTOML []byte
39+
40+
//go:embed pyproject-app-name.toml
41+
var PyprojectTOMLAppName []byte

0 commit comments

Comments
 (0)