vnxt 1.9.1 → 1.13.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/README.md +73 -36
- package/package.json +1 -1
- package/vnxt.js +465 -360
package/README.md
CHANGED
|
@@ -22,42 +22,56 @@ A lightweight CLI tool for automated version bumping with changelog generation a
|
|
|
22
22
|
|
|
23
23
|
## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Installation
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### npm (all platforms)
|
|
26
26
|
|
|
27
|
-
**Bash/PowerShell:**
|
|
28
27
|
```bash
|
|
29
28
|
npm install -g vnxt
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
|
|
31
|
+
### Scoop (Windows)
|
|
32
|
+
|
|
33
|
+
```powershell
|
|
34
|
+
scoop bucket add vnxt https://github.com/n-orrow/scoop-vnxt
|
|
35
|
+
scoop install vnxt
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Chocolatey (Windows)
|
|
39
|
+
|
|
40
|
+
> ⏳ Pending moderation — will be available on the Chocolatey community repository shortly.
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
choco install vnxt
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Homebrew (macOS/Linux)
|
|
47
|
+
|
|
33
48
|
```bash
|
|
34
|
-
vnxt
|
|
35
|
-
|
|
49
|
+
brew tap n-orrow/vnxt
|
|
50
|
+
brew install vnxt
|
|
36
51
|
```
|
|
37
52
|
|
|
38
|
-
###
|
|
53
|
+
### From Source
|
|
39
54
|
|
|
40
55
|
**Bash/macOS/Linux:**
|
|
41
56
|
```bash
|
|
42
|
-
# Clone the repository
|
|
43
57
|
git clone https://github.com/n-orrow/vnxt.git
|
|
44
58
|
cd vnxt
|
|
45
|
-
|
|
46
|
-
# Install globally via npm link
|
|
47
59
|
chmod +x vnxt.js
|
|
48
60
|
npm link
|
|
49
61
|
```
|
|
50
62
|
|
|
51
63
|
**PowerShell/Windows:**
|
|
52
64
|
```powershell
|
|
53
|
-
# Clone the repository
|
|
54
65
|
git clone https://github.com/n-orrow/vnxt.git
|
|
55
66
|
cd vnxt
|
|
56
|
-
|
|
57
|
-
# Install globally via npm link
|
|
58
67
|
npm link
|
|
59
68
|
```
|
|
60
69
|
|
|
70
|
+
After installation, you can use either `vnxt` or the shorter alias `vx`:
|
|
71
|
+
```bash
|
|
72
|
+
vx --help
|
|
73
|
+
```
|
|
74
|
+
|
|
61
75
|
## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Usage
|
|
62
76
|
|
|
63
77
|
### Basic Examples
|
|
@@ -89,18 +103,19 @@ All options work with both `vnxt` and `vx`:
|
|
|
89
103
|
```
|
|
90
104
|
-m, --message <msg> Commit message (required unless using interactive mode)
|
|
91
105
|
-t, --type <type> Version type: patch, minor, major (auto-detected from message)
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
106
|
+
-sv, --set-version <v> Set a specific version (e.g., 2.0.0-beta.1)
|
|
107
|
+
-vv, --vnxt-version Show the installed vnxt version
|
|
108
|
+
-gv, --get-version Show the current project's version
|
|
109
|
+
-p, --push Push to remote with tags
|
|
95
110
|
-dnp, --no-push Prevent auto-push (overrides config)
|
|
96
|
-
--publish
|
|
97
|
-
-c, --changelog
|
|
98
|
-
-d, --dry-run
|
|
99
|
-
-a, --all [mode]
|
|
100
|
-
|
|
101
|
-
-r, --release
|
|
102
|
-
-q, --quiet
|
|
103
|
-
-h, --help
|
|
111
|
+
--publish Push and trigger npm publish via GitHub Actions (implies --push)
|
|
112
|
+
-c, --changelog Update CHANGELOG.md
|
|
113
|
+
-d, --dry-run Show what would happen without making changes
|
|
114
|
+
-a, --all [mode] Stage files before versioning (prompts if no mode)
|
|
115
|
+
Modes: tracked, all, interactive (i), patch (p)
|
|
116
|
+
-r, --release Generate release notes file (saved to release-notes/)
|
|
117
|
+
-q, --quiet Minimal output (errors only)
|
|
118
|
+
-h, --help Show help message
|
|
104
119
|
```
|
|
105
120
|
|
|
106
121
|
### Automatic Version Detection
|
|
@@ -112,7 +127,7 @@ vnxt automatically detects the version bump type from your commit message:
|
|
|
112
127
|
- `patch:` → **patch** version bump
|
|
113
128
|
- `feat:` or `feature:` → **minor** version bump
|
|
114
129
|
- `fix:` → **patch** version bump
|
|
115
|
-
- `BREAKING
|
|
130
|
+
- `BREAKING` (anywhere in message) or `breaking:` → **major** version bump
|
|
116
131
|
|
|
117
132
|
You can override this with the `-t` flag.
|
|
118
133
|
|
|
@@ -144,8 +159,8 @@ Set a specific version number (useful for pre-releases):
|
|
|
144
159
|
|
|
145
160
|
**Bash/PowerShell:**
|
|
146
161
|
```bash
|
|
147
|
-
vx -
|
|
148
|
-
vx -
|
|
162
|
+
vx -sv 2.0.0-beta.1 -m "beta: initial release candidate"
|
|
163
|
+
vx -sv 1.5.0-rc.2 -m "release candidate 2"
|
|
149
164
|
```
|
|
150
165
|
|
|
151
166
|
### Changelog Generation
|
|
@@ -177,19 +192,25 @@ Generate a formatted release notes file:
|
|
|
177
192
|
vx -m "feat: major feature release" -r
|
|
178
193
|
```
|
|
179
194
|
|
|
180
|
-
|
|
195
|
+
You'll be prompted to add optional context before the file is created. Release notes are saved to `release-notes/v1.2.0.md` (respects your `tagPrefix` config):
|
|
181
196
|
```markdown
|
|
182
197
|
# Release v1.2.0
|
|
183
198
|
|
|
184
|
-
Released: 2024-02-10
|
|
199
|
+
Released: 2024-02-10 at 14:32:00 UTC
|
|
200
|
+
Author: Your Name
|
|
185
201
|
|
|
186
202
|
## Changes
|
|
187
203
|
feat: major feature release
|
|
188
204
|
|
|
189
205
|
## Installation
|
|
190
206
|
npm install your-package@1.2.0
|
|
207
|
+
|
|
208
|
+
## Full Changelog
|
|
209
|
+
See CHANGELOG.md for complete version history.
|
|
191
210
|
```
|
|
192
211
|
|
|
212
|
+
**Note:** `--publish` automatically generates release notes too, so `-r` is only needed for standalone bumps where you want the file without publishing.
|
|
213
|
+
|
|
193
214
|
### File Staging Options
|
|
194
215
|
|
|
195
216
|
vnxt offers flexible file staging with the `-a` flag:
|
|
@@ -237,14 +258,28 @@ Display the installed vnxt version:
|
|
|
237
258
|
|
|
238
259
|
**Bash/PowerShell:**
|
|
239
260
|
```bash
|
|
240
|
-
vx -
|
|
261
|
+
vx -vv
|
|
262
|
+
# or
|
|
263
|
+
vnxt --vnxt-version
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Output:
|
|
267
|
+
```
|
|
268
|
+
vnxt v1.9.3
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Display the current project's version:
|
|
272
|
+
|
|
273
|
+
**Bash/PowerShell:**
|
|
274
|
+
```bash
|
|
275
|
+
vx -gv
|
|
241
276
|
# or
|
|
242
|
-
vnxt --version
|
|
277
|
+
vnxt --get-version
|
|
243
278
|
```
|
|
244
279
|
|
|
245
280
|
Output:
|
|
246
281
|
```
|
|
247
|
-
|
|
282
|
+
my-package v2.4.1
|
|
248
283
|
```
|
|
249
284
|
|
|
250
285
|
### npm Publish
|
|
@@ -257,10 +292,12 @@ vx -m "feat: new feature" --publish
|
|
|
257
292
|
```
|
|
258
293
|
|
|
259
294
|
This will:
|
|
260
|
-
1.
|
|
261
|
-
2.
|
|
262
|
-
3.
|
|
263
|
-
4.
|
|
295
|
+
1. Prompt for optional release notes context
|
|
296
|
+
2. Bump the version and commit
|
|
297
|
+
3. Auto-generate a release notes file in `release-notes/`
|
|
298
|
+
4. Push with a standard `v*` tag
|
|
299
|
+
5. Push an additional `publish/v*` tag
|
|
300
|
+
6. GitHub Actions detects the `publish/v*` tag and publishes to npm automatically
|
|
264
301
|
|
|
265
302
|
This means you can batch up multiple changes without publishing to npm each time:
|
|
266
303
|
```bash
|
|
@@ -417,7 +454,7 @@ vx -m "feat: final feature" --publish
|
|
|
417
454
|
|
|
418
455
|
**Bash/PowerShell:**
|
|
419
456
|
```bash
|
|
420
|
-
vx -
|
|
457
|
+
vx -vv
|
|
421
458
|
```
|
|
422
459
|
|
|
423
460
|
## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Troubleshooting
|
package/package.json
CHANGED
package/vnxt.js
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// TODOs
|
|
5
|
+
// -----------------------------------------------------------------------------
|
|
6
|
+
// 1. Nothing comes to mind right now
|
|
7
|
+
// =============================================================================
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Imports & Constants
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
const {execSync, execFileSync} = require('child_process');
|
|
4
14
|
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
5
16
|
const readline = require('readline');
|
|
6
17
|
|
|
7
|
-
// ANSI color codes for terminal output
|
|
8
18
|
const colors = {
|
|
9
19
|
reset: '\x1b[0m',
|
|
10
20
|
bright: '\x1b[1m',
|
|
11
21
|
dim: '\x1b[2m',
|
|
12
|
-
|
|
13
|
-
// Foreground colors
|
|
14
22
|
red: '\x1b[31m',
|
|
15
23
|
green: '\x1b[32m',
|
|
16
24
|
yellow: '\x1b[33m',
|
|
@@ -20,10 +28,13 @@ const colors = {
|
|
|
20
28
|
gray: '\x1b[90m'
|
|
21
29
|
};
|
|
22
30
|
|
|
23
|
-
|
|
31
|
+
const args = process.argv.slice(2);
|
|
24
32
|
let quietMode = false;
|
|
25
33
|
|
|
26
|
-
//
|
|
34
|
+
// =============================================================================
|
|
35
|
+
// Logging
|
|
36
|
+
// =============================================================================
|
|
37
|
+
|
|
27
38
|
function log(message, color = '') {
|
|
28
39
|
if (quietMode) return;
|
|
29
40
|
if (color && colors[color] && config.colors) {
|
|
@@ -34,8 +45,6 @@ function log(message, color = '') {
|
|
|
34
45
|
}
|
|
35
46
|
|
|
36
47
|
function logError(message) {
|
|
37
|
-
// Errors always show, even in quiet mode
|
|
38
|
-
// Colors can be disabled for errors too
|
|
39
48
|
if (config.colors) {
|
|
40
49
|
console.error(`${colors.red}${message}${colors.reset}`);
|
|
41
50
|
} else {
|
|
@@ -43,10 +52,10 @@ function logError(message) {
|
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
//
|
|
47
|
-
|
|
55
|
+
// =============================================================================
|
|
56
|
+
// Argument Helpers
|
|
57
|
+
// =============================================================================
|
|
48
58
|
|
|
49
|
-
// Helper to parse flags
|
|
50
59
|
function getFlag(flag, short) {
|
|
51
60
|
const index = args.indexOf(flag) !== -1 ? args.indexOf(flag) : args.indexOf(short);
|
|
52
61
|
if (index === -1) return null;
|
|
@@ -54,351 +63,382 @@ function getFlag(flag, short) {
|
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
function hasFlag(flag, short) {
|
|
57
|
-
return args.includes(flag) || args.includes(short);
|
|
66
|
+
return args.includes(flag) || (short ? args.includes(short) : false);
|
|
58
67
|
}
|
|
59
68
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Load Config
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
function loadConfig() {
|
|
74
|
+
const defaults = {
|
|
75
|
+
autoChangelog: true,
|
|
76
|
+
defaultType: 'patch',
|
|
77
|
+
requireCleanWorkingDir: false,
|
|
78
|
+
autoPush: true,
|
|
79
|
+
defaultStageMode: 'tracked',
|
|
80
|
+
tagPrefix: 'v',
|
|
81
|
+
colors: true
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (fs.existsSync('.vnxtrc.json')) {
|
|
85
|
+
const userConfig = JSON.parse(fs.readFileSync('.vnxtrc.json', 'utf8'));
|
|
86
|
+
return {...defaults, ...userConfig};
|
|
87
|
+
}
|
|
70
88
|
|
|
71
|
-
|
|
72
|
-
const userConfig = JSON.parse(fs.readFileSync('.vnxtrc.json', 'utf8'));
|
|
73
|
-
config = {...config, ...userConfig};
|
|
89
|
+
return defaults;
|
|
74
90
|
}
|
|
75
91
|
|
|
76
|
-
|
|
77
|
-
if (args.includes('--version') || args.includes('-V')) {
|
|
78
|
-
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
79
|
-
console.log(`vnxt v${pkg.version}`);
|
|
80
|
-
process.exit(0);
|
|
81
|
-
}
|
|
92
|
+
const config = loadConfig();
|
|
82
93
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
94
|
+
// =============================================================================
|
|
95
|
+
// Handle Quick Flags (exit immediately)
|
|
96
|
+
// =============================================================================
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
function handleQuickFlags() {
|
|
99
|
+
// -vv / --vnxt-version: show vnxt's own installed version
|
|
100
|
+
if (args.includes('--vnxt-version') || args.includes('-vv')) {
|
|
101
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
|
102
|
+
console.log(`vnxt v${pkg.version}`);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// -gv / --get-version: show the current project's version
|
|
107
|
+
if (args.includes('--get-version') || args.includes('-gv')) {
|
|
108
|
+
if (!fs.existsSync('./package.json')) {
|
|
109
|
+
console.error('❌ No package.json found in current directory.');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
const pkg = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
113
|
+
console.log(`${pkg.name} v${pkg.version}`);
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// -h / --help
|
|
118
|
+
if (hasFlag('--help', '-h')) {
|
|
119
|
+
printHelp();
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
92
122
|
}
|
|
93
123
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const addAllFlag = getFlag('--all', '-a');
|
|
104
|
-
let addMode = null;
|
|
105
|
-
let promptForStaging = false;
|
|
106
|
-
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// Parse Args
|
|
126
|
+
// =============================================================================
|
|
127
|
+
|
|
128
|
+
function parseArgs() {
|
|
129
|
+
if (args.includes('--quiet') || args.includes('-q')) {
|
|
130
|
+
quietMode = true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const addAllFlag = getFlag('--all', '-a');
|
|
134
|
+
let addMode = null;
|
|
135
|
+
let promptForStaging = false;
|
|
136
|
+
|
|
137
|
+
if (addAllFlag) {
|
|
138
|
+
if (typeof addAllFlag === 'string') {
|
|
139
|
+
const mode = addAllFlag.toLowerCase();
|
|
140
|
+
const modeMap = { a: 'all', i: 'interactive', p: 'patch' };
|
|
141
|
+
const valid = ['tracked', 'all', 'interactive', 'patch', ...Object.keys(modeMap)];
|
|
142
|
+
if (!valid.includes(mode)) {
|
|
143
|
+
logError(`Error: Invalid add mode '${addAllFlag}'. Use: tracked, all, interactive (i), or patch (p)`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
addMode = modeMap[mode] || mode;
|
|
114
147
|
} else {
|
|
115
|
-
|
|
116
|
-
process.exit(1);
|
|
148
|
+
promptForStaging = true;
|
|
117
149
|
}
|
|
118
|
-
} else {
|
|
119
|
-
promptForStaging = true;
|
|
120
150
|
}
|
|
151
|
+
|
|
152
|
+
const noPush = hasFlag('--no-push', '-dnp');
|
|
153
|
+
const publishToNpm = hasFlag('--publish');
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
message: getFlag('--message', '-m'),
|
|
157
|
+
type: getFlag('--type', '-t') || config.defaultType,
|
|
158
|
+
customVersion: getFlag('--set-version', '-sv'),
|
|
159
|
+
dryRun: hasFlag('--dry-run', '-d'),
|
|
160
|
+
noPush,
|
|
161
|
+
publishToNpm,
|
|
162
|
+
push: noPush ? false : (hasFlag('--push', '-p') || publishToNpm || config.autoPush),
|
|
163
|
+
generateChangelog: hasFlag('--changelog', '-c') || config.autoChangelog,
|
|
164
|
+
generateReleaseNotes: hasFlag('--release', '-r'),
|
|
165
|
+
addMode,
|
|
166
|
+
promptForStaging
|
|
167
|
+
};
|
|
121
168
|
}
|
|
122
|
-
let generateReleaseNotes = hasFlag('--release', '-r');
|
|
123
169
|
|
|
124
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
input: process.stdin, output: process.stdout
|
|
128
|
-
});
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// Interactive Prompt Helper
|
|
172
|
+
// =============================================================================
|
|
129
173
|
|
|
174
|
+
async function prompt(question) {
|
|
175
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
130
176
|
return new Promise(resolve => {
|
|
131
|
-
rl.question(question, answer => {
|
|
132
|
-
rl.close();
|
|
133
|
-
resolve(answer);
|
|
134
|
-
});
|
|
177
|
+
rl.question(question, answer => { rl.close(); resolve(answer); });
|
|
135
178
|
});
|
|
136
179
|
}
|
|
137
180
|
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Interactive mode if no message provided
|
|
142
|
-
if (!message) {
|
|
143
|
-
log('🤔 Interactive mode\n', 'cyan');
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// Interactive Mode
|
|
183
|
+
// =============================================================================
|
|
144
184
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
logError('Error: Commit message is required');
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
185
|
+
async function runInteractiveMode(opts) {
|
|
186
|
+
log('🤔 Interactive mode\n', 'cyan');
|
|
150
187
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
188
|
+
opts.message = await prompt('Commit message: ');
|
|
189
|
+
if (!opts.message) {
|
|
190
|
+
logError('Error: Commit message is required');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
155
193
|
|
|
156
|
-
|
|
157
|
-
|
|
194
|
+
const typeInput = await prompt('Version type (patch/minor/major) [auto-detect]: ');
|
|
195
|
+
if (typeInput && ['patch', 'minor', 'major'].includes(typeInput)) {
|
|
196
|
+
opts.type = typeInput;
|
|
197
|
+
}
|
|
158
198
|
|
|
159
|
-
|
|
160
|
-
|
|
199
|
+
const changelogInput = await prompt('Update CHANGELOG.md? (y/n) [n]: ');
|
|
200
|
+
opts.generateChangelog = changelogInput.toLowerCase() === 'y' || changelogInput.toLowerCase() === 'yes' || opts.generateChangelog;
|
|
161
201
|
|
|
162
|
-
|
|
163
|
-
|
|
202
|
+
const publishInput = await prompt('Publish to npm? (y/n) [n]: ');
|
|
203
|
+
if (publishInput.toLowerCase() === 'y' || publishInput.toLowerCase() === 'yes') {
|
|
204
|
+
opts.publishToNpm = true;
|
|
205
|
+
opts.generateReleaseNotes = true;
|
|
206
|
+
}
|
|
164
207
|
|
|
165
|
-
|
|
166
|
-
|
|
208
|
+
const pushInput = await prompt('Push to remote? (y/n) [n]: ');
|
|
209
|
+
opts.push = pushInput.toLowerCase() === 'y' || pushInput.toLowerCase() === 'yes' || opts.push;
|
|
167
210
|
|
|
168
|
-
|
|
169
|
-
|
|
211
|
+
const dryRunInput = await prompt('Dry run (preview only)? (y/n) [n]: ');
|
|
212
|
+
opts.dryRun = dryRunInput.toLowerCase() === 'y' || dryRunInput.toLowerCase() === 'yes';
|
|
170
213
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (message.startsWith('major:') || message.startsWith('MAJOR:')) {
|
|
174
|
-
type = 'major';
|
|
175
|
-
log('📝 Auto-detected: major version bump', 'cyan');
|
|
176
|
-
} else if (message.startsWith('minor:') || message.startsWith('MINOR:')) {
|
|
177
|
-
type = 'minor';
|
|
178
|
-
log('📝 Auto-detected: minor version bump', 'cyan');
|
|
179
|
-
} else if (message.startsWith('patch:') || message.startsWith('PATCH:')) {
|
|
180
|
-
type = 'patch';
|
|
181
|
-
log('📝 Auto-detected: patch version bump', 'cyan');
|
|
182
|
-
} else if (message.startsWith('feat:') || message.startsWith('feature:')) {
|
|
183
|
-
type = 'minor';
|
|
184
|
-
log('📝 Auto-detected: minor version bump (feature)', 'cyan');
|
|
185
|
-
} else if (message.startsWith('fix:')) {
|
|
186
|
-
type = 'patch';
|
|
187
|
-
log('📝 Auto-detected: patch version bump (fix)', 'cyan');
|
|
188
|
-
} else if (message.includes('BREAKING') || message.startsWith('breaking:')) {
|
|
189
|
-
type = 'major';
|
|
190
|
-
log('📝 Auto-detected: major version bump (breaking change)', 'cyan');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
214
|
+
log('');
|
|
215
|
+
}
|
|
193
216
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
// =============================================================================
|
|
218
|
+
// Detect Version Type
|
|
219
|
+
// =============================================================================
|
|
220
|
+
|
|
221
|
+
function detectVersionType(message, currentType) {
|
|
222
|
+
const rules = [
|
|
223
|
+
{ prefixes: ['major:', 'MAJOR:'], type: 'major', label: 'major version bump' },
|
|
224
|
+
{ prefixes: ['minor:', 'MINOR:'], type: 'minor', label: 'minor version bump' },
|
|
225
|
+
{ prefixes: ['patch:', 'PATCH:'], type: 'patch', label: 'patch version bump' },
|
|
226
|
+
{ prefixes: ['feat:', 'feature:'], type: 'minor', label: 'minor version bump (feature)' },
|
|
227
|
+
{ prefixes: ['fix:'], type: 'patch', label: 'patch version bump (fix)' },
|
|
228
|
+
{ prefixes: ['breaking:'], type: 'major', label: 'major version bump (breaking change)' },
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
for (const rule of rules) {
|
|
232
|
+
if (rule.prefixes.some(p => message.startsWith(p))) {
|
|
233
|
+
log(`📝 Auto-detected: ${rule.label}`, 'cyan');
|
|
234
|
+
return rule.type;
|
|
198
235
|
}
|
|
236
|
+
}
|
|
199
237
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (releaseNotesContext) log('');
|
|
209
|
-
}
|
|
210
|
-
} else if (generateReleaseNotes && !quietMode) {
|
|
211
|
-
// -r flag was passed explicitly - still offer context prompt
|
|
212
|
-
releaseNotesContext = await prompt('\n📋 Add context to release notes (press Enter to skip): ');
|
|
213
|
-
if (releaseNotesContext) log('');
|
|
214
|
-
}
|
|
238
|
+
// Special case: BREAKING anywhere in message
|
|
239
|
+
if (message.includes('BREAKING')) {
|
|
240
|
+
log('📝 Auto-detected: major version bump (breaking change)', 'cyan');
|
|
241
|
+
return 'major';
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return currentType;
|
|
245
|
+
}
|
|
215
246
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
logError('Invalid choice. Exiting.');
|
|
247
|
-
process.exit(1);
|
|
248
|
-
}
|
|
249
|
-
log('');
|
|
247
|
+
// =============================================================================
|
|
248
|
+
// Pre-flight Checks
|
|
249
|
+
// =============================================================================
|
|
250
|
+
|
|
251
|
+
async function runPreflightChecks(opts) {
|
|
252
|
+
log('\n🔍 Running pre-flight checks...\n', 'cyan');
|
|
253
|
+
|
|
254
|
+
// Staging prompt if requested
|
|
255
|
+
if ((config.requireCleanWorkingDir && !opts.addMode) || opts.promptForStaging) {
|
|
256
|
+
const status = execSync('git status --porcelain --untracked-files=no').toString().trim();
|
|
257
|
+
if (status || opts.promptForStaging) {
|
|
258
|
+
if (status) log('⚠️ You have uncommitted changes.\n', 'yellow');
|
|
259
|
+
|
|
260
|
+
log('📁 How would you like to stage files?\n');
|
|
261
|
+
log(' 1. Tracked files only (git add -u)');
|
|
262
|
+
log(' 2. All changes (git add -A)');
|
|
263
|
+
log(' 3. Interactive selection (git add -i)');
|
|
264
|
+
log(' 4. Patch mode (git add -p)');
|
|
265
|
+
log(' 5. Skip staging (continue without staging)\n');
|
|
266
|
+
|
|
267
|
+
const choice = await prompt('Select [1-5]: ');
|
|
268
|
+
const choiceMap = { '1': 'tracked', '2': 'all', '3': 'interactive', '4': 'patch' };
|
|
269
|
+
|
|
270
|
+
if (choiceMap[choice]) {
|
|
271
|
+
opts.addMode = choiceMap[choice];
|
|
272
|
+
} else if (choice === '5') {
|
|
273
|
+
log('⚠️ Skipping file staging. Ensure files are staged manually.', 'yellow');
|
|
274
|
+
} else {
|
|
275
|
+
logError('Invalid choice. Exiting.');
|
|
276
|
+
process.exit(1);
|
|
250
277
|
}
|
|
278
|
+
log('');
|
|
251
279
|
}
|
|
280
|
+
}
|
|
252
281
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
282
|
+
// Branch check
|
|
283
|
+
const branch = execSync('git branch --show-current').toString().trim();
|
|
284
|
+
if (branch !== 'main' && branch !== 'master') {
|
|
285
|
+
log(`⚠️ Warning: You're on branch '${branch}', not main/master`, 'yellow');
|
|
286
|
+
}
|
|
258
287
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
log('⚠️ Warning: No remote repository configured', 'yellow');
|
|
288
|
+
// Remote check
|
|
289
|
+
try {
|
|
290
|
+
execSync('git remote get-url origin', {stdio: 'pipe'});
|
|
291
|
+
} catch {
|
|
292
|
+
if (opts.push) {
|
|
293
|
+
logError('❌ Error: No remote repository configured, cannot push');
|
|
294
|
+
process.exit(1);
|
|
268
295
|
}
|
|
296
|
+
log('⚠️ Warning: No remote repository configured', 'yellow');
|
|
297
|
+
}
|
|
269
298
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (dryRun) {
|
|
274
|
-
log('🔬 DRY RUN MODE - No changes will be made\n', 'yellow');
|
|
275
|
-
log('Would perform the following actions:');
|
|
276
|
-
|
|
277
|
-
if (addMode) {
|
|
278
|
-
const modeDescriptions = {
|
|
279
|
-
'tracked': 'Stage tracked files only (git add -u)',
|
|
280
|
-
'all': 'Stage all changes (git add -A)',
|
|
281
|
-
'interactive': 'Interactive selection (git add -i)',
|
|
282
|
-
'patch': 'Patch mode (git add -p)'
|
|
283
|
-
};
|
|
284
|
-
log(` 1. ${modeDescriptions[addMode]}`);
|
|
285
|
-
}
|
|
299
|
+
log('✅ Pre-flight checks passed\n', 'green');
|
|
300
|
+
return branch;
|
|
301
|
+
}
|
|
286
302
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
303
|
+
// =============================================================================
|
|
304
|
+
// Dry Run
|
|
305
|
+
// =============================================================================
|
|
306
|
+
|
|
307
|
+
function runDryRun(opts) {
|
|
308
|
+
log('🔬 DRY RUN MODE - No changes will be made\n', 'yellow');
|
|
309
|
+
log('Would perform the following actions:');
|
|
310
|
+
|
|
311
|
+
if (opts.addMode) {
|
|
312
|
+
const modeDescriptions = {
|
|
313
|
+
tracked: 'Stage tracked files only (git add -u)',
|
|
314
|
+
all: 'Stage all changes (git add -A)',
|
|
315
|
+
interactive: 'Interactive selection (git add -i)',
|
|
316
|
+
patch: 'Patch mode (git add -p)'
|
|
317
|
+
};
|
|
318
|
+
log(` 1. ${modeDescriptions[opts.addMode]}`);
|
|
319
|
+
}
|
|
290
320
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
321
|
+
log(` 2. Bump ${opts.type} version`);
|
|
322
|
+
log(` 3. Commit with message: "${opts.message}"`);
|
|
323
|
+
log(' 4. Create git tag with annotation');
|
|
324
|
+
log(opts.generateChangelog ? ' 5. Update CHANGELOG.md' : ' 5. (Skipping changelog - use --changelog to enable)');
|
|
325
|
+
log(opts.generateReleaseNotes ? ' 6. Generate release notes file' : ' 6. (Skipping release notes - use --release to enable)');
|
|
326
|
+
log(opts.push ? ' 7. Push to remote with tags' : ' 7. (Skipping push - use --push to enable)');
|
|
296
327
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
log(' 6. (Skipping release notes - use --release to enable)');
|
|
301
|
-
}
|
|
328
|
+
log('\n✓ Dry run complete. Use without -d to apply changes.', 'green');
|
|
329
|
+
process.exit(0);
|
|
330
|
+
}
|
|
302
331
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
332
|
+
// =============================================================================
|
|
333
|
+
// Stage Files
|
|
334
|
+
// =============================================================================
|
|
335
|
+
|
|
336
|
+
function stageFiles(addMode) {
|
|
337
|
+
log('📦 Staging files...', 'cyan');
|
|
338
|
+
const modeCommands = {
|
|
339
|
+
tracked: 'git add -u',
|
|
340
|
+
all: 'git add -A',
|
|
341
|
+
interactive: 'git add -i',
|
|
342
|
+
patch: 'git add -p'
|
|
343
|
+
};
|
|
344
|
+
execSync(modeCommands[addMode], {stdio: 'inherit'});
|
|
345
|
+
}
|
|
308
346
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
347
|
+
// =============================================================================
|
|
348
|
+
// Bump Version
|
|
349
|
+
// =============================================================================
|
|
312
350
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
log('📦 Staging files...', 'cyan');
|
|
316
|
-
|
|
317
|
-
if (addMode === 'tracked') {
|
|
318
|
-
execSync('git add -u', {stdio: 'inherit'});
|
|
319
|
-
} else if (addMode === 'all') {
|
|
320
|
-
execSync('git add -A', {stdio: 'inherit'});
|
|
321
|
-
} else if (addMode === 'interactive') {
|
|
322
|
-
execSync('git add -i', {stdio: 'inherit'});
|
|
323
|
-
} else if (addMode === 'patch') {
|
|
324
|
-
execSync('git add -p', {stdio: 'inherit'});
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
// BUMP VERSION
|
|
328
|
-
log(`\n🔼 Bumping version...`, 'cyan');
|
|
351
|
+
function bumpVersion(opts) {
|
|
352
|
+
log('\n🔼 Bumping version...', 'cyan');
|
|
329
353
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
const oldVersion = packageJson.version;
|
|
354
|
+
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
355
|
+
const oldVersion = packageJson.version;
|
|
333
356
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
execSync(`npm version ${customVersion} --git-tag-version=false`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
337
|
-
} else {
|
|
338
|
-
execSync(`npm version ${type} --git-tag-version=false`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
339
|
-
}
|
|
357
|
+
const versionArg = opts.customVersion || opts.type;
|
|
358
|
+
execSync(`npm version ${versionArg} --git-tag-version=false`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
340
359
|
|
|
341
|
-
|
|
342
|
-
const newVersion = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
|
|
360
|
+
const newVersion = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
|
|
343
361
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
362
|
+
// Stage package files and commit
|
|
363
|
+
execSync('git add package.json', {stdio: 'pipe'});
|
|
364
|
+
if (fs.existsSync('package-lock.json')) {
|
|
365
|
+
execSync('git add package-lock.json', {stdio: 'pipe'});
|
|
366
|
+
}
|
|
367
|
+
execFileSync('git', ['commit', '-m', opts.message], {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
349
368
|
|
|
350
|
-
|
|
351
|
-
|
|
369
|
+
// Create annotated tag
|
|
370
|
+
log('🏷️ Adding tag annotation...', 'cyan');
|
|
371
|
+
const tagMessage = `Version ${newVersion}\n\n${opts.message}`;
|
|
372
|
+
execFileSync('git', ['tag', '-a', `${config.tagPrefix}${newVersion}`, '-m', tagMessage], {stdio: 'pipe'});
|
|
352
373
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const tagMessage = `Version ${newVersion}\n\n${message}`;
|
|
356
|
-
execSync(`git tag -a ${config.tagPrefix}${newVersion} -m "${tagMessage}"`, {stdio: 'pipe'});
|
|
374
|
+
return { oldVersion, newVersion, packageJson };
|
|
375
|
+
}
|
|
357
376
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const date = new Date().toISOString().split('T')[0];
|
|
362
|
-
const changelogEntry = `\n## [${newVersion}] - ${date}\n- ${message}\n`;
|
|
377
|
+
// =============================================================================
|
|
378
|
+
// Generate Changelog
|
|
379
|
+
// =============================================================================
|
|
363
380
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
|
|
367
|
-
}
|
|
381
|
+
function generateChangelog(newVersion, message) {
|
|
382
|
+
log('📄 Updating CHANGELOG.md...', 'cyan');
|
|
368
383
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const titleIndex = lines.findIndex(line => line.startsWith('# Changelog'));
|
|
372
|
-
lines.splice(titleIndex + 1, 0, changelogEntry);
|
|
384
|
+
const date = new Date().toISOString().split('T')[0];
|
|
385
|
+
const entry = `\n## [${newVersion}] - ${date}\n- ${message}\n`;
|
|
373
386
|
|
|
374
|
-
|
|
387
|
+
let changelog = '# Changelog\n';
|
|
388
|
+
if (fs.existsSync('CHANGELOG.md')) {
|
|
389
|
+
changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
|
|
390
|
+
}
|
|
375
391
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
392
|
+
const lines = changelog.split('\n');
|
|
393
|
+
const titleIndex = lines.findIndex(line => line.startsWith('# Changelog'));
|
|
394
|
+
lines.splice(titleIndex + 1, 0, entry);
|
|
395
|
+
fs.writeFileSync('CHANGELOG.md', lines.join('\n'));
|
|
380
396
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
397
|
+
execSync('git add CHANGELOG.md', {stdio: 'pipe'});
|
|
398
|
+
execSync('git commit --amend --no-edit', {stdio: 'pipe'});
|
|
399
|
+
}
|
|
384
400
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
401
|
+
// =============================================================================
|
|
402
|
+
// Generate Release Notes
|
|
403
|
+
// =============================================================================
|
|
388
404
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
405
|
+
function generateReleaseNotes(newVersion, message, context, packageJson, isPublish = false) {
|
|
406
|
+
log('📋 Generating release notes...', 'cyan');
|
|
407
|
+
|
|
408
|
+
const date = new Date();
|
|
409
|
+
const timestamp = date.toISOString().replace('T', ' ').split('.')[0] + ' UTC';
|
|
410
|
+
const dateShort = date.toISOString().split('T')[0];
|
|
411
|
+
|
|
412
|
+
let author = '';
|
|
413
|
+
try { author = execSync('git config user.name', {stdio: 'pipe'}).toString().trim(); } catch {}
|
|
414
|
+
|
|
415
|
+
// If publishing, gather all commits since the last publish/v* tag
|
|
416
|
+
let changes = message;
|
|
417
|
+
if (isPublish) {
|
|
418
|
+
try {
|
|
419
|
+
const lastPublishTag = execSync(
|
|
420
|
+
'git tag --list "publish/v*" --sort=-version:refname',
|
|
421
|
+
{stdio: 'pipe'}
|
|
422
|
+
).toString().trim().split('\n').filter(Boolean)[0];
|
|
423
|
+
|
|
424
|
+
if (lastPublishTag) {
|
|
425
|
+
const commits = execSync(
|
|
426
|
+
`git log ${lastPublishTag}..HEAD --pretty=format:"- %s"`,
|
|
427
|
+
{stdio: 'pipe'}
|
|
428
|
+
).toString().trim();
|
|
429
|
+
if (commits) changes = commits;
|
|
394
430
|
}
|
|
431
|
+
} catch {
|
|
432
|
+
// Fall back to current message if git log fails
|
|
433
|
+
}
|
|
434
|
+
}
|
|
395
435
|
|
|
396
|
-
|
|
436
|
+
const notes = `# Release ${config.tagPrefix}${newVersion}
|
|
397
437
|
|
|
398
438
|
Released: ${dateShort} at ${timestamp.split(' ')[1]}${author ? `\nAuthor: ${author}` : ''}
|
|
399
439
|
|
|
400
440
|
## Changes
|
|
401
|
-
${
|
|
441
|
+
${changes}${context ? `\n\n## Release Notes\n${context}` : ''}
|
|
402
442
|
|
|
403
443
|
## Installation
|
|
404
444
|
\`\`\`bash
|
|
@@ -406,81 +446,79 @@ npm install ${packageJson.name}@${newVersion}
|
|
|
406
446
|
\`\`\`
|
|
407
447
|
|
|
408
448
|
## Full Changelog
|
|
409
|
-
See [CHANGELOG.md](
|
|
449
|
+
See [CHANGELOG.md](../CHANGELOG.md) for complete version history.
|
|
410
450
|
`;
|
|
411
451
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
fs.mkdirSync(releaseNotesDir);
|
|
415
|
-
}
|
|
452
|
+
const dir = 'release-notes';
|
|
453
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
|
416
454
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
// Stage and amend commit to include release notes
|
|
422
|
-
execSync(`git add ${filename}`, {stdio: 'pipe'});
|
|
423
|
-
execSync(`git commit --amend --no-edit`, {stdio: 'pipe'});
|
|
424
|
-
}
|
|
455
|
+
const filename = `${dir}/${config.tagPrefix}${newVersion}.md`;
|
|
456
|
+
fs.writeFileSync(filename, notes);
|
|
457
|
+
log(` Created: ${filename}`);
|
|
425
458
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
execSync('git push --follow-tags', {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
430
|
-
|
|
431
|
-
// If --publish flag, also push a publish/v* tag to trigger npm workflow
|
|
432
|
-
if (publishToNpm) {
|
|
433
|
-
log('📦 Pushing publish tag to trigger npm release...', 'cyan');
|
|
434
|
-
const publishTag = `publish/${config.tagPrefix}${newVersion}`;
|
|
435
|
-
execSync(`git tag ${publishTag}`, {stdio: 'pipe'});
|
|
436
|
-
execSync(`git push origin ${publishTag}`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// STATS/SUMMARY
|
|
441
|
-
log('\n📊 Summary:', 'cyan');
|
|
442
|
-
log('━'.repeat(50), 'gray');
|
|
459
|
+
execSync(`git add ${filename}`, {stdio: 'pipe'});
|
|
460
|
+
execSync('git commit --amend --no-edit', {stdio: 'pipe'});
|
|
461
|
+
}
|
|
443
462
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
log(`🌿 Branch: ${branch}`);
|
|
463
|
+
// =============================================================================
|
|
464
|
+
// Push to Remote
|
|
465
|
+
// =============================================================================
|
|
448
466
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
467
|
+
function pushToRemote(opts, newVersion) {
|
|
468
|
+
log('🚀 Pushing to remote...', 'cyan');
|
|
469
|
+
execSync('git push --follow-tags', {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
452
470
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
471
|
+
if (opts.publishToNpm) {
|
|
472
|
+
log('📦 Pushing publish tag to trigger npm release...', 'cyan');
|
|
473
|
+
const publishTag = `publish/${config.tagPrefix}${newVersion}`;
|
|
474
|
+
execSync(`git tag ${publishTag}`, {stdio: 'pipe'});
|
|
475
|
+
execSync(`git push origin ${publishTag}`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
456
478
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
479
|
+
// =============================================================================
|
|
480
|
+
// Print Summary
|
|
481
|
+
// =============================================================================
|
|
482
|
+
|
|
483
|
+
function printSummary(opts, oldVersion, newVersion, branch) {
|
|
484
|
+
log('\n📊 Summary:', 'cyan');
|
|
485
|
+
log('━'.repeat(50), 'gray');
|
|
486
|
+
log(`\n📦 Version: ${oldVersion} → ${newVersion}`, 'green');
|
|
487
|
+
log(`💬 Message: ${opts.message}`);
|
|
488
|
+
log(`🏷️ Tag: ${config.tagPrefix}${newVersion}`);
|
|
489
|
+
log(`🌿 Branch: ${branch}`);
|
|
490
|
+
|
|
491
|
+
if (opts.generateChangelog) log('📄 Changelog: Updated');
|
|
492
|
+
if (opts.generateReleaseNotes) log('📋 Release notes: Generated');
|
|
493
|
+
|
|
494
|
+
if (opts.push) {
|
|
495
|
+
log('🚀 Remote: Pushed with tags', 'green');
|
|
496
|
+
if (opts.publishToNpm) {
|
|
497
|
+
log(`📦 npm: Publishing triggered (publish/${config.tagPrefix}${newVersion})`, 'green');
|
|
464
498
|
}
|
|
499
|
+
} else {
|
|
500
|
+
log('📍 Remote: Not pushed (use --push to enable)', 'gray');
|
|
501
|
+
}
|
|
465
502
|
|
|
466
|
-
|
|
467
|
-
|
|
503
|
+
if (!quietMode) {
|
|
504
|
+
try {
|
|
468
505
|
log('\n📝 Files changed:');
|
|
469
506
|
const diff = execSync('git diff HEAD~1 --stat').toString();
|
|
470
507
|
console.log(diff);
|
|
508
|
+
} catch {
|
|
509
|
+
// No previous commit to diff against
|
|
471
510
|
}
|
|
472
|
-
|
|
473
|
-
log('━'.repeat(50), 'gray');
|
|
474
|
-
log('\n✅ Version bump complete!\n', 'green');
|
|
475
|
-
|
|
476
|
-
} catch (error) {
|
|
477
|
-
logError('\n❌ Error: ' + error.message);
|
|
478
|
-
process.exit(1);
|
|
479
511
|
}
|
|
512
|
+
|
|
513
|
+
log('━'.repeat(50), 'gray');
|
|
514
|
+
log('\n✅ Version bump complete!\n', 'green');
|
|
480
515
|
}
|
|
481
516
|
|
|
482
|
-
//
|
|
483
|
-
|
|
517
|
+
// =============================================================================
|
|
518
|
+
// Help
|
|
519
|
+
// =============================================================================
|
|
520
|
+
|
|
521
|
+
function printHelp() {
|
|
484
522
|
console.log(`
|
|
485
523
|
vnxt (vx) - Version Bump CLI Tool
|
|
486
524
|
|
|
@@ -491,17 +529,18 @@ Usage:
|
|
|
491
529
|
Options:
|
|
492
530
|
-m, --message <msg> Commit message (required, or use interactive mode)
|
|
493
531
|
-t, --type <type> Version type: patch, minor, major (auto-detected from message)
|
|
494
|
-
-
|
|
495
|
-
-
|
|
532
|
+
-sv, --set-version <v> Set a specific version (e.g., 2.0.0-beta.1)
|
|
533
|
+
-gv, --get-version Show the current project's version
|
|
534
|
+
-vv, --vnxt-version Show the installed vnxt version
|
|
496
535
|
-p, --push Push to remote with tags
|
|
497
536
|
-dnp, --no-push Prevent auto-push (overrides config)
|
|
498
|
-
--publish Push and trigger npm publish via GitHub Actions
|
|
537
|
+
--publish Push and trigger npm publish via GitHub Actions (implies --push)
|
|
499
538
|
-c, --changelog Update CHANGELOG.md
|
|
500
539
|
-d, --dry-run Show what would happen without making changes
|
|
501
540
|
-a, --all [mode] Stage files before versioning
|
|
502
541
|
Modes: tracked (default), all, interactive (i), patch (p)
|
|
503
542
|
If no mode specified, prompts interactively
|
|
504
|
-
-r, --release Generate release notes file
|
|
543
|
+
-r, --release Generate release notes file (saved to release-notes/)
|
|
505
544
|
-q, --quiet Minimal output (errors only)
|
|
506
545
|
-h, --help Show this help message
|
|
507
546
|
|
|
@@ -510,7 +549,7 @@ Auto-detection:
|
|
|
510
549
|
- "minor:" → minor version
|
|
511
550
|
- "patch:" → patch version
|
|
512
551
|
- "feat:" or "feature:" → minor version
|
|
513
|
-
- "fix:" → patch version
|
|
552
|
+
- "fix:" → patch version
|
|
514
553
|
- "BREAKING" or "breaking:" → major version
|
|
515
554
|
|
|
516
555
|
Configuration:
|
|
@@ -526,11 +565,12 @@ Configuration:
|
|
|
526
565
|
}
|
|
527
566
|
|
|
528
567
|
Examples:
|
|
529
|
-
vx -
|
|
568
|
+
vx -vv # Show vnxt version
|
|
569
|
+
vx -gv # Show current project version
|
|
530
570
|
vx -m "fix: resolve bug" # Auto-pushes with autoPush: true
|
|
531
571
|
vx -m "feat: add new feature" # Auto-pushes with autoPush: true
|
|
532
572
|
vx -m "fix: bug" -dnp # Don't push (override)
|
|
533
|
-
vx -
|
|
573
|
+
vx -sv 2.0.0-beta.1 -m "beta release"
|
|
534
574
|
vx -m "test" -d
|
|
535
575
|
vx -m "fix: bug" -a # Interactive prompt for staging
|
|
536
576
|
vx -m "fix: bug" -a tracked # Stage tracked files only
|
|
@@ -539,10 +579,75 @@ Examples:
|
|
|
539
579
|
vx -m "fix: bug" -a p # Patch mode
|
|
540
580
|
vx -m "fix: bug" -q # Quiet mode (minimal output)
|
|
541
581
|
vx -m "feat: new feature" --publish # Bump, push and trigger npm publish
|
|
582
|
+
vx -m "fix: bug" -r # Generate release notes in release-notes/
|
|
542
583
|
vx # Interactive mode
|
|
543
584
|
`);
|
|
544
|
-
process.exit(0);
|
|
545
585
|
}
|
|
546
586
|
|
|
547
|
-
//
|
|
587
|
+
// =============================================================================
|
|
588
|
+
// Main
|
|
589
|
+
// =============================================================================
|
|
590
|
+
|
|
591
|
+
async function main() {
|
|
592
|
+
try {
|
|
593
|
+
handleQuickFlags();
|
|
594
|
+
|
|
595
|
+
// Git repo check
|
|
596
|
+
if (!fs.existsSync('.git')) {
|
|
597
|
+
logError('❌ Not a git repository. Run `git init` first.');
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const opts = parseArgs();
|
|
602
|
+
|
|
603
|
+
// Interactive mode if no message provided
|
|
604
|
+
if (!opts.message) {
|
|
605
|
+
await runInteractiveMode(opts);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Auto-detect version type from commit message
|
|
609
|
+
if (!opts.customVersion && !getFlag('--type', '-t')) {
|
|
610
|
+
opts.type = detectVersionType(opts.message, opts.type);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Validate version type
|
|
614
|
+
if (!opts.customVersion && !['patch', 'minor', 'major'].includes(opts.type)) {
|
|
615
|
+
logError('Error: Version type must be patch, minor, or major');
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Release notes context prompt
|
|
620
|
+
let releaseNotesContext = '';
|
|
621
|
+
if (!opts.generateReleaseNotes && opts.publishToNpm) {
|
|
622
|
+
opts.generateReleaseNotes = true;
|
|
623
|
+
if (!quietMode) {
|
|
624
|
+
log('\n📋 Release notes required for --publish.', 'yellow');
|
|
625
|
+
releaseNotesContext = await prompt(' Add context (press Enter to skip): ');
|
|
626
|
+
if (releaseNotesContext) log('');
|
|
627
|
+
}
|
|
628
|
+
} else if (opts.generateReleaseNotes && !quietMode) {
|
|
629
|
+
releaseNotesContext = await prompt('\n📋 Add context to release notes (press Enter to skip): ');
|
|
630
|
+
if (releaseNotesContext) log('');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const branch = await runPreflightChecks(opts);
|
|
634
|
+
|
|
635
|
+
if (opts.dryRun) runDryRun(opts);
|
|
636
|
+
|
|
637
|
+
if (opts.addMode) stageFiles(opts.addMode);
|
|
638
|
+
|
|
639
|
+
const { oldVersion, newVersion, packageJson } = bumpVersion(opts);
|
|
640
|
+
|
|
641
|
+
if (opts.generateChangelog) generateChangelog(newVersion, opts.message);
|
|
642
|
+
if (opts.generateReleaseNotes) generateReleaseNotes(newVersion, opts.message, releaseNotesContext, packageJson, opts.publishToNpm);
|
|
643
|
+
if (opts.push) pushToRemote(opts, newVersion);
|
|
644
|
+
|
|
645
|
+
printSummary(opts, oldVersion, newVersion, branch);
|
|
646
|
+
|
|
647
|
+
} catch (error) {
|
|
648
|
+
logError('\n❌ Error: ' + error.message);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
548
653
|
main();
|