Skip to content

Commit 57fc655

Browse files
committed
fix: preserve YAML inline comments when updating deps
🐛 **Critical Fix:** YAML comments are now preserved during dependency updates **Problem:** When updating dependencies in YAML files, inline comments were being stripped: - Before: \`node: ^22.12.0 # only temporarily needed until bun & vue-tsc issue is resolved\` - After: \`node: ^22.17.1\` ❌ (comment lost) **Root Cause:** The regex pattern \`([^\\n\\r]*)\` captured everything after the colon, including comments, then replaced the entire match with just the version number. **Solution:** Enhanced regex to capture three distinct parts: 1. \`(\\s*\\bpackage\\b\\s*:\\s*)\` - package name and colon 2. \`([^\\s#\\n\\r]+)\` - version part only (stops at whitespace or #) 3. \`(\\s*#.*)?\` - optional comment part Replacement now preserves: \`package + colon + new version + original comment\` **Regex Changes:** - Old: \`package: version-and-comment-together\` → \`package: new-version\` ❌ - New: \`package: version # comment\` → \`package: new-version # comment\` ✅ **Test Coverage:** - ✅ Single dependency with comment preservation - ✅ Multiple dependencies with mixed comments - ✅ Dependencies without comments (no change) - ✅ Full-line comments remain untouched - ✅ All existing constraint preservation tests still pass **Real-world Impact:** ✅ \`node: ^22.12.0 # only temporarily needed until bun & vue-tsc issue is resolved\` ✅ Updates to: \`node: ^22.17.1 # only temporarily needed until bun & vue-tsc issue is resolved\` No more losing valuable context information in dependency files
1 parent f119a13 commit 57fc655

File tree

2 files changed

+122
-5
lines changed

2 files changed

+122
-5
lines changed

src/utils/dependency-file-parser.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,17 +142,26 @@ export async function updateDependencyFile(filePath: string, content: string, up
142142
// Create regex to find the package line and update its version
143143
// Handle various YAML formats: "package: version", "package:version", " package: ^version"
144144
// Use word boundaries to prevent partial matches (e.g., "zip" matching "unzip")
145+
// Capture: (1) package name and colon, (2) version part, (3) optional comment part
145146
const packageRegex = new RegExp(
146-
`(\\s*\\b${cleanPackageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*:\\s*)([^\\n\\r]*)`,
147+
`(\\s*\\b${cleanPackageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*:\\s*)([^\\s#\\n\\r]+)(\\s*#.*)?`,
147148
'g',
148149
)
149150

150151
// Extract the original version prefix (^, ~, >=, etc.) or lack thereof
151152
const currentMatch = updatedContent.match(packageRegex)
152153
if (currentMatch) {
153-
const currentVersionMatch = currentMatch[0].match(/:[ \t]*([^\\nr]+)/)
154-
if (currentVersionMatch) {
155-
const currentVersionInFile = currentVersionMatch[1].trim()
154+
const fullMatch = currentMatch[0]
155+
// Parse the match into parts: package+colon, version, comment
156+
const matchParts = fullMatch.match(new RegExp(
157+
`(\\s*\\b${cleanPackageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b\\s*:\\s*)([^\\s#\\n\\r]+)(\\s*#.*)?`,
158+
))
159+
160+
if (matchParts) {
161+
const packageAndColon = matchParts[1] // " package: "
162+
const currentVersionInFile = matchParts[2] // "^1.0.0"
163+
const commentPart = matchParts[3] || '' // " # comment" or empty
164+
156165
const versionPrefixMatch = currentVersionInFile.match(/^(\D*)/)
157166
const originalPrefix = versionPrefixMatch ? versionPrefixMatch[1] : ''
158167

@@ -162,7 +171,9 @@ export async function updateDependencyFile(filePath: string, content: string, up
162171
// Use newVersion as-is if it already has a prefix, otherwise preserve original prefix
163172
const finalVersion = newVersionHasPrefix ? update.newVersion : `${originalPrefix}${update.newVersion}`
164173

165-
updatedContent = updatedContent.replace(packageRegex, `$1${finalVersion}`)
174+
// Replace with: package+colon + new version + preserved comment
175+
const replacement = `${packageAndColon}${finalVersion}${commentPart}`
176+
updatedContent = updatedContent.replace(packageRegex, replacement)
166177
}
167178
}
168179
}

test/regression-deps-constraint.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,110 @@ dependencies:
191191
expect(result).toContain('"@types/node-fetch": ^2.0.0')
192192
})
193193
})
194+
195+
describe('YAML comment preservation', () => {
196+
it('should preserve inline comments when updating dependencies', async () => {
197+
const content = `dependencies:
198+
aws/cli: ^2.22.26
199+
bun: ^1.2.19
200+
gh: ^2.76.1
201+
zip: ^3.0
202+
unzip: ^6.0
203+
sqlite3: ^3.50.3
204+
node: ^22.12.0 # only temporarily needed until bun & vue-tsc issue is resolved`
205+
206+
const updates: PackageUpdate[] = [
207+
{
208+
name: 'node',
209+
currentVersion: '^22.12.0',
210+
newVersion: '22.17.1',
211+
updateType: 'minor',
212+
dependencyType: 'dependencies',
213+
file: 'deps.yaml',
214+
metadata: undefined,
215+
},
216+
]
217+
218+
const result = await updateDependencyFile('deps.yaml', content, updates)
219+
220+
// Should update the version but preserve the comment
221+
expect(result).toContain('node: ^22.17.1 # only temporarily needed until bun & vue-tsc issue is resolved')
222+
expect(result).not.toContain('node: ^22.12.0') // Old version should be gone
223+
expect(result).not.toContain('node: ^22.17.1\n') // Should not strip the comment
224+
})
225+
226+
it('should preserve comments for multiple dependencies with comments', async () => {
227+
const content = `dependencies:
228+
aws/cli: ^2.22.26 # AWS command line interface
229+
bun: ^1.2.19 # JavaScript runtime
230+
node: ^22.12.0 # only temporarily needed until bun & vue-tsc issue is resolved
231+
# redis: ^7.4.1 # disabled for now
232+
sqlite3: ^3.50.3 # database engine`
233+
234+
const updates: PackageUpdate[] = [
235+
{
236+
name: 'aws/cli',
237+
currentVersion: '^2.22.26',
238+
newVersion: '2.27.60',
239+
updateType: 'minor',
240+
dependencyType: 'dependencies',
241+
file: 'deps.yaml',
242+
metadata: undefined,
243+
},
244+
{
245+
name: 'node',
246+
currentVersion: '^22.12.0',
247+
newVersion: '22.17.1',
248+
updateType: 'minor',
249+
dependencyType: 'dependencies',
250+
file: 'deps.yaml',
251+
metadata: undefined,
252+
},
253+
]
254+
255+
const result = await updateDependencyFile('deps.yaml', content, updates)
256+
257+
// Should update versions but preserve all comments
258+
expect(result).toContain('aws/cli: ^2.27.60 # AWS command line interface')
259+
expect(result).toContain('bun: ^1.2.19 # JavaScript runtime')
260+
expect(result).toContain('node: ^22.17.1 # only temporarily needed until bun & vue-tsc issue is resolved')
261+
expect(result).toContain('# redis: ^7.4.1 # disabled for now') // Full-line comments should remain
262+
expect(result).toContain('sqlite3: ^3.50.3 # database engine')
263+
})
264+
265+
it('should handle dependencies without comments alongside ones with comments', async () => {
266+
const content = `dependencies:
267+
zip: ^3.0
268+
unzip: ^6.0 # extraction utility
269+
sqlite3: ^3.50.3`
270+
271+
const updates: PackageUpdate[] = [
272+
{
273+
name: 'zip',
274+
currentVersion: '^3.0',
275+
newVersion: '3.0.0',
276+
updateType: 'patch',
277+
dependencyType: 'dependencies',
278+
file: 'deps.yaml',
279+
metadata: undefined,
280+
},
281+
{
282+
name: 'unzip',
283+
currentVersion: '^6.0',
284+
newVersion: '6.0.0',
285+
updateType: 'patch',
286+
dependencyType: 'dependencies',
287+
file: 'deps.yaml',
288+
metadata: undefined,
289+
},
290+
]
291+
292+
const result = await updateDependencyFile('deps.yaml', content, updates)
293+
294+
// Should update both, preserving comment only where it exists
295+
expect(result).toContain('zip: ^3.0.0')
296+
expect(result).toContain('unzip: ^6.0.0 # extraction utility')
297+
expect(result).toContain('sqlite3: ^3.50.3')
298+
})
299+
})
194300
})

0 commit comments

Comments
 (0)