Skip to content

Commit f4910dc

Browse files
committed
fixes for tag checking
1 parent cf5e203 commit f4910dc

2 files changed

Lines changed: 165 additions & 15 deletions

File tree

scripts/create-release.test.ts

Lines changed: 135 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,12 +175,6 @@ describe('create-release.ts', () => {
175175
expect(isClean).toBe(false)
176176
})
177177

178-
it('should check if tag exists', () => {
179-
const tagExists = 'abc123'
180-
181-
expect(tagExists.length).toBeGreaterThan(0)
182-
})
183-
184178
it('should handle non-existent tag error', () => {
185179
const throwError = () => {
186180
throw new Error('tag not found')
@@ -222,6 +216,141 @@ describe('create-release.ts', () => {
222216
})
223217
})
224218

219+
describe('Tag Existence Checking', () => {
220+
it('should validate 40-character SHA hashes correctly', () => {
221+
const shaRegex = /^[0-9a-f]{40}$/i
222+
223+
// Valid SHA hashes
224+
expect(shaRegex.test('cf5e203d0890ca2450876def639f1c7ed3f83f1c')).toBe(true)
225+
expect(shaRegex.test('ABCDEF1234567890ABCDEF1234567890ABCDEF12')).toBe(true)
226+
227+
// Invalid - too short
228+
expect(shaRegex.test('cf5e203')).toBe(false)
229+
230+
// Invalid - too long
231+
expect(shaRegex.test('cf5e203d0890ca2450876def639f1c7ed3f83f1c1')).toBe(false)
232+
233+
// Invalid - contains non-hex characters
234+
expect(shaRegex.test('v1.2.3')).toBe(false)
235+
expect(shaRegex.test('fatal: unknown revision')).toBe(false)
236+
expect(shaRegex.test('Needed a single revision')).toBe(false)
237+
})
238+
239+
it('should detect tag exists when git returns valid SHA', () => {
240+
// This simulates successful git rev-parse --verify
241+
const result = 'cf5e203d0890ca2450876def639f1c7ed3f83f1c'
242+
const shaRegex = /^[0-9a-f]{40}$/i
243+
const exists = result.length > 0 && shaRegex.test(result)
244+
245+
expect(exists).toBe(true)
246+
})
247+
248+
it('should detect tag does NOT exist when git throws error', () => {
249+
// This simulates failed git rev-parse --verify (exception thrown)
250+
let exists = false
251+
try {
252+
throw new Error('fatal: Needed a single revision')
253+
} catch {
254+
exists = false
255+
}
256+
257+
expect(exists).toBe(false)
258+
})
259+
260+
it('should NOT be fooled by error messages containing tag name (THE BUG)', () => {
261+
// This is the exact bug we had: git rev-parse fails but returns text
262+
// containing the tag name, which was treated as truthy
263+
const errorOutput = "fatal: ambiguous argument 'v1.2.5': unknown revision"
264+
const shaRegex = /^[0-9a-f]{40}$/i
265+
266+
// Old buggy logic would check: if (errorOutput) { tag exists }
267+
// New correct logic checks for valid SHA
268+
const exists = errorOutput.length > 0 && shaRegex.test(errorOutput)
269+
270+
expect(exists).toBe(false) // Should be false because it's not a valid SHA
271+
})
272+
273+
it('should handle error output that looks like it might be valid', () => {
274+
const testCases = [
275+
{ output: 'v1.2.5', shouldExist: false },
276+
{ output: 'tag not found', shouldExist: false },
277+
{ output: '', shouldExist: false },
278+
{ output: '123', shouldExist: false },
279+
{ output: 'abcdef', shouldExist: false }, // Too short
280+
{ output: 'cf5e203d0890ca2450876def639f1c7ed3f83f1c', shouldExist: true }, // Valid SHA
281+
]
282+
283+
const shaRegex = /^[0-9a-f]{40}$/i
284+
285+
testCases.forEach(({ output, shouldExist }) => {
286+
const exists = output.length > 0 && shaRegex.test(output)
287+
expect(exists).toBe(shouldExist)
288+
})
289+
})
290+
291+
it('should detect tag on remote when ls-remote returns data', () => {
292+
// Simulate git ls-remote --tags origin refs/tags/v1.2.3
293+
const remoteOutput = 'cf5e203d0890ca2450876def639f1c7ed3f83f1c\trefs/tags/v1.2.3'
294+
const exists = remoteOutput.length > 0
295+
296+
expect(exists).toBe(true)
297+
})
298+
299+
it('should detect tag NOT on remote when ls-remote returns empty', () => {
300+
// Simulate git ls-remote --tags origin refs/tags/v1.2.3 (tag doesn't exist)
301+
const remoteOutput = ''
302+
const exists = remoteOutput.length > 0
303+
304+
expect(exists).toBe(false)
305+
})
306+
307+
it('should check tag existence before making file changes', () => {
308+
// This tests the order of operations - tag check should come FIRST
309+
const operations: string[] = []
310+
311+
// Simulate the workflow
312+
const checkTag = () => {
313+
operations.push('check_tag')
314+
return false // Tag doesn't exist
315+
}
316+
317+
const updateFiles = () => operations.push('update_files')
318+
const commitChanges = () => operations.push('commit')
319+
const createTag = () => operations.push('create_tag')
320+
321+
// Correct order
322+
if (!checkTag()) {
323+
updateFiles()
324+
commitChanges()
325+
createTag()
326+
}
327+
328+
expect(operations).toEqual(['check_tag', 'update_files', 'commit', 'create_tag'])
329+
expect(operations[0]).toBe('check_tag') // Tag check MUST be first
330+
})
331+
332+
it('should exit early if tag already exists', () => {
333+
// This tests that we don't modify files if tag exists
334+
const operations: string[] = []
335+
336+
const checkTag = () => {
337+
operations.push('check_tag')
338+
return true // Tag EXISTS
339+
}
340+
341+
const updateFiles = () => operations.push('update_files')
342+
const commitChanges = () => operations.push('commit')
343+
344+
// Should exit early
345+
if (!checkTag()) {
346+
updateFiles()
347+
commitChanges()
348+
}
349+
350+
expect(operations).toEqual(['check_tag']) // Only tag check, nothing else!
351+
})
352+
})
353+
225354
describe('Argument Parsing', () => {
226355
it('should parse --version argument', () => {
227356
process.argv = ['node', 'script.ts', '--version', '1.2.3']

scripts/create-release.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,40 @@ const isGitClean = (): boolean => {
231231
return status.length === 0
232232
}
233233

234+
/**
235+
* Check if a tag exists (locally or remotely)
236+
*/
237+
const tagExists = (tagName: string): boolean => {
238+
try {
239+
// Check if tag exists locally
240+
const result = exec(`git rev-parse --verify ${tagName}`, true)
241+
// If the command succeeds and returns a SHA, the tag exists
242+
return result.length > 0 && /^[0-9a-f]{40}$/i.test(result)
243+
} catch {
244+
// Tag doesn't exist locally, check remote
245+
try {
246+
const output = exec(`git ls-remote --tags origin refs/tags/${tagName}`, true)
247+
return output.length > 0
248+
} catch {
249+
return false
250+
}
251+
}
252+
}
253+
234254
/**
235255
* Commit and tag the version
236256
*/
237257
const commitAndTag = (version: string) => {
258+
const tagName = `v${version}`
259+
260+
// Check if tag already exists BEFORE making any changes
261+
if (tagExists(tagName)) {
262+
console.error(`\n❌ Tag ${tagName} already exists!`)
263+
console.error(`💡 Tip: Delete the local tag with: git tag -d ${tagName}`)
264+
console.error('💡 Or use a different version number')
265+
process.exit(1)
266+
}
267+
238268
console.log('\n📦 Committing changes...')
239269

240270
exec('git add package.json src-tauri/Cargo.toml src-tauri/tauri.conf.json')
@@ -247,15 +277,6 @@ const commitAndTag = (version: string) => {
247277
exec(`git commit -m "chore: bump version to ${version}"`)
248278
console.log(' ✓ Changes committed')
249279

250-
const tagName = `v${version}`
251-
252-
// Check if tag already exists
253-
const tagExists = exec(`git rev-parse ${tagName} 2>/dev/null || echo ''`, true)
254-
if (tagExists) {
255-
console.error(`\n❌ Tag ${tagName} already exists!`)
256-
process.exit(1)
257-
}
258-
259280
console.log(`\n🏷️ Creating tag: ${tagName}`)
260281
exec(`git tag ${tagName}`)
261282
console.log(' ✓ Tag created')

0 commit comments

Comments
 (0)