Skip to content

Commit a0d85c6

Browse files
committed
fix: only update package.json dependencies section
1 parent 7f10aca commit a0d85c6

File tree

3 files changed

+176
-91
lines changed

3 files changed

+176
-91
lines changed

package.json

Lines changed: 1 addition & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1 @@
1-
{
2-
"name": "buddy-bot",
3-
"type": "module",
4-
"version": "0.8.9",
5-
"description": "The Stacks CLI.",
6-
"author": "Chris Breuer <[email protected]>",
7-
"license": "MIT",
8-
"homepage": "https://github.com/stacksjs/buddy-bot",
9-
"repository": {
10-
"type": "git",
11-
"url": "git+https://github.com/stacksjs/buddy-bot.git"
12-
},
13-
"bugs": {
14-
"url": "https://github.com/stacksjs/buddy-bot/issues"
15-
},
16-
"keywords": [
17-
"buddy",
18-
"stacks",
19-
"bun",
20-
"typescript",
21-
"javascript"
22-
],
23-
"exports": {
24-
".": {
25-
"types": "./dist/index.d.ts",
26-
"import": "./dist/index.js"
27-
}
28-
},
29-
"module": "./dist/index.js",
30-
"types": "./dist/index.d.ts",
31-
"bin": {
32-
"buddy-bot": "./dist/bin/cli.js"
33-
},
34-
"files": [
35-
"README.md",
36-
"bin",
37-
"dist"
38-
],
39-
"scripts": {
40-
"build": "bun build.ts",
41-
"fresh": "bunx rimraf node_modules/ bun.lock && bun i",
42-
"prepublishOnly": "bun run build",
43-
"test": "bun test",
44-
"lint": "bunx --bun eslint .",
45-
"lint:fix": "bunx --bun eslint . --fix",
46-
"changelog": "bunx logsmith --verbose",
47-
"changelog:generate": "bunx logsmith --output CHANGELOG.md",
48-
"release": "bun run changelog:generate && bunx bumpx prompt --recursive",
49-
"postinstall": "bunx git-hooks",
50-
"typecheck": "bunx tsc --noEmit",
51-
"dev:docs": "bun --bun vitepress dev docs",
52-
"build:docs": "bun --bun vitepress build docs",
53-
"preview:docs": "bun --bun vitepress preview docs"
54-
},
55-
"dependencies": {
56-
"@types/prompts": "^2.4.9",
57-
"bunfig": "^0.10.1",
58-
"cac": "6.7.14",
59-
"prompts": "^2.4.2",
60-
"ts-pkgx": "0.4.38"
61-
},
62-
"devDependencies": {
63-
"@stacksjs/bumpx": "^0.1.17",
64-
"@stacksjs/docs": "^0.70.23",
65-
"@stacksjs/eslint-config": "^4.14.0-beta.3",
66-
"@stacksjs/gitlint": "^0.1.5",
67-
"@stacksjs/logsmith": "^0.1.8",
68-
"@types/bun": "^1.2.20",
69-
"buddy-bot": "^0.8.8",
70-
"bun-git-hooks": "^0.2.19",
71-
"bun-plugin-dtsx": "0.9.5",
72-
"typescript": "^5.9.2"
73-
},
74-
"overrides": {
75-
"unconfig": "0.3.10"
76-
},
77-
"git-hooks": {
78-
"pre-commit": {
79-
"staged-lint": {
80-
"*.{js,ts,json,yaml,yml,md}": "bunx --bun eslint --fix"
81-
}
82-
},
83-
"commit-msg": "bunx gitlint --edit .git/COMMIT_EDITMSG"
84-
}
85-
}
1+
{"name":"x"}

src/buddy.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -671,16 +671,20 @@ export class Buddy {
671671
const versionPrefixMatch = currentVersionInFile.match(/^(\D*)/)
672672
const originalPrefix = versionPrefixMatch ? versionPrefixMatch[1] : ''
673673

674-
// Create regex to find the exact line with this package and version
675-
// This handles various formatting styles like: "package": "version", "package":"version", etc.
676-
const packageRegex = new RegExp(
677-
`("${cleanPackageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:\\s*")([^"]+)(")`,
678-
'g',
674+
// Create a more specific regex that only matches within the current dependency section
675+
// This prevents accidentally updating scripts or other sections with the same package name
676+
const escapedPackageName = cleanPackageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
677+
const escapedSectionName = section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
678+
679+
// Match the section, then find the package within that section
680+
const sectionRegex = new RegExp(
681+
`("${escapedSectionName}"\\s*:\\s*\\{[^}]*?)("${escapedPackageName}"\\s*:\\s*")([^"]+)(")([^}]*?\\})`,
682+
'gs'
679683
)
680684

681685
// Preserve the original prefix when updating to new version
682686
const newVersion = `${originalPrefix}${update.newVersion}`
683-
packageJsonContent = packageJsonContent.replace(packageRegex, `$1${newVersion}$3`)
687+
packageJsonContent = packageJsonContent.replace(sectionRegex, `$1$2${newVersion}$4$5`)
684688
packageFound = true
685689
break
686690
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { describe, expect, it, beforeEach, afterEach } from 'bun:test'
2+
import fs from 'node:fs'
3+
import path from 'node:path'
4+
import { tmpdir } from 'node:os'
5+
import { Buddy } from '../src/buddy'
6+
import type { PackageUpdate } from '../src/types'
7+
8+
describe('Package.json Script Update Bug', () => {
9+
let tempDir: string
10+
let packageJsonPath: string
11+
let buddy: Buddy
12+
13+
beforeEach(() => {
14+
// Create a temporary directory for testing
15+
tempDir = fs.mkdtempSync(path.join(tmpdir(), 'buddy-test-'))
16+
packageJsonPath = path.join(tempDir, 'package.json')
17+
18+
// Create a package.json with a script that has the same name as a dependency
19+
const packageJson = {
20+
name: 'test-package',
21+
version: '1.0.0',
22+
scripts: {
23+
prettier: 'bunx prettier --write .',
24+
test: 'bun test'
25+
},
26+
dependencies: {
27+
prettier: '^3.0.0',
28+
typescript: '^5.0.0'
29+
},
30+
devDependencies: {
31+
'@types/node': '^20.0.0'
32+
}
33+
}
34+
35+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
36+
37+
// Initialize Buddy instance
38+
buddy = new Buddy({
39+
repository: {
40+
provider: 'github',
41+
owner: 'test',
42+
name: 'test-repo',
43+
token: 'fake-token'
44+
}
45+
})
46+
})
47+
48+
afterEach(() => {
49+
// Clean up temporary directory
50+
fs.rmSync(tempDir, { recursive: true, force: true })
51+
})
52+
53+
it('should not update script values when updating dependency versions', async () => {
54+
// Simulate updating prettier from 3.0.0 to 3.6.2
55+
const updates: PackageUpdate[] = [{
56+
name: 'prettier',
57+
currentVersion: '3.0.0',
58+
newVersion: '3.6.2',
59+
file: packageJsonPath,
60+
updateType: 'minor',
61+
dependencyType: 'dependencies'
62+
}]
63+
64+
// Generate file updates
65+
const fileUpdates = await buddy.generateAllFileUpdates(updates)
66+
67+
// Find the package.json update
68+
const packageUpdate = fileUpdates.find(update => update.path === packageJsonPath)
69+
expect(packageUpdate).toBeDefined()
70+
71+
if (packageUpdate) {
72+
const updatedContent = JSON.parse(packageUpdate.content)
73+
74+
// The dependency should be updated
75+
expect(updatedContent.dependencies.prettier).toBe('^3.6.2')
76+
77+
// The script should NOT be updated - it should remain the original command
78+
expect(updatedContent.scripts.prettier).toBe('bunx prettier --write .')
79+
80+
// Other scripts should remain unchanged
81+
expect(updatedContent.scripts.test).toBe('bun test')
82+
83+
// Other dependencies should remain unchanged
84+
expect(updatedContent.dependencies.typescript).toBe('^5.0.0')
85+
expect(updatedContent.devDependencies['@types/node']).toBe('^20.0.0')
86+
}
87+
})
88+
89+
it('should handle multiple dependency updates without affecting scripts', async () => {
90+
const updates: PackageUpdate[] = [
91+
{
92+
name: 'prettier',
93+
currentVersion: '3.0.0',
94+
newVersion: '3.6.2',
95+
file: packageJsonPath,
96+
updateType: 'minor',
97+
dependencyType: 'dependencies'
98+
},
99+
{
100+
name: 'typescript',
101+
currentVersion: '5.0.0',
102+
newVersion: '5.3.0',
103+
file: packageJsonPath,
104+
updateType: 'minor',
105+
dependencyType: 'dependencies'
106+
}
107+
]
108+
109+
const fileUpdates = await buddy.generateAllFileUpdates(updates)
110+
const packageUpdate = fileUpdates.find(update => update.path === packageJsonPath)
111+
112+
if (packageUpdate) {
113+
const updatedContent = JSON.parse(packageUpdate.content)
114+
115+
// Dependencies should be updated
116+
expect(updatedContent.dependencies.prettier).toBe('^3.6.2')
117+
expect(updatedContent.dependencies.typescript).toBe('^5.3.0')
118+
119+
// Scripts should remain unchanged
120+
expect(updatedContent.scripts.prettier).toBe('bunx prettier --write .')
121+
expect(updatedContent.scripts.test).toBe('bun test')
122+
}
123+
})
124+
125+
it('should preserve exact formatting and spacing in package.json', async () => {
126+
// Create a package.json with specific formatting
127+
const formattedPackageJson = `{
128+
"name": "test-package",
129+
"version": "1.0.0",
130+
"scripts": {
131+
"prettier": "bunx prettier --write .",
132+
"test": "bun test"
133+
},
134+
"dependencies": {
135+
"prettier": "^3.0.0",
136+
"typescript": "^5.0.0"
137+
}
138+
}`
139+
140+
fs.writeFileSync(packageJsonPath, formattedPackageJson)
141+
142+
const updates: PackageUpdate[] = [{
143+
name: 'prettier',
144+
currentVersion: '3.0.0',
145+
newVersion: '3.6.2',
146+
file: packageJsonPath,
147+
updateType: 'minor',
148+
dependencyType: 'dependencies'
149+
}]
150+
151+
const fileUpdates = await buddy.generateAllFileUpdates(updates)
152+
const packageUpdate = fileUpdates.find(update => update.path === packageJsonPath)
153+
154+
if (packageUpdate) {
155+
// Check that the script line is preserved exactly
156+
expect(packageUpdate.content).toContain('"prettier": "bunx prettier --write ."')
157+
158+
// Check that the dependency line is updated
159+
expect(packageUpdate.content).toContain('"prettier": "^3.6.2"')
160+
161+
// Ensure we don't have the wrong update
162+
expect(packageUpdate.content).not.toContain('"prettier": "3.6.2"') // This would be the script being wrongly updated
163+
}
164+
})
165+
})

0 commit comments

Comments
 (0)