vnxt 1.4.5

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/.vnxtrc.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "autoChangelog": true,
3
+ "defaultType": "patch",
4
+ "requireCleanWorkingDir": false,
5
+ "autoPush": true,
6
+ "defaultStageMode": "tracked",
7
+ "tagPrefix": "v"
8
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nate Orrow
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,379 @@
1
+ <p>
2
+ <img src="./docs/logos/vnxt_light_logo.png" alt="vnxt logo" width="200">
3
+ </p>
4
+
5
+ # vnxt (vx)
6
+
7
+ A lightweight CLI tool for automated version bumping with changelog generation and git integration.
8
+
9
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Features
10
+
11
+ - šŸš€ Automatic semantic version detection from commit messages
12
+ - šŸ“ Automatic CHANGELOG.md generation
13
+ - šŸ·ļø Git tag annotation
14
+ - šŸ” Pre-flight checks for clean working directory
15
+ - šŸ”¬ Dry-run mode to preview changes
16
+ - šŸ“‹ Release notes generation
17
+ - āš™ļø Project-level configuration support
18
+ - šŸ’¬ Interactive mode when no arguments provided
19
+
20
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Installation
21
+
22
+ ### Global Installation
23
+
24
+ **Bash/PowerShell:**
25
+ ```bash
26
+ npm install -g vnxt
27
+ ```
28
+
29
+ After installation, you can use either `vnxt` or the shorter alias `vx`:
30
+ ```bash
31
+ vnxt --help
32
+ vx --help # Same thing, shorter!
33
+ ```
34
+
35
+ ### Local Installation (from source)
36
+
37
+ **Bash/macOS/Linux:**
38
+ ```bash
39
+ # Clone the repository
40
+ git clone https://github.com/n-orrow/vnxt.git
41
+ cd vnxt
42
+
43
+ # Install globally via npm link
44
+ chmod +x vnxt.js
45
+ npm link
46
+ ```
47
+
48
+ **PowerShell/Windows:**
49
+ ```powershell
50
+ # Clone the repository
51
+ git clone https://github.com/n-orrow/vnxt.git
52
+ cd vnxt
53
+
54
+ # Install globally via npm link
55
+ npm link
56
+ ```
57
+
58
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Usage
59
+
60
+ ### Basic Examples
61
+
62
+ **Bash/PowerShell:**
63
+ ```bash
64
+ # Simple version bump (auto-detects patch from "fix:")
65
+ vnxt -m "fix: resolve RFID reader bug"
66
+ # or use the shorter alias:
67
+ vx -m "fix: resolve RFID reader bug"
68
+
69
+ # Feature addition (auto-detects minor from "feat:")
70
+ vx -m "feat: add heatmap visualization"
71
+
72
+ # Breaking change (auto-detects major from "BREAKING")
73
+ vx -m "BREAKING: redesign API structure"
74
+
75
+ # With changelog and push to remote
76
+ vx -m "feat: add new dashboard" -c -p
77
+
78
+ # Interactive mode (prompts for input)
79
+ vx
80
+ ```
81
+
82
+ ### Command Line Options
83
+
84
+ All options work with both `vnxt` and `vx`:
85
+
86
+ ```
87
+ -m, --message <msg> Commit message (required unless using interactive mode)
88
+ -t, --type <type> Version type: patch, minor, major (auto-detected from message)
89
+ -v, --version <ver> Set specific version (e.g., 2.0.0-beta.1)
90
+ -p, --push Push to remote with tags
91
+ -c, --changelog Update CHANGELOG.md
92
+ -d, --dry-run Show what would happen without making changes
93
+ -a, --all [mode] Stage files before versioning (prompts if no mode)
94
+ Modes: tracked, all, interactive (i), patch (p)
95
+ -r, --release Generate release notes file
96
+ -h, --help Show help message
97
+ ```
98
+
99
+ ### Automatic Version Detection
100
+
101
+ vnxt automatically detects the version bump type from your commit message:
102
+
103
+ - `major:` → **major** version bump
104
+ - `minor:` → **minor** version bump
105
+ - `patch:` → **patch** version bump
106
+ - `feat:` or `feature:` → **minor** version bump
107
+ - `fix:` → **patch** version bump
108
+ - `BREAKING:` or contains `BREAKING` → **major** version bump
109
+
110
+ You can override this with the `-t` flag.
111
+
112
+ ### Dry Run
113
+
114
+ Preview what will happen without making changes:
115
+
116
+ **Bash/PowerShell:**
117
+ ```bash
118
+ vx -m "feat: new feature" -d
119
+ ```
120
+
121
+ Output:
122
+ ```
123
+ šŸ”¬ DRY RUN MODE - No changes will be made
124
+
125
+ Would perform the following actions:
126
+ 1. Bump minor version
127
+ 2. Commit with message: "feat: new feature"
128
+ 3. Create git tag with annotation
129
+ 4. (Skipping push - use --push to enable)
130
+
131
+ āœ“ Dry run complete. Use without -d to apply changes.
132
+ ```
133
+
134
+ ### Custom Versions
135
+
136
+ Set a specific version number (useful for pre-releases):
137
+
138
+ **Bash/PowerShell:**
139
+ ```bash
140
+ vx -v 2.0.0-beta.1 -m "beta: initial release candidate"
141
+ vx -v 1.5.0-rc.2 -m "release candidate 2"
142
+ ```
143
+
144
+ ### Changelog Generation
145
+
146
+ Automatically update CHANGELOG.md with version history:
147
+
148
+ **Bash/PowerShell:**
149
+ ```bash
150
+ vx -m "feat: add user authentication" -c
151
+ ```
152
+
153
+ Creates/updates CHANGELOG.md:
154
+ ```markdown
155
+ # Changelog
156
+
157
+ ## [1.2.0] - 2024-02-10
158
+ - feat: add user authentication
159
+
160
+ ## [1.1.0] - 2024-02-09
161
+ - feat: add dashboard
162
+ ```
163
+
164
+ ### Release Notes
165
+
166
+ Generate a formatted release notes file:
167
+
168
+ **Bash/PowerShell:**
169
+ ```bash
170
+ vx -m "feat: major feature release" -r
171
+ ```
172
+
173
+ Creates `release-notes-v1.2.0.md`:
174
+ ```markdown
175
+ # Release v1.2.0
176
+
177
+ Released: 2024-02-10
178
+
179
+ ## Changes
180
+ feat: major feature release
181
+
182
+ ## Installation
183
+ npm install your-package@1.2.0
184
+ ```
185
+
186
+ ### File Staging Options
187
+
188
+ vnxt offers flexible file staging with the `-a` flag:
189
+
190
+ **Bash/PowerShell:**
191
+ ```bash
192
+ # Interactive prompt (asks which mode to use)
193
+ vx -m "chore: update" -a
194
+
195
+ # Specific modes
196
+ vx -m "fix: bug" -a tracked # Stage tracked files only (git add -u)
197
+ vx -m "feat: new" -a all # Stage all changes (git add -A)
198
+ vx -m "refactor: code" -a i # Interactive selection (git add -i)
199
+ vx -m "fix: typo" -a p # Patch mode (git add -p)
200
+ ```
201
+
202
+ **Staging Modes:**
203
+ - `tracked` - Only staged tracked files that have been modified/deleted (default)
204
+ - `all` - Stages all changes, respects `.gitignore` for new files
205
+ - `interactive` or `i` - Opens git's interactive staging mode
206
+ - `patch` or `p` - Opens patch mode for selective staging
207
+
208
+ **Note:** If you run `vx` without any files staged and without the `-a` flag, vnxt will prompt you interactively to choose a staging mode.
209
+
210
+ ### Complete Workflow Example
211
+
212
+ **Bash/PowerShell:**
213
+ ```bash
214
+ # Make changes to your code
215
+ # ...
216
+
217
+ # Dry run to preview
218
+ vx -m "feat: add new API endpoint" -d
219
+
220
+ # Execute with changelog, release notes, and push
221
+ vx -m "feat: add new API endpoint" -c -r -p
222
+ ```
223
+
224
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Configuration
225
+
226
+ Create a `.vnxtrc.json` file in your project root to set defaults:
227
+ ```json
228
+ {
229
+ "autoChangelog": true,
230
+ "defaultType": "patch",
231
+ "requireCleanWorkingDir": true,
232
+ "autoPush": false
233
+ }
234
+ ```
235
+
236
+ ### Configuration Options
237
+
238
+ | Option | Type | Default | Description |
239
+ |--------|------|---------|-------------|
240
+ | `autoChangelog` | boolean | `false` | Automatically update CHANGELOG.md on every bump |
241
+ | `defaultType` | string | `"patch"` | Default version bump type if not auto-detected |
242
+ | `requireCleanWorkingDir` | boolean | `true` | Require clean git working directory before bumping |
243
+ | `autoPush` | boolean | `false` | Automatically push to remote after bumping |
244
+
245
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Pre-flight Checks
246
+
247
+ vnxt performs several checks before making changes:
248
+
249
+ - āœ… Verifies no uncommitted changes (unless using `-a`)
250
+ - āœ… Warns if not on main/master branch
251
+ - āœ… Checks for remote repository (if pushing)
252
+
253
+ Example output:
254
+ ```
255
+ šŸ” Running pre-flight checks...
256
+
257
+ āš ļø Warning: You're on branch 'feature/new-dashboard', not main/master
258
+ āœ… Pre-flight checks passed
259
+ ```
260
+
261
+ ## Interactive Mode
262
+
263
+ Run `vnxt` (or `vx`) without arguments for guided prompts:
264
+
265
+ **Bash/PowerShell:**
266
+ ```bash
267
+ vx
268
+ ```
269
+
270
+ Output:
271
+ ```
272
+ šŸ¤” Interactive mode
273
+
274
+ Commit message: feat: add new feature
275
+ Version type (patch/minor/major) [patch]: minor
276
+
277
+ šŸ“ Auto-detected: minor version bump (feature)
278
+
279
+ šŸ” Running pre-flight checks...
280
+ ...
281
+ ```
282
+
283
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Workflow Examples
284
+
285
+ ### Quick Fix
286
+
287
+ **Bash/PowerShell:**
288
+ ```bash
289
+ vx -m "fix: resolve login bug"
290
+ ```
291
+
292
+ ### Feature Release
293
+
294
+ **Bash/PowerShell:**
295
+ ```bash
296
+ vx -m "feat: add dashboard analytics" -c -p
297
+ ```
298
+
299
+ ### Major Release with Full Documentation
300
+
301
+ **Bash/PowerShell:**
302
+ ```bash
303
+ vx -m "BREAKING: new API structure" -c -r -p
304
+ ```
305
+
306
+ ### Local Development (No Push)
307
+
308
+ **Bash/PowerShell:**
309
+ ```bash
310
+ vx -m "chore: refactor code" -a
311
+ ```
312
+
313
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Troubleshooting
314
+
315
+ ### Permission Denied (Windows PowerShell)
316
+
317
+ If you get execution policy errors:
318
+ ```powershell
319
+ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
320
+ ```
321
+
322
+ ### Uncommitted Changes Error
323
+
324
+ Either commit your changes first, or use the `-a` flag to stage all changes:
325
+
326
+ **Bash/PowerShell:**
327
+ ```bash
328
+ vx -m "your message" -a
329
+ ```
330
+
331
+ ### Command Not Found After Installation
332
+
333
+ Make sure npm's global bin directory is in your PATH:
334
+
335
+ **Bash:**
336
+ ```bash
337
+ npm config get prefix
338
+ # Add the bin subdirectory to your PATH
339
+ ```
340
+
341
+ **PowerShell:**
342
+ ```powershell
343
+ npm config get prefix
344
+ # Add the bin subdirectory to your PATH in System Environment Variables
345
+ ```
346
+
347
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Requirements
348
+
349
+ - Node.js 12.x or higher
350
+ - npm 6.x or higher
351
+ - Git installed and configured
352
+
353
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Version Management
354
+
355
+ This project uses [vnxt](https://vnxt.dev) for version bumping with the following configuration:
356
+
357
+ - Auto-push enabled (`autoPush: true`)
358
+ - Auto-changelog enabled (`autoChangelog: true`)
359
+ - Clean working directory not required (`requireCleanWorkingDir: false`)
360
+
361
+ See `.vnxtrc.json` for full configuration.
362
+
363
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Author
364
+
365
+ Nate Orrow - Software Developer
366
+
367
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> License
368
+
369
+ MIT License - see [LICENSE](LICENSE) file for details
370
+
371
+ ## <img src="./docs/logos/caret-38x38.png" width="24" align="center"> Contributing
372
+
373
+ Contributions are welcome! Please feel free to submit a Pull Request.
374
+
375
+ 1. Fork the repository
376
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
377
+ 3. Commit your changes (`git commit -m 'feat: add amazing feature'`)
378
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
379
+ 5. Open a Pull Request
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "vnxt",
3
+ "version": "1.4.5",
4
+ "description": "Version incrementation CLI tool with built in git commit, push and changelog generation",
5
+ "main": "vnxt.js",
6
+ "bin": {
7
+ "vnxt": "./vnxt.js",
8
+ "vx": "./vnxt.js"
9
+ },
10
+ "scripts": {
11
+ "test": "jest",
12
+ "release": "vnxt -m 'release' -c -p"
13
+ },
14
+ "keywords": [
15
+ "version",
16
+ "bump",
17
+ "semver",
18
+ "git",
19
+ "changelog",
20
+ "cli",
21
+ "release",
22
+ "npm"
23
+ ],
24
+ "author": "Nate Orrow | Nate@Orrow.uk",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/n-orrow/vnxt.git"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/n-orrow/vnxt/issues"
32
+ },
33
+ "homepage": "https://vnxt.dev",
34
+ "engines": {
35
+ "node": ">=12.0.0"
36
+ },
37
+ "files": [
38
+ "vnxt.js",
39
+ ".vnxtrc.json",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "devDependencies": {
44
+ "jest": "^29.7.0"
45
+ }
46
+ }
package/vnxt.js ADDED
@@ -0,0 +1,443 @@
1
+ #!/usr/bin/env node
2
+
3
+ const {execSync} = require('child_process');
4
+ const fs = require('fs');
5
+ const readline = require('readline');
6
+
7
+ // Parse command line arguments
8
+ const args = process.argv.slice(2);
9
+
10
+ // Helper to parse flags
11
+ function getFlag(flag, short) {
12
+ const index = args.indexOf(flag) !== -1 ? args.indexOf(flag) : args.indexOf(short);
13
+ if (index === -1) return null;
14
+ return args[index + 1] || true;
15
+ }
16
+
17
+ function hasFlag(flag, short) {
18
+ return args.includes(flag) || args.includes(short);
19
+ }
20
+
21
+ // Load config file if exists
22
+ let config = {
23
+ autoChangelog: true,
24
+ defaultType: 'patch',
25
+ requireCleanWorkingDir: false,
26
+ autoPush: true,
27
+ defaultStageMode: 'tracked',
28
+ tagPrefix: 'v'
29
+ };
30
+
31
+ if (fs.existsSync('.vnxtrc.json')) {
32
+ const userConfig = JSON.parse(fs.readFileSync('.vnxtrc.json', 'utf8'));
33
+ config = {...config, ...userConfig};
34
+ }
35
+
36
+ // Parse arguments
37
+ let message = getFlag('--message', '-m');
38
+ let type = getFlag('--type', '-t') || config.defaultType;
39
+ let customVersion = getFlag('--version', '-v');
40
+ let dryRun = hasFlag('--dry-run', '-d');
41
+ let noPush = hasFlag('--no-push', '-dnp');
42
+ let push = noPush ? false : (hasFlag('--push', '-p') || config.autoPush);
43
+ let generateChangelog = hasFlag('--changelog', '-c') || config.autoChangelog;
44
+ const addAllFlag = getFlag('--all', '-a');
45
+ let addMode = null; // Will be set to: 'tracked', 'all', 'interactive', 'patch', or null
46
+ let promptForStaging = false; // If true, prompt user for staging mode
47
+ if (addAllFlag) {
48
+ // If -a has a value, use it as the mode
49
+ if (typeof addAllFlag === 'string') {
50
+ const mode = addAllFlag.toLowerCase();
51
+ if (['tracked', 'all', 'a', 'interactive', 'i', 'patch', 'p'].includes(mode)) {
52
+ if (mode === 'a') addMode = 'all';
53
+ else if (mode === 'i') addMode = 'interactive';
54
+ else if (mode === 'p') addMode = 'patch';
55
+ else addMode = mode;
56
+ } else {
57
+ console.error(`Error: Invalid add mode '${addAllFlag}'. Use: tracked, all, interactive (i), or patch (p)`);
58
+ process.exit(1);
59
+ }
60
+ } else {
61
+ // If -a has no value, we'll prompt the user later
62
+ promptForStaging = true;
63
+ }
64
+ }
65
+ let generateReleaseNotes = hasFlag('--release', '-r');
66
+
67
+ // Interactive mode helper
68
+ async function prompt(question) {
69
+ const rl = readline.createInterface({
70
+ input: process.stdin, output: process.stdout
71
+ });
72
+
73
+ return new Promise(resolve => {
74
+ rl.question(question, answer => {
75
+ rl.close();
76
+ resolve(answer);
77
+ });
78
+ });
79
+ }
80
+
81
+ // Main function
82
+ async function main() {
83
+ try {
84
+ // Interactive mode if no message provided
85
+ if (!message) {
86
+ console.log('šŸ¤” Interactive mode\n');
87
+
88
+ message = await prompt('Commit message: ');
89
+ if (!message) {
90
+ console.error('Error: Commit message is required');
91
+ process.exit(1);
92
+ }
93
+
94
+ const typeInput = await prompt('Version type (patch/minor/major) [auto-detect]: ');
95
+ if (typeInput && ['patch', 'minor', 'major'].includes(typeInput)) {
96
+ type = typeInput;
97
+ }
98
+
99
+ const changelogInput = await prompt('Update CHANGELOG.md? (y/n) [n]: ');
100
+ generateChangelog = changelogInput.toLowerCase() === 'y' || changelogInput.toLowerCase() === 'yes' || generateChangelog;
101
+
102
+ const releaseNotesInput = await prompt('Generate release notes? (y/n) [n]: ');
103
+ generateReleaseNotes = releaseNotesInput.toLowerCase() === 'y' || releaseNotesInput.toLowerCase() === 'yes';
104
+
105
+ const pushInput = await prompt('Push to remote? (y/n) [n]: ');
106
+ push = pushInput.toLowerCase() === 'y' || pushInput.toLowerCase() === 'yes' || push;
107
+
108
+ const dryRunInput = await prompt('Dry run (preview only)? (y/n) [n]: ');
109
+ dryRun = dryRunInput.toLowerCase() === 'y' || dryRunInput.toLowerCase() === 'yes';
110
+
111
+ console.log(''); // Blank line before proceeding
112
+ }
113
+
114
+ // Auto-detect version type from conventional commit format
115
+ if (!customVersion && !getFlag('--type', '-t')) {
116
+ if (message.startsWith('major:') || message.startsWith('MAJOR:')) {
117
+ type = 'major';
118
+ console.log('šŸ“ Auto-detected: major version bump');
119
+ } else if (message.startsWith('minor:') || message.startsWith('MINOR:')) {
120
+ type = 'minor';
121
+ console.log('šŸ“ Auto-detected: minor version bump');
122
+ } else if (message.startsWith('patch:') || message.startsWith('PATCH:')) {
123
+ type = 'patch';
124
+ console.log('šŸ“ Auto-detected: patch version bump');
125
+ } else if (message.startsWith('feat:') || message.startsWith('feature:')) {
126
+ type = 'minor';
127
+ console.log('šŸ“ Auto-detected: minor version bump (feature)');
128
+ } else if (message.startsWith('fix:')) {
129
+ type = 'patch';
130
+ console.log('šŸ“ Auto-detected: patch version bump (fix)');
131
+ } else if (message.includes('BREAKING') || message.startsWith('breaking:')) {
132
+ type = 'major';
133
+ console.log('šŸ“ Auto-detected: major version bump (breaking change)');
134
+ }
135
+ }
136
+
137
+ // Validate version type
138
+ if (!customVersion && !['patch', 'minor', 'major'].includes(type)) {
139
+ console.error('Error: Version type must be patch, minor, or major');
140
+ process.exit(1);
141
+ }
142
+
143
+ // PRE-FLIGHT CHECKS
144
+ console.log('\nšŸ” Running pre-flight checks...\n');
145
+
146
+ // Check for uncommitted changes OR if user requested staging prompt
147
+ if ((config.requireCleanWorkingDir && !addMode) || promptForStaging) {
148
+ const status = execSync('git status --porcelain --untracked-files=no').toString().trim();
149
+ if (status || promptForStaging) {
150
+ // No files staged and changes exist - offer interactive selection
151
+ if (status) {
152
+ console.log('āš ļø You have uncommitted changes.\n');
153
+ }
154
+ console.log('šŸ“ How would you like to stage files?\n');
155
+ console.log(' 1. Tracked files only (git add -u)');
156
+ console.log(' 2. All changes (git add -A)');
157
+ console.log(' 3. Interactive selection (git add -i)');
158
+ console.log(' 4. Patch mode (git add -p)');
159
+ console.log(' 5. Skip staging (continue without staging)\n');
160
+
161
+ const choice = await prompt('Select [1-5]: ');
162
+
163
+ if (choice === '1') {
164
+ addMode = 'tracked';
165
+ } else if (choice === '2') {
166
+ addMode = 'all';
167
+ } else if (choice === '3') {
168
+ addMode = 'interactive';
169
+ } else if (choice === '4') {
170
+ addMode = 'patch';
171
+ } else if (choice === '5') {
172
+ console.log('āš ļø Skipping file staging. Ensure files are staged manually.');
173
+ } else {
174
+ console.error('Invalid choice. Exiting.');
175
+ process.exit(1);
176
+ }
177
+ console.log('');
178
+ }
179
+ }
180
+
181
+ // Check current branch
182
+ const branch = execSync('git branch --show-current').toString().trim();
183
+ if (branch !== 'main' && branch !== 'master') {
184
+ console.log(`āš ļø Warning: You're on branch '${branch}', not main/master`);
185
+ }
186
+
187
+ // Check if remote exists
188
+ try {
189
+ execSync('git remote get-url origin', {stdio: 'pipe'});
190
+ } catch {
191
+ if (push) {
192
+ console.error('āŒ Error: No remote repository configured, cannot push');
193
+ process.exit(1);
194
+ }
195
+ console.log('āš ļø Warning: No remote repository configured');
196
+ }
197
+
198
+ console.log('āœ… Pre-flight checks passed\n');
199
+
200
+ // DRY RUN MODE
201
+ if (dryRun) {
202
+ console.log('šŸ”¬ DRY RUN MODE - No changes will be made\n');
203
+ console.log('Would perform the following actions:');
204
+
205
+ if (addMode) {
206
+ const modeDescriptions = {
207
+ 'tracked': 'Stage tracked files only (git add -u)',
208
+ 'all': 'Stage all changes (git add -A)',
209
+ 'interactive': 'Interactive selection (git add -i)',
210
+ 'patch': 'Patch mode (git add -p)'
211
+ };
212
+ console.log(` 1. ${modeDescriptions[addMode]}`);
213
+ }
214
+
215
+ if (customVersion) {
216
+ console.log(` 2. Set version to: ${customVersion}`);
217
+ } else {
218
+ console.log(` 2. Bump ${type} version`);
219
+ }
220
+
221
+ console.log(` 3. Commit with message: "${message}"`);
222
+ console.log(` 4. Create git tag with annotation`);
223
+
224
+ if (generateChangelog) {
225
+ console.log(' 5. Update CHANGELOG.md');
226
+ }
227
+
228
+ if (generateReleaseNotes) {
229
+ console.log(' 6. Generate release notes file');
230
+ }
231
+
232
+ if (push) {
233
+ console.log(' 7. Push to remote with tags');
234
+ } else {
235
+ console.log(' 7. (Skipping push - use --push to enable)');
236
+ }
237
+
238
+ console.log('\nāœ“ Dry run complete. Use without -d to apply changes.');
239
+ process.exit(0);
240
+ }
241
+
242
+ // STAGE FILES if requested
243
+ if (addMode) {
244
+ console.log('šŸ“¦ Staging files...');
245
+
246
+ if (addMode === 'tracked') {
247
+ // Only stage tracked files that have been modified/deleted
248
+ execSync('git add -u', {stdio: 'inherit'});
249
+ } else if (addMode === 'all') {
250
+ // Stage all changes (respects .gitignore for new files)
251
+ execSync('git add -A', {stdio: 'inherit'});
252
+ } else if (addMode === 'interactive') {
253
+ // Interactive staging
254
+ execSync('git add -i', {stdio: 'inherit'});
255
+ } else if (addMode === 'patch') {
256
+ // Patch mode staging
257
+ execSync('git add -p', {stdio: 'inherit'});
258
+ }
259
+ }
260
+ // BUMP VERSION
261
+ console.log(`\nšŸ”¼ Bumping version...`);
262
+
263
+ // Get current version before bump
264
+ const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
265
+ const oldVersion = packageJson.version;
266
+
267
+ // Always disable npm's git integration and handle it ourselves
268
+ if (customVersion) {
269
+ execSync(`npm version ${customVersion} --git-tag-version=false`, {stdio: 'inherit'});
270
+ } else {
271
+ execSync(`npm version ${type} --git-tag-version=false`, {stdio: 'inherit'});
272
+ }
273
+
274
+ // Get new version
275
+ const newVersion = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version;
276
+
277
+ // Stage package files
278
+ execSync('git add package.json', {stdio: 'pipe'});
279
+ if (fs.existsSync('package-lock.json')) {
280
+ execSync('git add package-lock.json', {stdio: 'pipe'});
281
+ }
282
+
283
+ // Commit with user's message
284
+ execSync(`git commit -m "${message}"`, {stdio: 'inherit'});
285
+
286
+ // Create annotated tag
287
+ execSync(`git tag -a v${newVersion} -m "Version ${newVersion}\n\n${message}"`, {stdio: 'pipe'});
288
+
289
+ // ADD GIT TAG ANNOTATION (keeping console.log for UX)
290
+ console.log('šŸ·ļø Adding tag annotation...');
291
+ const tagMessage = `Version ${newVersion}\n\n${message}`;
292
+ execSync(`git tag -a v${newVersion} -f -m "${tagMessage}"`, {stdio: 'pipe'});
293
+
294
+ // GENERATE CHANGELOG
295
+ if (generateChangelog) {
296
+ console.log('šŸ“„ Updating CHANGELOG.md...');
297
+ const date = new Date().toISOString().split('T')[0];
298
+ const changelogEntry = `\n## [${newVersion}] - ${date}\n- ${message}\n`;
299
+
300
+ let changelog = '# Changelog\n';
301
+ if (fs.existsSync('CHANGELOG.md')) {
302
+ changelog = fs.readFileSync('CHANGELOG.md', 'utf8');
303
+ }
304
+
305
+ // Insert new entry after the title
306
+ const lines = changelog.split('\n');
307
+ const titleIndex = lines.findIndex(line => line.startsWith('# Changelog'));
308
+ lines.splice(titleIndex + 1, 0, changelogEntry);
309
+
310
+ fs.writeFileSync('CHANGELOG.md', lines.join('\n'));
311
+
312
+ // Stage the changelog
313
+ execSync('git add CHANGELOG.md', {stdio: 'pipe'});
314
+ execSync(`git commit --amend --no-edit`, {stdio: 'pipe'});
315
+ }
316
+
317
+ // GENERATE RELEASE NOTES
318
+ if (generateReleaseNotes) {
319
+ console.log('šŸ“‹ Generating release notes...');
320
+ const releaseNotes = `# Release v${newVersion}
321
+
322
+ Released: ${new Date().toISOString().split('T')[0]}
323
+
324
+ ## Changes
325
+ ${message}
326
+
327
+ ## Installation
328
+ \`\`\`bash
329
+ npm install ${packageJson.name}@${newVersion}
330
+ \`\`\`
331
+
332
+ ## Full Changelog
333
+ See [CHANGELOG.md](./CHANGELOG.md) for complete version history.
334
+ `;
335
+
336
+ const filename = `release-notes-v${newVersion}.md`;
337
+ fs.writeFileSync(filename, releaseNotes);
338
+ console.log(` Created: ${filename}`);
339
+ }
340
+
341
+ // PUSH TO REMOTE
342
+ if (push) {
343
+ console.log('šŸš€ Pushing to remote...');
344
+ execSync('git push --follow-tags', {stdio: 'inherit'});
345
+ }
346
+
347
+ // STATS/SUMMARY
348
+ console.log('\nšŸ“Š Summary:');
349
+ console.log('━'.repeat(50));
350
+
351
+ console.log(`\nšŸ“¦ Version: ${oldVersion} → ${newVersion}`);
352
+ console.log(`šŸ’¬ Message: ${message}`);
353
+ console.log(`šŸ·ļø Tag: v${newVersion}`);
354
+ console.log(`🌿 Branch: ${branch}`);
355
+
356
+ if (generateChangelog) {
357
+ console.log(`šŸ“„ Changelog: Updated`);
358
+ }
359
+
360
+ if (generateReleaseNotes) {
361
+ console.log(`šŸ“‹ Release notes: Generated`);
362
+ }
363
+
364
+ if (push) {
365
+ console.log(`šŸš€ Remote: Pushed with tags`);
366
+ } else {
367
+ console.log(`šŸ“ Remote: Not pushed (use --push to enable)`);
368
+ }
369
+
370
+ // Show files changed
371
+ console.log('\nšŸ“ Files changed:');
372
+ const diff = execSync('git diff HEAD~1 --stat').toString();
373
+ console.log(diff);
374
+
375
+ console.log('━'.repeat(50));
376
+ console.log('\nāœ… Version bump complete!\n');
377
+
378
+ } catch (error) {
379
+ console.error('\nāŒ Error:', error.message);
380
+ process.exit(1);
381
+ }
382
+ }
383
+
384
+ // Show help
385
+ if (hasFlag('--help', '-h')) {
386
+ console.log(`
387
+ vnxt (vx) - Version Bump CLI Tool
388
+
389
+ Usage:
390
+ vnxt [options]
391
+ vx -m "commit message" [options]
392
+
393
+ Options:
394
+ -m, --message <msg> Commit message (required, or use interactive mode)
395
+ -t, --type <type> Version type: patch, minor, major (auto-detected from message)
396
+ -v, --version <ver> Set specific version (e.g., 2.0.0-beta.1)
397
+ -p, --push Push to remote with tags
398
+ -dnp, --no-push Prevent auto-push (overrides config)
399
+ -c, --changelog Update CHANGELOG.md
400
+ -d, --dry-run Show what would happen without making changes
401
+ -a, --all [mode] Stage files before versioning
402
+ Modes: tracked (default), all, interactive (i), patch (p)
403
+ If no mode specified, prompts interactively
404
+ -r, --release Generate release notes file
405
+ -h, --help Show this help message
406
+
407
+ Auto-detection:
408
+ - "major:" → major version
409
+ - "minor:" → minor version
410
+ - "patch:" → patch version
411
+ - "feat:" or "feature:" → minor version
412
+ - "fix:" → patch version
413
+ - "BREAKING" or "breaking:" → major version
414
+
415
+ Configuration:
416
+ Create .vnxtrc.json in your project:
417
+ {
418
+ "autoChangelog": true,
419
+ "defaultType": "patch",
420
+ "requireCleanWorkingDir": false,
421
+ "autoPush": true,
422
+ "defaultStageMode": "tracked",
423
+ "tagPrefix": "v"
424
+ }
425
+
426
+ Examples:
427
+ vx -m "fix: resolve bug" # Auto-pushes with autoPush: true
428
+ vx -m "feat: add new feature" # Auto-pushes with autoPush: true
429
+ vx -m "fix: bug" -dnp # Don't push (override)
430
+ vx -v 2.0.0-beta.1 -m "beta release"
431
+ vx -m "test" -d
432
+ vx -m "fix: bug" -a # Interactive prompt for staging
433
+ vx -m "fix: bug" -a tracked # Stage tracked files only
434
+ vx -m "fix: bug" -a all # Stage all changes
435
+ vx -m "fix: bug" -a i # Interactive git add
436
+ vx -m "fix: bug" -a p # Patch mode
437
+ vx # Interactive mode
438
+ `);
439
+ process.exit(0);
440
+ }
441
+
442
+ // Run main function
443
+ main();