claude-git-hooks 2.11.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -20
- package/README.md +59 -0
- package/bin/claude-hooks +8 -0
- package/lib/commands/bump-version.js +452 -0
- package/lib/commands/create-pr.js +104 -0
- package/lib/commands/generate-changelog.js +154 -0
- package/lib/commands/help.js +53 -0
- package/lib/utils/changelog-generator.js +382 -0
- package/lib/utils/git-tag-manager.js +516 -0
- package/lib/utils/version-manager.js +583 -0
- package/package.json +1 -1
- package/templates/GENERATE_CHANGELOG.md +83 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: git-tag-manager.js
|
|
3
|
+
* Purpose: Manage Git tag operations for version management
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Creating annotated tags
|
|
7
|
+
* - Listing local and remote tags
|
|
8
|
+
* - Comparing tag versions
|
|
9
|
+
* - Pushing tags to remote
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'child_process';
|
|
13
|
+
import logger from './logger.js';
|
|
14
|
+
import { getRemoteName } from './git-operations.js';
|
|
15
|
+
import { compareVersions } from './version-manager.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Custom error for git tag operation failures
|
|
19
|
+
*/
|
|
20
|
+
class GitTagError extends Error {
|
|
21
|
+
constructor(message, { command, cause, output } = {}) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'GitTagError';
|
|
24
|
+
this.command = command;
|
|
25
|
+
this.cause = cause;
|
|
26
|
+
this.output = output;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Executes a git command for tag operations
|
|
32
|
+
* Why: Consistent error handling for tag-specific operations
|
|
33
|
+
*
|
|
34
|
+
* @param {string} command - Git command to execute
|
|
35
|
+
* @returns {string} Command output
|
|
36
|
+
* @throws {GitTagError} If command fails
|
|
37
|
+
*/
|
|
38
|
+
function execGitTagCommand(command) {
|
|
39
|
+
logger.debug('git-tag-manager - execGitTagCommand', 'Executing command', { command });
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const output = execSync(command, {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
logger.debug('git-tag-manager - execGitTagCommand', 'Command successful', {
|
|
48
|
+
command,
|
|
49
|
+
outputLength: output.length
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return output.trim();
|
|
53
|
+
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.error('git-tag-manager - execGitTagCommand', 'Command failed', {
|
|
56
|
+
command,
|
|
57
|
+
error: error.message
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
throw new GitTagError('Git tag command failed', {
|
|
61
|
+
command,
|
|
62
|
+
cause: error,
|
|
63
|
+
output: error.stderr || error.stdout
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses tag name to extract version
|
|
70
|
+
* Why: Tags have 'v' prefix (v2.7.0), need clean version
|
|
71
|
+
*
|
|
72
|
+
* @param {string} tagName - Tag name (e.g., "v2.7.0-SNAPSHOT")
|
|
73
|
+
* @returns {string} Version without 'v' prefix
|
|
74
|
+
*/
|
|
75
|
+
export function parseTagVersion(tagName) {
|
|
76
|
+
logger.debug('git-tag-manager - parseTagVersion', 'Parsing tag version', { tagName });
|
|
77
|
+
|
|
78
|
+
// Remove 'v' prefix if present
|
|
79
|
+
const version = tagName.replace(/^v/, '');
|
|
80
|
+
|
|
81
|
+
logger.debug('git-tag-manager - parseTagVersion', 'Version parsed', { tagName, version });
|
|
82
|
+
|
|
83
|
+
return version;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Formats version as tag name
|
|
88
|
+
* Why: Adds 'v' prefix consistently
|
|
89
|
+
*
|
|
90
|
+
* @param {string} version - Version string (e.g., "2.7.0")
|
|
91
|
+
* @returns {string} Tag name (e.g., "v2.7.0")
|
|
92
|
+
*/
|
|
93
|
+
export function formatTagName(version) {
|
|
94
|
+
logger.debug('git-tag-manager - formatTagName', 'Formatting tag name', { version });
|
|
95
|
+
|
|
96
|
+
// Add 'v' prefix if not present
|
|
97
|
+
const tagName = version.startsWith('v') ? version : `v${version}`;
|
|
98
|
+
|
|
99
|
+
logger.debug('git-tag-manager - formatTagName', 'Tag name formatted', { version, tagName });
|
|
100
|
+
|
|
101
|
+
return tagName;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets all local tags
|
|
106
|
+
* Why: Lists tags for comparison and validation
|
|
107
|
+
*
|
|
108
|
+
* @returns {Array<string>} Array of tag names
|
|
109
|
+
*/
|
|
110
|
+
export function getLocalTags() {
|
|
111
|
+
logger.debug('git-tag-manager - getLocalTags', 'Getting local tags');
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const output = execGitTagCommand('git tag --list');
|
|
115
|
+
|
|
116
|
+
if (!output) {
|
|
117
|
+
logger.debug('git-tag-manager - getLocalTags', 'No local tags found');
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const tags = output.split(/\r?\n/).filter(t => t.length > 0);
|
|
122
|
+
|
|
123
|
+
logger.debug('git-tag-manager - getLocalTags', 'Local tags retrieved', {
|
|
124
|
+
count: tags.length
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return tags;
|
|
128
|
+
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logger.error('git-tag-manager - getLocalTags', 'Failed to get local tags', error);
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gets latest local tag (by version order)
|
|
137
|
+
* Why: Determines current version for alignment checks
|
|
138
|
+
*
|
|
139
|
+
* @returns {string|null} Latest tag name or null if no tags
|
|
140
|
+
*/
|
|
141
|
+
export function getLatestLocalTag() {
|
|
142
|
+
logger.debug('git-tag-manager - getLatestLocalTag', 'Getting latest local tag');
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
// Get tags sorted by version (descending)
|
|
146
|
+
const output = execGitTagCommand('git tag --list --sort=-v:refname');
|
|
147
|
+
|
|
148
|
+
if (!output) {
|
|
149
|
+
logger.debug('git-tag-manager - getLatestLocalTag', 'No tags found');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const tags = output.split(/\r?\n/).filter(t => t.length > 0);
|
|
154
|
+
const latestTag = tags[0] || null;
|
|
155
|
+
|
|
156
|
+
logger.debug('git-tag-manager - getLatestLocalTag', 'Latest tag found', { latestTag });
|
|
157
|
+
|
|
158
|
+
return latestTag;
|
|
159
|
+
|
|
160
|
+
} catch (error) {
|
|
161
|
+
logger.error('git-tag-manager - getLatestLocalTag', 'Failed to get latest tag', error);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Gets all remote tags
|
|
168
|
+
* Why: Compare local tags with remote for push status
|
|
169
|
+
*
|
|
170
|
+
* @param {string} remoteName - Remote name (default: 'origin')
|
|
171
|
+
* @returns {Promise<Array<string>>} Array of tag names
|
|
172
|
+
*/
|
|
173
|
+
export async function getRemoteTags(remoteName = null) {
|
|
174
|
+
logger.debug('git-tag-manager - getRemoteTags', 'Getting remote tags', { remoteName });
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const remote = remoteName || getRemoteName();
|
|
178
|
+
const output = execGitTagCommand(`git ls-remote --tags ${remote}`);
|
|
179
|
+
|
|
180
|
+
if (!output) {
|
|
181
|
+
logger.debug('git-tag-manager - getRemoteTags', 'No remote tags found');
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Parse output: "hash refs/tags/tagname"
|
|
186
|
+
const tags = output
|
|
187
|
+
.split(/\r?\n/)
|
|
188
|
+
.filter(line => line.length > 0)
|
|
189
|
+
.map(line => {
|
|
190
|
+
const parts = line.split('\t');
|
|
191
|
+
if (parts.length < 2) return null;
|
|
192
|
+
const ref = parts[1];
|
|
193
|
+
// Extract tag name from refs/tags/v2.7.0
|
|
194
|
+
const match = ref.match(/refs\/tags\/(.+?)(\^\{\})?$/);
|
|
195
|
+
return match ? match[1] : null;
|
|
196
|
+
})
|
|
197
|
+
.filter(tag => tag !== null);
|
|
198
|
+
|
|
199
|
+
logger.debug('git-tag-manager - getRemoteTags', 'Remote tags retrieved', {
|
|
200
|
+
remote,
|
|
201
|
+
count: tags.length
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
return tags;
|
|
205
|
+
|
|
206
|
+
} catch (error) {
|
|
207
|
+
logger.error('git-tag-manager - getRemoteTags', 'Failed to get remote tags', error);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Gets latest remote tag (by version order)
|
|
214
|
+
* Why: Compare local version with deployed version
|
|
215
|
+
*
|
|
216
|
+
* @param {string} remoteName - Remote name (default: 'origin')
|
|
217
|
+
* @returns {Promise<string|null>} Latest tag name or null
|
|
218
|
+
*/
|
|
219
|
+
export async function getLatestRemoteTag(remoteName = null) {
|
|
220
|
+
logger.debug('git-tag-manager - getLatestRemoteTag', 'Getting latest remote tag');
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
const tags = await getRemoteTags(remoteName);
|
|
224
|
+
|
|
225
|
+
if (tags.length === 0) {
|
|
226
|
+
logger.debug('git-tag-manager - getLatestRemoteTag', 'No remote tags found');
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Sort tags by version (descending)
|
|
231
|
+
// Why: Reuse compareVersions for consistency and maintainability
|
|
232
|
+
const sortedTags = tags.sort((a, b) => {
|
|
233
|
+
const versionA = parseTagVersion(a);
|
|
234
|
+
const versionB = parseTagVersion(b);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
// compareVersions returns: 1 if a > b, -1 if a < b, 0 if equal
|
|
238
|
+
// For descending sort, we need to reverse the comparison
|
|
239
|
+
return -compareVersions(versionA, versionB);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// Fallback: keep original order if comparison fails
|
|
242
|
+
logger.debug('git-tag-manager - getLatestRemoteTag', 'Version comparison failed', {
|
|
243
|
+
versionA,
|
|
244
|
+
versionB,
|
|
245
|
+
error: error.message
|
|
246
|
+
});
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const latestTag = sortedTags[0];
|
|
252
|
+
|
|
253
|
+
logger.debug('git-tag-manager - getLatestRemoteTag', 'Latest remote tag found', { latestTag });
|
|
254
|
+
|
|
255
|
+
return latestTag;
|
|
256
|
+
|
|
257
|
+
} catch (error) {
|
|
258
|
+
logger.error('git-tag-manager - getLatestRemoteTag', 'Failed to get latest remote tag', error);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Checks if tag exists
|
|
265
|
+
* Why: Prevents duplicate tag creation
|
|
266
|
+
*
|
|
267
|
+
* @param {string} tagName - Tag name to check
|
|
268
|
+
* @param {string} location - Where to check: 'local' | 'remote' | 'both'
|
|
269
|
+
* @returns {Promise<boolean>} True if tag exists
|
|
270
|
+
*/
|
|
271
|
+
export async function tagExists(tagName, location = 'local') {
|
|
272
|
+
logger.debug('git-tag-manager - tagExists', 'Checking tag existence', { tagName, location });
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
if (location === 'local' || location === 'both') {
|
|
276
|
+
const localTags = getLocalTags();
|
|
277
|
+
if (localTags.includes(tagName)) {
|
|
278
|
+
logger.debug('git-tag-manager - tagExists', 'Tag exists locally', { tagName });
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (location === 'remote' || location === 'both') {
|
|
284
|
+
const remoteTags = await getRemoteTags();
|
|
285
|
+
if (remoteTags.includes(tagName)) {
|
|
286
|
+
logger.debug('git-tag-manager - tagExists', 'Tag exists on remote', { tagName });
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
logger.debug('git-tag-manager - tagExists', 'Tag does not exist', { tagName, location });
|
|
292
|
+
return false;
|
|
293
|
+
|
|
294
|
+
} catch (error) {
|
|
295
|
+
logger.error('git-tag-manager - tagExists', 'Failed to check tag existence', error);
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Creates annotated Git tag
|
|
302
|
+
* Why: Tags mark version releases with metadata
|
|
303
|
+
*
|
|
304
|
+
* @param {string} version - Version string (e.g., "2.7.0")
|
|
305
|
+
* @param {string} message - Tag annotation message
|
|
306
|
+
* @param {Object} options - Tag options
|
|
307
|
+
* @param {boolean} options.force - Force tag creation (overwrite existing)
|
|
308
|
+
* @returns {Object} Result: { success, tagName, error }
|
|
309
|
+
*/
|
|
310
|
+
export function createTag(version, message, { force = false } = {}) {
|
|
311
|
+
logger.debug('git-tag-manager - createTag', 'Creating tag', { version, message, force });
|
|
312
|
+
|
|
313
|
+
const tagName = formatTagName(version);
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
// Check if tag already exists (unless force)
|
|
317
|
+
if (!force) {
|
|
318
|
+
const localTags = getLocalTags();
|
|
319
|
+
if (localTags.includes(tagName)) {
|
|
320
|
+
logger.warning('git-tag-manager - createTag', 'Tag already exists', { tagName });
|
|
321
|
+
return {
|
|
322
|
+
success: false,
|
|
323
|
+
tagName,
|
|
324
|
+
error: `Tag ${tagName} already exists. Use --force to overwrite.`
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Create annotated tag
|
|
330
|
+
// Why: Escape message to prevent shell injection and handle special characters
|
|
331
|
+
const escapedMessage = message.replace(/"/g, '\\"').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
332
|
+
const forceFlag = force ? '-f' : '';
|
|
333
|
+
const command = `git tag ${forceFlag} -a ${tagName} -m "${escapedMessage}"`;
|
|
334
|
+
execGitTagCommand(command);
|
|
335
|
+
|
|
336
|
+
logger.debug('git-tag-manager - createTag', 'Tag created successfully', { tagName });
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
success: true,
|
|
340
|
+
tagName,
|
|
341
|
+
error: null
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
} catch (error) {
|
|
345
|
+
logger.error('git-tag-manager - createTag', 'Failed to create tag', error);
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
success: false,
|
|
349
|
+
tagName,
|
|
350
|
+
error: error.message
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Pushes tag(s) to remote
|
|
357
|
+
* Why: Publishes version tags to remote repository
|
|
358
|
+
*
|
|
359
|
+
* @param {string} remoteName - Remote name (default: from config)
|
|
360
|
+
* @param {string|Array<string>} tags - Tag name(s) to push, or 'all' for all tags
|
|
361
|
+
* @returns {Object} Result: { success, pushed, failed, error }
|
|
362
|
+
*/
|
|
363
|
+
export function pushTags(remoteName = null, tags = 'all') {
|
|
364
|
+
logger.debug('git-tag-manager - pushTags', 'Pushing tags', { remoteName, tags });
|
|
365
|
+
|
|
366
|
+
const remote = remoteName || getRemoteName();
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
if (tags === 'all') {
|
|
370
|
+
// Push all tags
|
|
371
|
+
execGitTagCommand(`git push ${remote} --tags`);
|
|
372
|
+
|
|
373
|
+
logger.debug('git-tag-manager - pushTags', 'All tags pushed successfully');
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
success: true,
|
|
377
|
+
pushed: 'all',
|
|
378
|
+
failed: [],
|
|
379
|
+
error: null
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
} else {
|
|
383
|
+
// Push specific tag(s)
|
|
384
|
+
const tagList = Array.isArray(tags) ? tags : [tags];
|
|
385
|
+
const pushed = [];
|
|
386
|
+
const failed = [];
|
|
387
|
+
|
|
388
|
+
for (const tag of tagList) {
|
|
389
|
+
try {
|
|
390
|
+
execGitTagCommand(`git push ${remote} ${tag}`);
|
|
391
|
+
pushed.push(tag);
|
|
392
|
+
logger.debug('git-tag-manager - pushTags', 'Tag pushed', { tag });
|
|
393
|
+
} catch (error) {
|
|
394
|
+
failed.push({ tag, error: error.message });
|
|
395
|
+
logger.error('git-tag-manager - pushTags', 'Failed to push tag', { tag, error });
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const success = failed.length === 0;
|
|
400
|
+
|
|
401
|
+
logger.debug('git-tag-manager - pushTags', 'Tag push complete', {
|
|
402
|
+
pushed: pushed.length,
|
|
403
|
+
failed: failed.length
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
success,
|
|
408
|
+
pushed,
|
|
409
|
+
failed,
|
|
410
|
+
error: failed.length > 0 ? `Failed to push ${failed.length} tag(s)` : null
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
} catch (error) {
|
|
415
|
+
logger.error('git-tag-manager - pushTags', 'Failed to push tags', error);
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
success: false,
|
|
419
|
+
pushed: [],
|
|
420
|
+
failed: [],
|
|
421
|
+
error: error.message
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Compares local and remote tags
|
|
428
|
+
* Why: Identifies unpushed tags for create-pr validation
|
|
429
|
+
*
|
|
430
|
+
* @returns {Promise<Object>} Comparison result: { localNewer, remoteNewer, common }
|
|
431
|
+
*/
|
|
432
|
+
export async function compareLocalAndRemoteTags() {
|
|
433
|
+
logger.debug('git-tag-manager - compareLocalAndRemoteTags', 'Comparing tags');
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const localTags = getLocalTags();
|
|
437
|
+
const remoteTags = await getRemoteTags();
|
|
438
|
+
|
|
439
|
+
const localSet = new Set(localTags);
|
|
440
|
+
const remoteSet = new Set(remoteTags);
|
|
441
|
+
|
|
442
|
+
// Tags only in local (not pushed)
|
|
443
|
+
const localNewer = localTags.filter(tag => !remoteSet.has(tag));
|
|
444
|
+
|
|
445
|
+
// Tags only in remote (local behind)
|
|
446
|
+
const remoteNewer = remoteTags.filter(tag => !localSet.has(tag));
|
|
447
|
+
|
|
448
|
+
// Tags in both
|
|
449
|
+
const common = localTags.filter(tag => remoteSet.has(tag));
|
|
450
|
+
|
|
451
|
+
const result = {
|
|
452
|
+
localNewer,
|
|
453
|
+
remoteNewer,
|
|
454
|
+
common
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
logger.debug('git-tag-manager - compareLocalAndRemoteTags', 'Comparison complete', {
|
|
458
|
+
localNewer: localNewer.length,
|
|
459
|
+
remoteNewer: remoteNewer.length,
|
|
460
|
+
common: common.length
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
return result;
|
|
464
|
+
|
|
465
|
+
} catch (error) {
|
|
466
|
+
logger.error('git-tag-manager - compareLocalAndRemoteTags', 'Comparison failed', error);
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
localNewer: [],
|
|
470
|
+
remoteNewer: [],
|
|
471
|
+
common: []
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Deletes a tag
|
|
478
|
+
* Why: Remove incorrect or test tags
|
|
479
|
+
*
|
|
480
|
+
* @param {string} tagName - Tag name to delete
|
|
481
|
+
* @param {Object} options - Delete options
|
|
482
|
+
* @param {boolean} options.remote - Also delete from remote
|
|
483
|
+
* @returns {Object} Result: { success, error }
|
|
484
|
+
*/
|
|
485
|
+
export function deleteTag(tagName, { remote = false } = {}) {
|
|
486
|
+
logger.debug('git-tag-manager - deleteTag', 'Deleting tag', { tagName, remote });
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
// Delete local tag
|
|
490
|
+
execGitTagCommand(`git tag -d ${tagName}`);
|
|
491
|
+
|
|
492
|
+
logger.debug('git-tag-manager - deleteTag', 'Local tag deleted', { tagName });
|
|
493
|
+
|
|
494
|
+
// Delete remote tag if requested
|
|
495
|
+
if (remote) {
|
|
496
|
+
const remoteName = getRemoteName();
|
|
497
|
+
execGitTagCommand(`git push ${remoteName} --delete ${tagName}`);
|
|
498
|
+
logger.debug('git-tag-manager - deleteTag', 'Remote tag deleted', { tagName });
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
success: true,
|
|
503
|
+
error: null
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
} catch (error) {
|
|
507
|
+
logger.error('git-tag-manager - deleteTag', 'Failed to delete tag', error);
|
|
508
|
+
|
|
509
|
+
return {
|
|
510
|
+
success: false,
|
|
511
|
+
error: error.message
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export { GitTagError };
|