vnxt 1.9.0 ā 1.9.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/package.json +1 -1
- package/vnxt.js +446 -353
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,361 @@ 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
|
+
// =============================================================================
|
|
97
|
+
|
|
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
|
+
}
|
|
87
116
|
|
|
88
|
-
//
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
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'));
|
|
396
|
+
|
|
397
|
+
execSync('git add CHANGELOG.md', {stdio: 'pipe'});
|
|
398
|
+
execSync('git commit --amend --no-edit', {stdio: 'pipe'});
|
|
399
|
+
}
|
|
380
400
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
401
|
+
// =============================================================================
|
|
402
|
+
// Generate Release Notes
|
|
403
|
+
// =============================================================================
|
|
384
404
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const dateShort = date.toISOString().split('T')[0];
|
|
405
|
+
function generateReleaseNotes(newVersion, message, context, packageJson) {
|
|
406
|
+
log('š Generating release notes...', 'cyan');
|
|
388
407
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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 {}
|
|
395
414
|
|
|
396
|
-
|
|
415
|
+
const notes = `# Release ${config.tagPrefix}${newVersion}
|
|
397
416
|
|
|
398
417
|
Released: ${dateShort} at ${timestamp.split(' ')[1]}${author ? `\nAuthor: ${author}` : ''}
|
|
399
418
|
|
|
400
419
|
## Changes
|
|
401
|
-
${message}${
|
|
420
|
+
${message}${context ? `\n\n## Release Notes\n${context}` : ''}
|
|
402
421
|
|
|
403
422
|
## Installation
|
|
404
423
|
\`\`\`bash
|
|
@@ -406,72 +425,79 @@ npm install ${packageJson.name}@${newVersion}
|
|
|
406
425
|
\`\`\`
|
|
407
426
|
|
|
408
427
|
## Full Changelog
|
|
409
|
-
See [CHANGELOG.md](
|
|
428
|
+
See [CHANGELOG.md](../CHANGELOG.md) for complete version history.
|
|
410
429
|
`;
|
|
411
430
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
log(` Created: ${filename}`);
|
|
415
|
-
}
|
|
431
|
+
const dir = 'release-notes';
|
|
432
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
|
|
416
433
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
execSync('git push --follow-tags', {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
421
|
-
|
|
422
|
-
// If --publish flag, also push a publish/v* tag to trigger npm workflow
|
|
423
|
-
if (publishToNpm) {
|
|
424
|
-
log('š¦ Pushing publish tag to trigger npm release...', 'cyan');
|
|
425
|
-
const publishTag = `publish/${config.tagPrefix}${newVersion}`;
|
|
426
|
-
execSync(`git tag ${publishTag}`, {stdio: 'pipe'});
|
|
427
|
-
execSync(`git push origin ${publishTag}`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
428
|
-
}
|
|
429
|
-
}
|
|
434
|
+
const filename = `${dir}/${config.tagPrefix}${newVersion}.md`;
|
|
435
|
+
fs.writeFileSync(filename, notes);
|
|
436
|
+
log(` Created: ${filename}`);
|
|
430
437
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
438
|
+
execSync(`git add ${filename}`, {stdio: 'pipe'});
|
|
439
|
+
execSync('git commit --amend --no-edit', {stdio: 'pipe'});
|
|
440
|
+
}
|
|
434
441
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
log(`šæ Branch: ${branch}`);
|
|
442
|
+
// =============================================================================
|
|
443
|
+
// Push to Remote
|
|
444
|
+
// =============================================================================
|
|
439
445
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
446
|
+
function pushToRemote(opts, newVersion) {
|
|
447
|
+
log('š Pushing to remote...', 'cyan');
|
|
448
|
+
execSync('git push --follow-tags', {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
443
449
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
}
|
|
450
|
+
if (opts.publishToNpm) {
|
|
451
|
+
log('š¦ Pushing publish tag to trigger npm release...', 'cyan');
|
|
452
|
+
const publishTag = `publish/${config.tagPrefix}${newVersion}`;
|
|
453
|
+
execSync(`git tag ${publishTag}`, {stdio: 'pipe'});
|
|
454
|
+
execSync(`git push origin ${publishTag}`, {stdio: quietMode ? 'pipe' : 'inherit'});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
447
457
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
458
|
+
// =============================================================================
|
|
459
|
+
// Print Summary
|
|
460
|
+
// =============================================================================
|
|
461
|
+
|
|
462
|
+
function printSummary(opts, oldVersion, newVersion, branch) {
|
|
463
|
+
log('\nš Summary:', 'cyan');
|
|
464
|
+
log('ā'.repeat(50), 'gray');
|
|
465
|
+
log(`\nš¦ Version: ${oldVersion} ā ${newVersion}`, 'green');
|
|
466
|
+
log(`š¬ Message: ${opts.message}`);
|
|
467
|
+
log(`š·ļø Tag: ${config.tagPrefix}${newVersion}`);
|
|
468
|
+
log(`šæ Branch: ${branch}`);
|
|
469
|
+
|
|
470
|
+
if (opts.generateChangelog) log('š Changelog: Updated');
|
|
471
|
+
if (opts.generateReleaseNotes) log('š Release notes: Generated');
|
|
472
|
+
|
|
473
|
+
if (opts.push) {
|
|
474
|
+
log('š Remote: Pushed with tags', 'green');
|
|
475
|
+
if (opts.publishToNpm) {
|
|
476
|
+
log(`š¦ npm: Publishing triggered (publish/${config.tagPrefix}${newVersion})`, 'green');
|
|
455
477
|
}
|
|
478
|
+
} else {
|
|
479
|
+
log('š Remote: Not pushed (use --push to enable)', 'gray');
|
|
480
|
+
}
|
|
456
481
|
|
|
457
|
-
|
|
458
|
-
|
|
482
|
+
if (!quietMode) {
|
|
483
|
+
try {
|
|
459
484
|
log('\nš Files changed:');
|
|
460
485
|
const diff = execSync('git diff HEAD~1 --stat').toString();
|
|
461
486
|
console.log(diff);
|
|
487
|
+
} catch {
|
|
488
|
+
// No previous commit to diff against
|
|
462
489
|
}
|
|
463
|
-
|
|
464
|
-
log('ā'.repeat(50), 'gray');
|
|
465
|
-
log('\nā
Version bump complete!\n', 'green');
|
|
466
|
-
|
|
467
|
-
} catch (error) {
|
|
468
|
-
logError('\nā Error: ' + error.message);
|
|
469
|
-
process.exit(1);
|
|
470
490
|
}
|
|
491
|
+
|
|
492
|
+
log('ā'.repeat(50), 'gray');
|
|
493
|
+
log('\nā
Version bump complete!\n', 'green');
|
|
471
494
|
}
|
|
472
495
|
|
|
473
|
-
//
|
|
474
|
-
|
|
496
|
+
// =============================================================================
|
|
497
|
+
// Help
|
|
498
|
+
// =============================================================================
|
|
499
|
+
|
|
500
|
+
function printHelp() {
|
|
475
501
|
console.log(`
|
|
476
502
|
vnxt (vx) - Version Bump CLI Tool
|
|
477
503
|
|
|
@@ -482,17 +508,18 @@ Usage:
|
|
|
482
508
|
Options:
|
|
483
509
|
-m, --message <msg> Commit message (required, or use interactive mode)
|
|
484
510
|
-t, --type <type> Version type: patch, minor, major (auto-detected from message)
|
|
485
|
-
-
|
|
486
|
-
-
|
|
511
|
+
-sv, --set-version <v> Set a specific version (e.g., 2.0.0-beta.1)
|
|
512
|
+
-gv, --get-version Show the current project's version
|
|
513
|
+
-vv, --vnxt-version Show the installed vnxt version
|
|
487
514
|
-p, --push Push to remote with tags
|
|
488
515
|
-dnp, --no-push Prevent auto-push (overrides config)
|
|
489
|
-
--publish Push and trigger npm publish via GitHub Actions
|
|
516
|
+
--publish Push and trigger npm publish via GitHub Actions (implies --push)
|
|
490
517
|
-c, --changelog Update CHANGELOG.md
|
|
491
518
|
-d, --dry-run Show what would happen without making changes
|
|
492
519
|
-a, --all [mode] Stage files before versioning
|
|
493
520
|
Modes: tracked (default), all, interactive (i), patch (p)
|
|
494
521
|
If no mode specified, prompts interactively
|
|
495
|
-
-r, --release Generate release notes file
|
|
522
|
+
-r, --release Generate release notes file (saved to release-notes/)
|
|
496
523
|
-q, --quiet Minimal output (errors only)
|
|
497
524
|
-h, --help Show this help message
|
|
498
525
|
|
|
@@ -501,7 +528,7 @@ Auto-detection:
|
|
|
501
528
|
- "minor:" ā minor version
|
|
502
529
|
- "patch:" ā patch version
|
|
503
530
|
- "feat:" or "feature:" ā minor version
|
|
504
|
-
- "fix:" ā patch version
|
|
531
|
+
- "fix:" ā patch version
|
|
505
532
|
- "BREAKING" or "breaking:" ā major version
|
|
506
533
|
|
|
507
534
|
Configuration:
|
|
@@ -517,11 +544,12 @@ Configuration:
|
|
|
517
544
|
}
|
|
518
545
|
|
|
519
546
|
Examples:
|
|
520
|
-
vx -
|
|
547
|
+
vx -vv # Show vnxt version
|
|
548
|
+
vx -gv # Show current project version
|
|
521
549
|
vx -m "fix: resolve bug" # Auto-pushes with autoPush: true
|
|
522
550
|
vx -m "feat: add new feature" # Auto-pushes with autoPush: true
|
|
523
551
|
vx -m "fix: bug" -dnp # Don't push (override)
|
|
524
|
-
vx -
|
|
552
|
+
vx -sv 2.0.0-beta.1 -m "beta release"
|
|
525
553
|
vx -m "test" -d
|
|
526
554
|
vx -m "fix: bug" -a # Interactive prompt for staging
|
|
527
555
|
vx -m "fix: bug" -a tracked # Stage tracked files only
|
|
@@ -530,10 +558,75 @@ Examples:
|
|
|
530
558
|
vx -m "fix: bug" -a p # Patch mode
|
|
531
559
|
vx -m "fix: bug" -q # Quiet mode (minimal output)
|
|
532
560
|
vx -m "feat: new feature" --publish # Bump, push and trigger npm publish
|
|
561
|
+
vx -m "fix: bug" -r # Generate release notes in release-notes/
|
|
533
562
|
vx # Interactive mode
|
|
534
563
|
`);
|
|
535
|
-
process.exit(0);
|
|
536
564
|
}
|
|
537
565
|
|
|
538
|
-
//
|
|
539
|
-
|
|
566
|
+
// =============================================================================
|
|
567
|
+
// Main
|
|
568
|
+
// =============================================================================
|
|
569
|
+
|
|
570
|
+
async function main() {
|
|
571
|
+
try {
|
|
572
|
+
handleQuickFlags();
|
|
573
|
+
|
|
574
|
+
// Git repo check
|
|
575
|
+
if (!fs.existsSync('.git')) {
|
|
576
|
+
logError('ā Not a git repository. Run `git init` first.');
|
|
577
|
+
process.exit(1);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const opts = parseArgs();
|
|
581
|
+
|
|
582
|
+
// Interactive mode if no message provided
|
|
583
|
+
if (!opts.message) {
|
|
584
|
+
await runInteractiveMode(opts);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Auto-detect version type from commit message
|
|
588
|
+
if (!opts.customVersion && !getFlag('--type', '-t')) {
|
|
589
|
+
opts.type = detectVersionType(opts.message, opts.type);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Validate version type
|
|
593
|
+
if (!opts.customVersion && !['patch', 'minor', 'major'].includes(opts.type)) {
|
|
594
|
+
logError('Error: Version type must be patch, minor, or major');
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Release notes context prompt
|
|
599
|
+
let releaseNotesContext = '';
|
|
600
|
+
if (!opts.generateReleaseNotes && opts.publishToNpm) {
|
|
601
|
+
opts.generateReleaseNotes = true;
|
|
602
|
+
if (!quietMode) {
|
|
603
|
+
log('\nš Release notes required for --publish.', 'yellow');
|
|
604
|
+
releaseNotesContext = await prompt(' Add context (press Enter to skip): ');
|
|
605
|
+
if (releaseNotesContext) log('');
|
|
606
|
+
}
|
|
607
|
+
} else if (opts.generateReleaseNotes && !quietMode) {
|
|
608
|
+
releaseNotesContext = await prompt('\nš Add context to release notes (press Enter to skip): ');
|
|
609
|
+
if (releaseNotesContext) log('');
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const branch = await runPreflightChecks(opts);
|
|
613
|
+
|
|
614
|
+
if (opts.dryRun) runDryRun(opts);
|
|
615
|
+
|
|
616
|
+
if (opts.addMode) stageFiles(opts.addMode);
|
|
617
|
+
|
|
618
|
+
const { oldVersion, newVersion, packageJson } = bumpVersion(opts);
|
|
619
|
+
|
|
620
|
+
if (opts.generateChangelog) generateChangelog(newVersion, opts.message);
|
|
621
|
+
if (opts.generateReleaseNotes) generateReleaseNotes(newVersion, opts.message, releaseNotesContext, packageJson);
|
|
622
|
+
if (opts.push) pushToRemote(opts, newVersion);
|
|
623
|
+
|
|
624
|
+
printSummary(opts, oldVersion, newVersion, branch);
|
|
625
|
+
|
|
626
|
+
} catch (error) {
|
|
627
|
+
logError('\nā Error: ' + error.message);
|
|
628
|
+
process.exit(1);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
main();
|