Skip to content

Commit b97ebf1

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

File tree

2 files changed

+175
-6
lines changed

2 files changed

+175
-6
lines changed

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)