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
package/CHANGELOG.md
CHANGED
|
@@ -5,28 +5,80 @@ Todos los cambios notables en este proyecto se documentarán en este archivo.
|
|
|
5
5
|
El formato está basado en [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.12.0] - 2026-02-03
|
|
9
|
+
|
|
10
|
+
### ✨ Added
|
|
11
|
+
|
|
12
|
+
- **Automatic version management with `bump-version` command**
|
|
13
|
+
- Detects project type (Node.js, Maven, or monorepo with both)
|
|
14
|
+
- Increments version following semantic versioning (major/minor/patch)
|
|
15
|
+
- Supports version suffixes (SNAPSHOT, RC, custom)
|
|
16
|
+
- Updates package.json and/or pom.xml automatically
|
|
17
|
+
- Generates CHANGELOG entries using Claude AI analysis of commits
|
|
18
|
+
- Creates annotated Git tags with `v` prefix (v2.7.0, v2.7.0-SNAPSHOT)
|
|
19
|
+
- Automatically pushes tags to remote
|
|
20
|
+
- CLI options: `--suffix`, `--update-changelog`, `--dry-run`, `--no-tag`, `--no-push`
|
|
21
|
+
|
|
22
|
+
- **CHANGELOG generation with Claude integration**
|
|
23
|
+
- Standalone `generate-changelog` command for independent CHANGELOG generation without version bumping
|
|
24
|
+
- Support for manual version specification in CHANGELOG generation via command argument
|
|
25
|
+
- Base branch selection for CHANGELOG comparison via `--base-branch` flag
|
|
26
|
+
- Release marking option via `--release` flag to control [Unreleased] header behavior
|
|
27
|
+
- Analyzes commits since last final version tag
|
|
28
|
+
- Categorizes changes by Conventional Commits types
|
|
29
|
+
- Generates human-readable entries in Keep a Changelog format
|
|
30
|
+
- Sections: Added, Changed, Fixed, Security, Deprecated, Removed
|
|
31
|
+
- Places entries in [Unreleased] for suffixed versions, [X.Y.Z] for final versions
|
|
32
|
+
|
|
33
|
+
- **Version alignment validation in `create-pr`** (#44)
|
|
34
|
+
- Checks consistency across package.json, pom.xml, CHANGELOG.md, and git tags
|
|
35
|
+
- Compares local version vs remote version
|
|
36
|
+
- Shows helpful prompts with fix commands if misalignment detected
|
|
37
|
+
- Allows user to continue or abort PR creation based on validation results
|
|
38
|
+
|
|
39
|
+
- **Unpushed tags detection in `create-pr`** (#44)
|
|
40
|
+
- Detects local tags not pushed to remote
|
|
41
|
+
- Prompts user to push tags before creating PR
|
|
42
|
+
- Prevents version inconsistencies between local and remote
|
|
43
|
+
|
|
44
|
+
- **New utility modules**:
|
|
45
|
+
- `lib/utils/version-manager.js` - Version detection, parsing, incrementing, validation
|
|
46
|
+
- `lib/utils/git-tag-manager.js` - Git tag operations (create, list, compare, push)
|
|
47
|
+
- `lib/utils/changelog-generator.js` - CHANGELOG generation with Claude
|
|
48
|
+
- `templates/GENERATE_CHANGELOG.md` - Claude prompt for changelog analysis
|
|
49
|
+
|
|
50
|
+
### 🔧 Changed
|
|
51
|
+
|
|
52
|
+
- Improved Windows compatibility by quoting git log format string in changelog-generator.js
|
|
53
|
+
|
|
54
|
+
### 📚 Documentation
|
|
55
|
+
|
|
56
|
+
- Updated help.js with bump-version command documentation and examples
|
|
57
|
+
- Added version workflow examples (development → RC → release)
|
|
58
|
+
- Updated CLAUDE.md with new utility modules and exports
|
|
59
|
+
|
|
8
60
|
## [2.11.0] - 2026-02-03
|
|
9
61
|
|
|
10
62
|
### ✨ Added
|
|
11
63
|
|
|
12
64
|
- **Auto-push functionality in `create-pr` command** - Automatically detects and pushes unpublished branches before creating PR (#59, #34)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
65
|
+
- Handles 3 scenarios: unpublished branch, unpushed commits, up-to-date branch
|
|
66
|
+
- Interactive confirmation prompt before pushing (configurable)
|
|
67
|
+
- Commit preview showing what will be pushed
|
|
68
|
+
- Diverged branch detection with helpful error message and resolution steps
|
|
69
|
+
- Never uses `git push --force` for security
|
|
18
70
|
|
|
19
71
|
- **New configuration options** under `github.pr`:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
72
|
+
- `autoPush: true` - Enable/disable auto-push (enabled by default)
|
|
73
|
+
- `pushConfirm: true` - Prompt for confirmation before push
|
|
74
|
+
- `showCommits: true` - Show commit preview before push
|
|
75
|
+
- `verifyRemote: true` - Verify remote exists before push
|
|
24
76
|
|
|
25
77
|
- **New git-operations functions**:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
78
|
+
- `getRemoteName()` - Get configured remote name (usually 'origin')
|
|
79
|
+
- `verifyRemoteExists()` - Check if remote is configured
|
|
80
|
+
- `getBranchPushStatus()` - Detect branch publish status and unpushed commits
|
|
81
|
+
- `pushBranch()` - Execute git push with upstream tracking
|
|
30
82
|
|
|
31
83
|
### 🔧 Fixed
|
|
32
84
|
|
|
@@ -76,13 +128,13 @@ This release implements a **modular, decoupled, reusable** architecture for bett
|
|
|
76
128
|
|
|
77
129
|
### 🎯 Benefits
|
|
78
130
|
|
|
79
|
-
| Aspect
|
|
80
|
-
|
|
81
|
-
| `bin/claude-hooks` size | 2,277 lines
|
|
82
|
-
| Command modules
|
|
83
|
-
| Testability
|
|
84
|
-
| Finding code
|
|
85
|
-
| Adding commands
|
|
131
|
+
| Aspect | Before | After |
|
|
132
|
+
| ----------------------- | --------------------- | ------------------------- |
|
|
133
|
+
| `bin/claude-hooks` size | 2,277 lines | 111 lines |
|
|
134
|
+
| Command modules | 1 (`setup-github.js`) | 13 files |
|
|
135
|
+
| Testability | Hard (monolithic) | Easy (modular) |
|
|
136
|
+
| Finding code | Search entire file | Match filename to command |
|
|
137
|
+
| Adding commands | Edit large file | Create new module |
|
|
86
138
|
|
|
87
139
|
### 📚 Documentation
|
|
88
140
|
|
|
@@ -94,6 +146,7 @@ This release implements a **modular, decoupled, reusable** architecture for bett
|
|
|
94
146
|
### 🔧 Technical Details
|
|
95
147
|
|
|
96
148
|
**New module structure:**
|
|
149
|
+
|
|
97
150
|
```
|
|
98
151
|
lib/commands/
|
|
99
152
|
├── helpers.js (389 lines) - Shared utilities
|
|
@@ -111,6 +164,7 @@ lib/commands/
|
|
|
111
164
|
```
|
|
112
165
|
|
|
113
166
|
**Key patterns implemented:**
|
|
167
|
+
|
|
114
168
|
- **Command Pattern**: Each CLI command is a self-contained module
|
|
115
169
|
- **Thin Controller**: `bin/claude-hooks` only routes, doesn't implement
|
|
116
170
|
- **Separation of Concerns**: UI helpers in `helpers.js`, business logic in specific modules
|
package/README.md
CHANGED
|
@@ -67,6 +67,65 @@ claude-hooks analyze-diff develop
|
|
|
67
67
|
# Generates PR metadata without creating
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
### Bump Version
|
|
71
|
+
|
|
72
|
+
Automatic version management with CHANGELOG generation and Git tagging:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Bump patch version (1.0.0 → 1.0.1)
|
|
76
|
+
claude-hooks bump-version patch
|
|
77
|
+
|
|
78
|
+
# Bump with suffix
|
|
79
|
+
claude-hooks bump-version minor --suffix SNAPSHOT # → 1.1.0-SNAPSHOT
|
|
80
|
+
|
|
81
|
+
# Bump and generate CHANGELOG
|
|
82
|
+
claude-hooks bump-version major --update-changelog
|
|
83
|
+
|
|
84
|
+
# Preview without applying
|
|
85
|
+
claude-hooks bump-version patch --dry-run
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**What it does:**
|
|
89
|
+
- Detects project type (Node.js, Maven, or monorepo with both)
|
|
90
|
+
- Updates `package.json` and/or `pom.xml`
|
|
91
|
+
- Generates CHANGELOG entry with Claude (analyzes commits)
|
|
92
|
+
- Creates annotated Git tag with `v` prefix (e.g., `v2.7.0`)
|
|
93
|
+
- Pushes tag to remote automatically
|
|
94
|
+
|
|
95
|
+
**Version workflow:**
|
|
96
|
+
```
|
|
97
|
+
2.7.0 → 2.8.0-SNAPSHOT # Start development
|
|
98
|
+
2.8.0-SNAPSHOT → 2.8.0-RC # Release candidate
|
|
99
|
+
2.8.0-RC → 2.8.0 # Final release
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Integration with create-pr:**
|
|
103
|
+
- Validates version alignment (package.json, pom.xml, CHANGELOG, tags)
|
|
104
|
+
- Detects and prompts to push unpushed tags
|
|
105
|
+
- Warns if local version ≤ remote version
|
|
106
|
+
|
|
107
|
+
### Generate CHANGELOG
|
|
108
|
+
|
|
109
|
+
Standalone CHANGELOG generation (without version bump):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Auto-detect version from package.json/pom.xml
|
|
113
|
+
claude-hooks generate-changelog
|
|
114
|
+
|
|
115
|
+
# Specific version with release marking
|
|
116
|
+
claude-hooks generate-changelog 2.7.0 --release
|
|
117
|
+
|
|
118
|
+
# Compare against different base branch
|
|
119
|
+
claude-hooks generate-changelog --base-branch develop
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**What it does:**
|
|
123
|
+
- Analyzes commits since last tag using Claude
|
|
124
|
+
- Categorizes by Conventional Commits types (feat, fix, refactor, etc.)
|
|
125
|
+
- Generates Keep a Changelog format entries
|
|
126
|
+
- Updates CHANGELOG.md automatically
|
|
127
|
+
- Useful when `bump-version --update-changelog` fails
|
|
128
|
+
|
|
70
129
|
### Disable/Enable Hooks
|
|
71
130
|
|
|
72
131
|
```bash
|
package/bin/claude-hooks
CHANGED
|
@@ -22,6 +22,8 @@ import { runMigrateConfig } from '../lib/commands/migrate-config.js';
|
|
|
22
22
|
import { runSetDebug } from '../lib/commands/debug.js';
|
|
23
23
|
import { runShowTelemetry, runClearTelemetry } from '../lib/commands/telemetry-cmd.js';
|
|
24
24
|
import { runShowHelp, runShowVersion } from '../lib/commands/help.js';
|
|
25
|
+
import { runBumpVersion } from '../lib/commands/bump-version.js';
|
|
26
|
+
import { runGenerateChangelog } from '../lib/commands/generate-changelog.js';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* Main CLI router
|
|
@@ -75,6 +77,12 @@ async function main() {
|
|
|
75
77
|
case 'migrate-config':
|
|
76
78
|
await runMigrateConfig();
|
|
77
79
|
break;
|
|
80
|
+
case 'bump-version':
|
|
81
|
+
await runBumpVersion(args.slice(1));
|
|
82
|
+
break;
|
|
83
|
+
case 'generate-changelog':
|
|
84
|
+
await runGenerateChangelog(args.slice(1));
|
|
85
|
+
break;
|
|
78
86
|
case 'telemetry':
|
|
79
87
|
// Handle subcommands: telemetry show, telemetry clear
|
|
80
88
|
if (args[1] === 'show' || args[1] === undefined) {
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: bump-version.js
|
|
3
|
+
* Purpose: Bump version command with automatic CHANGELOG and Git tag
|
|
4
|
+
*
|
|
5
|
+
* Workflow:
|
|
6
|
+
* 1. Validate prerequisites (clean working directory, valid branch, remote)
|
|
7
|
+
* 2. Detect project type and current version
|
|
8
|
+
* 3. Calculate new version with optional suffix
|
|
9
|
+
* 4. Update version file(s)
|
|
10
|
+
* 5. [Optional] Generate and update CHANGELOG
|
|
11
|
+
* 6. Create annotated Git tag
|
|
12
|
+
* 7. Push tag to remote
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import {
|
|
19
|
+
detectProjectType,
|
|
20
|
+
getCurrentVersion,
|
|
21
|
+
incrementVersion,
|
|
22
|
+
updateVersion,
|
|
23
|
+
parseVersion
|
|
24
|
+
} from '../utils/version-manager.js';
|
|
25
|
+
import {
|
|
26
|
+
createTag,
|
|
27
|
+
pushTags,
|
|
28
|
+
tagExists,
|
|
29
|
+
formatTagName
|
|
30
|
+
} from '../utils/git-tag-manager.js';
|
|
31
|
+
import {
|
|
32
|
+
generateChangelogEntry,
|
|
33
|
+
updateChangelogFile
|
|
34
|
+
} from '../utils/changelog-generator.js';
|
|
35
|
+
import {
|
|
36
|
+
getRepoRoot,
|
|
37
|
+
getCurrentBranch,
|
|
38
|
+
verifyRemoteExists,
|
|
39
|
+
getRemoteName
|
|
40
|
+
} from '../utils/git-operations.js';
|
|
41
|
+
import { getConfig } from '../config.js';
|
|
42
|
+
import { showInfo, showSuccess, showError, showWarning, promptConfirmation } from '../utils/interactive-ui.js';
|
|
43
|
+
import logger from '../utils/logger.js';
|
|
44
|
+
import {
|
|
45
|
+
colors,
|
|
46
|
+
error,
|
|
47
|
+
success,
|
|
48
|
+
info,
|
|
49
|
+
warning,
|
|
50
|
+
checkGitRepo
|
|
51
|
+
} from './helpers.js';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validates prerequisites before version bump
|
|
55
|
+
* Why: Prevents version bump in invalid state
|
|
56
|
+
*
|
|
57
|
+
* @returns {Object} Validation result: { valid, errors }
|
|
58
|
+
*/
|
|
59
|
+
function validatePrerequisites() {
|
|
60
|
+
logger.debug('bump-version - validatePrerequisites', 'Validating prerequisites');
|
|
61
|
+
|
|
62
|
+
const errors = [];
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Check if git repo
|
|
66
|
+
if (!checkGitRepo()) {
|
|
67
|
+
errors.push('Not a git repository');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check for clean working directory
|
|
71
|
+
try {
|
|
72
|
+
const status = execSync('git status --porcelain', { encoding: 'utf8' }).trim();
|
|
73
|
+
if (status) {
|
|
74
|
+
errors.push('Working directory has uncommitted changes');
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
errors.push('Failed to check git status');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for valid branch (not detached HEAD)
|
|
81
|
+
const currentBranch = getCurrentBranch();
|
|
82
|
+
if (!currentBranch || currentBranch === 'unknown') {
|
|
83
|
+
errors.push('Not on a valid branch (detached HEAD?)');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for remote
|
|
87
|
+
const remoteName = getRemoteName();
|
|
88
|
+
if (!verifyRemoteExists(remoteName)) {
|
|
89
|
+
errors.push(`Remote '${remoteName}' does not exist`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const valid = errors.length === 0;
|
|
93
|
+
|
|
94
|
+
logger.debug('bump-version - validatePrerequisites', 'Validation complete', {
|
|
95
|
+
valid,
|
|
96
|
+
errorCount: errors.length
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return { valid, errors };
|
|
100
|
+
|
|
101
|
+
} catch (err) {
|
|
102
|
+
logger.error('bump-version - validatePrerequisites', 'Validation failed', err);
|
|
103
|
+
return {
|
|
104
|
+
valid: false,
|
|
105
|
+
errors: ['Validation error: ' + err.message]
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parses command line arguments
|
|
112
|
+
* Why: Extracts bump type and options from CLI args
|
|
113
|
+
*
|
|
114
|
+
* @param {Array<string>} args - CLI arguments
|
|
115
|
+
* @returns {Object} Parsed args: { bumpType, suffix, updateChangelog, baseBranch, dryRun, noTag, noPush }
|
|
116
|
+
*/
|
|
117
|
+
function parseArguments(args) {
|
|
118
|
+
logger.debug('bump-version - parseArguments', 'Parsing arguments', { args });
|
|
119
|
+
|
|
120
|
+
const parsed = {
|
|
121
|
+
bumpType: null,
|
|
122
|
+
suffix: null,
|
|
123
|
+
updateChangelog: false,
|
|
124
|
+
baseBranch: 'main',
|
|
125
|
+
dryRun: false,
|
|
126
|
+
noTag: false,
|
|
127
|
+
noPush: false
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// First argument should be bump type
|
|
131
|
+
if (args.length === 0) {
|
|
132
|
+
return parsed;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const firstArg = args[0].toLowerCase();
|
|
136
|
+
if (['major', 'minor', 'patch'].includes(firstArg)) {
|
|
137
|
+
parsed.bumpType = firstArg;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Parse options
|
|
141
|
+
for (let i = 1; i < args.length; i++) {
|
|
142
|
+
const arg = args[i];
|
|
143
|
+
|
|
144
|
+
if (arg === '--suffix' && i + 1 < args.length) {
|
|
145
|
+
parsed.suffix = args[i + 1];
|
|
146
|
+
i++; // Skip next arg
|
|
147
|
+
} else if (arg === '--update-changelog') {
|
|
148
|
+
parsed.updateChangelog = true;
|
|
149
|
+
// Check if next arg is a branch name (not starting with --)
|
|
150
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
151
|
+
parsed.baseBranch = args[i + 1];
|
|
152
|
+
i++; // Skip next arg
|
|
153
|
+
}
|
|
154
|
+
} else if (arg === '--dry-run') {
|
|
155
|
+
parsed.dryRun = true;
|
|
156
|
+
} else if (arg === '--no-tag') {
|
|
157
|
+
parsed.noTag = true;
|
|
158
|
+
} else if (arg === '--no-push') {
|
|
159
|
+
parsed.noPush = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
logger.debug('bump-version - parseArguments', 'Arguments parsed', parsed);
|
|
164
|
+
|
|
165
|
+
return parsed;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Displays dry run preview
|
|
170
|
+
* Why: Shows what would change without applying
|
|
171
|
+
*
|
|
172
|
+
* @param {Object} info - Version info
|
|
173
|
+
*/
|
|
174
|
+
function showDryRunPreview(info) {
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(`${colors.yellow}═════════════════════════════════════════════════${colors.reset}`);
|
|
177
|
+
console.log(`${colors.yellow} DRY RUN - No changes will be made ${colors.reset}`);
|
|
178
|
+
console.log(`${colors.yellow}═════════════════════════════════════════════════${colors.reset}`);
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(`${colors.blue}Project Type:${colors.reset} ${info.projectType}`);
|
|
181
|
+
console.log(`${colors.blue}Current Version:${colors.reset} ${info.currentVersion}`);
|
|
182
|
+
console.log(`${colors.green}New Version:${colors.reset} ${info.newVersion}`);
|
|
183
|
+
console.log(`${colors.blue}Tag Name:${colors.reset} ${info.tagName}`);
|
|
184
|
+
console.log('');
|
|
185
|
+
|
|
186
|
+
if (info.files.length > 0) {
|
|
187
|
+
console.log(`${colors.blue}Files to update:${colors.reset}`);
|
|
188
|
+
info.files.forEach(file => console.log(` - ${file}`));
|
|
189
|
+
console.log('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (info.updateChangelog) {
|
|
193
|
+
console.log(`${colors.blue}CHANGELOG:${colors.reset} Will be generated and updated`);
|
|
194
|
+
console.log('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`${colors.yellow}Run without --dry-run to apply these changes${colors.reset}`);
|
|
198
|
+
console.log('');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Bump version command
|
|
203
|
+
* @param {Array<string>} args - Command arguments
|
|
204
|
+
*/
|
|
205
|
+
export async function runBumpVersion(args) {
|
|
206
|
+
logger.debug('bump-version', 'Starting bump-version command', { args });
|
|
207
|
+
|
|
208
|
+
// Parse arguments
|
|
209
|
+
const options = parseArguments(args);
|
|
210
|
+
|
|
211
|
+
if (!options.bumpType) {
|
|
212
|
+
error('Usage: claude-hooks bump-version <major|minor|patch> [options]');
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log('Options:');
|
|
215
|
+
console.log(' --suffix <value> Add version suffix (e.g., SNAPSHOT, RC)');
|
|
216
|
+
console.log(' --update-changelog [branch] Generate CHANGELOG entry (default branch: main)');
|
|
217
|
+
console.log(' --dry-run Preview changes without applying');
|
|
218
|
+
console.log(' --no-tag Skip Git tag creation');
|
|
219
|
+
console.log(' --no-push Create tag but don\'t push to remote');
|
|
220
|
+
console.log('');
|
|
221
|
+
console.log('Examples:');
|
|
222
|
+
console.log(' claude-hooks bump-version minor --suffix SNAPSHOT');
|
|
223
|
+
console.log(' claude-hooks bump-version patch --update-changelog');
|
|
224
|
+
console.log(' claude-hooks bump-version major --dry-run');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
showInfo(`🔢 Bumping version (${options.bumpType})...`);
|
|
229
|
+
console.log('');
|
|
230
|
+
|
|
231
|
+
// Step 1: Validate prerequisites
|
|
232
|
+
logger.debug('bump-version', 'Step 1: Validating prerequisites');
|
|
233
|
+
const validation = validatePrerequisites();
|
|
234
|
+
|
|
235
|
+
if (!validation.valid) {
|
|
236
|
+
showError('Prerequisites validation failed:');
|
|
237
|
+
console.log('');
|
|
238
|
+
validation.errors.forEach(err => console.log(` ❌ ${err}`));
|
|
239
|
+
console.log('');
|
|
240
|
+
console.log('Please fix these issues:');
|
|
241
|
+
console.log(' - Commit or stash uncommitted changes: git stash');
|
|
242
|
+
console.log(' - Checkout a branch: git checkout main');
|
|
243
|
+
console.log(' - Configure remote: git remote add origin <url>');
|
|
244
|
+
console.log('');
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
showSuccess('Prerequisites validated');
|
|
249
|
+
|
|
250
|
+
// Step 2: Detect project type and current version
|
|
251
|
+
logger.debug('bump-version', 'Step 2: Detecting project type and version');
|
|
252
|
+
const projectType = detectProjectType();
|
|
253
|
+
|
|
254
|
+
if (projectType === 'none') {
|
|
255
|
+
showError('No version files found');
|
|
256
|
+
console.log('');
|
|
257
|
+
console.log('This command requires either:');
|
|
258
|
+
console.log(' - package.json (Node.js project)');
|
|
259
|
+
console.log(' - pom.xml (Maven project)');
|
|
260
|
+
console.log('');
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const versions = getCurrentVersion(projectType);
|
|
265
|
+
const currentVersion = versions.resolved;
|
|
266
|
+
|
|
267
|
+
if (!currentVersion) {
|
|
268
|
+
showError('Could not determine current version');
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Warn if monorepo has version mismatch
|
|
273
|
+
if (versions.mismatch) {
|
|
274
|
+
showWarning('Version mismatch detected in monorepo:');
|
|
275
|
+
console.log(` package.json: ${versions.packageJson}`);
|
|
276
|
+
console.log(` pom.xml: ${versions.pomXml}`);
|
|
277
|
+
console.log('');
|
|
278
|
+
console.log('Both files will be updated to the new version.');
|
|
279
|
+
console.log('');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
showInfo(`Project: ${projectType}`);
|
|
283
|
+
showInfo(`Current version: ${currentVersion}`);
|
|
284
|
+
|
|
285
|
+
// Step 3: Calculate new version
|
|
286
|
+
logger.debug('bump-version', 'Step 3: Calculating new version');
|
|
287
|
+
const newVersion = incrementVersion(currentVersion, options.bumpType, options.suffix);
|
|
288
|
+
const tagName = formatTagName(newVersion);
|
|
289
|
+
|
|
290
|
+
showSuccess(`New version: ${newVersion}`);
|
|
291
|
+
console.log('');
|
|
292
|
+
|
|
293
|
+
// Prepare files list
|
|
294
|
+
const filesToUpdate = [];
|
|
295
|
+
if (projectType === 'node' || projectType === 'both') {
|
|
296
|
+
filesToUpdate.push('package.json');
|
|
297
|
+
}
|
|
298
|
+
if (projectType === 'maven' || projectType === 'both') {
|
|
299
|
+
filesToUpdate.push('pom.xml');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Dry run preview
|
|
303
|
+
if (options.dryRun) {
|
|
304
|
+
showDryRunPreview({
|
|
305
|
+
projectType,
|
|
306
|
+
currentVersion,
|
|
307
|
+
newVersion,
|
|
308
|
+
tagName,
|
|
309
|
+
files: filesToUpdate,
|
|
310
|
+
updateChangelog: options.updateChangelog
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Confirm with user
|
|
316
|
+
const shouldContinue = await promptConfirmation(
|
|
317
|
+
`Update version to ${newVersion}?`,
|
|
318
|
+
true
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
if (!shouldContinue) {
|
|
322
|
+
showInfo('Version bump cancelled');
|
|
323
|
+
process.exit(0);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log('');
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
// Step 4: Update version files
|
|
330
|
+
logger.debug('bump-version', 'Step 4: Updating version files');
|
|
331
|
+
showInfo('Updating version files...');
|
|
332
|
+
updateVersion(projectType, newVersion);
|
|
333
|
+
|
|
334
|
+
filesToUpdate.forEach(file => {
|
|
335
|
+
showSuccess(`✓ Updated ${file}`);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
console.log('');
|
|
339
|
+
|
|
340
|
+
// Step 5: Generate CHANGELOG (if requested)
|
|
341
|
+
if (options.updateChangelog) {
|
|
342
|
+
logger.debug('bump-version', 'Step 5: Generating CHANGELOG');
|
|
343
|
+
showInfo('Generating CHANGELOG entry...');
|
|
344
|
+
|
|
345
|
+
const config = await getConfig();
|
|
346
|
+
const isReleaseVersion = !options.suffix; // Final version if no suffix
|
|
347
|
+
|
|
348
|
+
const changelogResult = await generateChangelogEntry({
|
|
349
|
+
version: newVersion,
|
|
350
|
+
isReleaseVersion,
|
|
351
|
+
baseBranch: options.baseBranch,
|
|
352
|
+
config
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (changelogResult.content) {
|
|
356
|
+
const updated = updateChangelogFile(changelogResult.content);
|
|
357
|
+
if (updated) {
|
|
358
|
+
showSuccess('✓ CHANGELOG.md updated');
|
|
359
|
+
} else {
|
|
360
|
+
showWarning('⚠ Failed to update CHANGELOG.md');
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
showWarning('⚠ No commits found for CHANGELOG');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
console.log('');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Step 6: Create Git tag (if not disabled)
|
|
370
|
+
if (!options.noTag) {
|
|
371
|
+
logger.debug('bump-version', 'Step 6: Creating Git tag');
|
|
372
|
+
showInfo('Creating Git tag...');
|
|
373
|
+
|
|
374
|
+
// Check if tag already exists
|
|
375
|
+
const exists = await tagExists(tagName, 'local');
|
|
376
|
+
if (exists) {
|
|
377
|
+
showWarning(`Tag ${tagName} already exists locally`);
|
|
378
|
+
const shouldOverwrite = await promptConfirmation(
|
|
379
|
+
'Overwrite existing tag?',
|
|
380
|
+
false
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
if (!shouldOverwrite) {
|
|
384
|
+
showInfo('Tag creation skipped');
|
|
385
|
+
console.log('');
|
|
386
|
+
console.log('Version files updated successfully, but tag was not created.');
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const tagMessage = `Release version ${newVersion}`;
|
|
392
|
+
const tagResult = createTag(newVersion, tagMessage, { force: exists });
|
|
393
|
+
|
|
394
|
+
if (tagResult.success) {
|
|
395
|
+
showSuccess(`✓ Tag created: ${tagName}`);
|
|
396
|
+
console.log('');
|
|
397
|
+
|
|
398
|
+
// Step 7: Push tag (if not disabled)
|
|
399
|
+
if (!options.noPush) {
|
|
400
|
+
logger.debug('bump-version', 'Step 7: Pushing tag to remote');
|
|
401
|
+
showInfo('Pushing tag to remote...');
|
|
402
|
+
|
|
403
|
+
const pushResult = pushTags(null, tagName);
|
|
404
|
+
|
|
405
|
+
if (pushResult.success) {
|
|
406
|
+
showSuccess(`✓ Tag pushed to remote`);
|
|
407
|
+
} else {
|
|
408
|
+
showError(`Failed to push tag: ${pushResult.error}`);
|
|
409
|
+
console.log('');
|
|
410
|
+
console.log('You can push the tag manually:');
|
|
411
|
+
console.log(` git push origin ${tagName}`);
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
showInfo('Tag push skipped (--no-push)');
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log('To push the tag later:');
|
|
417
|
+
console.log(` git push origin ${tagName}`);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
showError(`Failed to create tag: ${tagResult.error}`);
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
showInfo('Tag creation skipped (--no-tag)');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Success summary
|
|
427
|
+
console.log('');
|
|
428
|
+
console.log(`${colors.green}════════════════════════════════════════════════${colors.reset}`);
|
|
429
|
+
console.log(`${colors.green} Version Bump Complete ✅ ${colors.reset}`);
|
|
430
|
+
console.log(`${colors.green}════════════════════════════════════════════════${colors.reset}`);
|
|
431
|
+
console.log('');
|
|
432
|
+
console.log(`${colors.blue}Old version:${colors.reset} ${currentVersion}`);
|
|
433
|
+
console.log(`${colors.blue}New version:${colors.reset} ${newVersion}`);
|
|
434
|
+
console.log(`${colors.blue}Tag:${colors.reset} ${tagName}`);
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log('Next steps:');
|
|
437
|
+
console.log(' 1. Review the changes: git diff');
|
|
438
|
+
console.log(' 2. Commit the version bump: git add . && git commit -m "chore: bump version to ' + newVersion + '"');
|
|
439
|
+
console.log(' 3. Create PR: claude-hooks create-pr main');
|
|
440
|
+
console.log('');
|
|
441
|
+
|
|
442
|
+
} catch (err) {
|
|
443
|
+
logger.error('bump-version', 'Version bump failed', err);
|
|
444
|
+
showError(`Version bump failed: ${err.message}`);
|
|
445
|
+
console.log('');
|
|
446
|
+
console.log('The operation was interrupted. You may need to:');
|
|
447
|
+
console.log(' - Revert changes: git checkout .');
|
|
448
|
+
console.log(' - Delete tag if created: git tag -d ' + tagName);
|
|
449
|
+
console.log('');
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
}
|