format-commit 0.4.0 → 1.0.1
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 +98 -22
- package/lib/ai-service.js +289 -0
- package/lib/commit.js +275 -76
- package/lib/create-branch.js +6 -25
- package/lib/default-config.json +7 -2
- package/lib/env-utils.js +109 -0
- package/lib/index.js +21 -14
- package/lib/options.json +27 -0
- package/lib/setup.js +169 -18
- package/lib/utils.js +115 -14
- package/package.json +11 -6
package/lib/commit.js
CHANGED
|
@@ -1,25 +1,126 @@
|
|
|
1
|
-
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import kleur from 'kleur';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import * as utils from './utils.js';
|
|
2
8
|
|
|
3
|
-
const prompts = require('prompts');
|
|
4
|
-
const utils = require('./utils');
|
|
5
|
-
const options = require('./options.json');
|
|
6
9
|
|
|
10
|
+
const { gray } = kleur;
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
7
13
|
|
|
8
|
-
|
|
14
|
+
const options = JSON.parse(
|
|
15
|
+
readFileSync(join(__dirname, 'options.json'), 'utf-8')
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/** Prompt with pre-filled text (editable) using readline */
|
|
19
|
+
const promptWithPrefill = (message, prefill, validate) => {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const askQuestion = (text) => {
|
|
22
|
+
const rl = readline.createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const questionText = `${kleur.bold(message)}\n› `;
|
|
28
|
+
|
|
29
|
+
// Handle Ctrl+C to cancel
|
|
30
|
+
rl.on('SIGINT', () => {
|
|
31
|
+
rl.close();
|
|
32
|
+
console.log('');
|
|
33
|
+
reject(new Error('cancelled'));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
rl.question(questionText, (answer) => {
|
|
37
|
+
rl.close();
|
|
38
|
+
|
|
39
|
+
const validation = validate ? validate(answer) : true;
|
|
40
|
+
if (validation !== true) {
|
|
41
|
+
utils.log(validation, 'error');
|
|
42
|
+
// Ask again with current answer as prefill
|
|
43
|
+
askQuestion(answer);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
resolve(answer);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
rl.write(text);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
askQuestion(prefill);
|
|
54
|
+
});
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
/** Execute commit, handle version bump, push and show status */
|
|
59
|
+
const finalizeCommit = async (title, description, commitData, currentBranch, testMode) => {
|
|
60
|
+
utils.log('commit changes...');
|
|
61
|
+
|
|
62
|
+
if (testMode) {
|
|
63
|
+
utils.log(title, 'warning');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const commitRes = utils.handleCmdExec(`git commit -m "${title}" -m "${description}"`);
|
|
68
|
+
if (!commitRes) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
utils.log('commit successfully completed', 'success');
|
|
72
|
+
console.log(commitRes);
|
|
73
|
+
|
|
74
|
+
let newVersion = null;
|
|
75
|
+
if (commitData.version === 'prerelease') {
|
|
76
|
+
const preRelease = await prompts([
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
name: 'tag',
|
|
80
|
+
message: 'Pre-release tag?',
|
|
81
|
+
},
|
|
82
|
+
]);
|
|
83
|
+
utils.log('update version...');
|
|
84
|
+
newVersion = utils.handleCmdExec(`npm version ${commitData.version} --preid=${preRelease.tag}`);
|
|
85
|
+
} else if (commitData.version) {
|
|
86
|
+
utils.log('update version...');
|
|
87
|
+
const version = commitData.customVersion ? commitData.customVersion : commitData.version;
|
|
88
|
+
newVersion = utils.handleCmdExec(`npm version ${version} --allow-same-version`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (newVersion) {
|
|
92
|
+
utils.log(`package updated to ${newVersion}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (commitData.pushAfterCommit) {
|
|
96
|
+
utils.log('push changes...');
|
|
97
|
+
const gitPush = utils.handleCmdExec(`git push -u origin ${currentBranch}`);
|
|
98
|
+
console.log(gitPush);
|
|
99
|
+
}
|
|
100
|
+
const gitStatus = utils.handleCmdExec('git status');
|
|
101
|
+
console.log(gitStatus);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
export default async function commit(config, testMode) {
|
|
9
106
|
if (!config) {
|
|
10
107
|
return;
|
|
11
108
|
}
|
|
12
109
|
utils.log('new commit');
|
|
13
110
|
|
|
111
|
+
if (!utils.hasStagedChanges()) {
|
|
112
|
+
utils.log('No staged changes found - stage your changes with git add', 'error');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
14
116
|
if (testMode) {
|
|
15
117
|
utils.log('test mode enabled - commit will not be performed', 'warning');
|
|
16
118
|
}
|
|
17
119
|
|
|
18
|
-
|
|
19
|
-
* Get current git branch for version change option "only on release branch"
|
|
20
|
-
*/
|
|
120
|
+
// Get current git branch for version change option "only on release branch"
|
|
21
121
|
const currentBranch = utils.getCurrentBranch();
|
|
22
122
|
const askForVersion = utils.askForVersion(config, currentBranch);
|
|
123
|
+
const ignoreVersion = config.changeVersion === 'ignore';
|
|
23
124
|
|
|
24
125
|
const noType = !config.types || (config.types && config.types.length === 0);
|
|
25
126
|
if (noType) {
|
|
@@ -33,8 +134,144 @@ module.exports = async (config, testMode) => {
|
|
|
33
134
|
return;
|
|
34
135
|
}
|
|
35
136
|
|
|
137
|
+
// Warn if using default example scope and skip AI suggestions
|
|
138
|
+
const hasDefaultScope = config.scopes && config.scopes.length === 1 && config.scopes[0].value === 'example';
|
|
139
|
+
if (hasDefaultScope) {
|
|
140
|
+
utils.log('You are using the default scope "example" - customize your scopes in commit-config.json or run format-commit --config', 'warning');
|
|
141
|
+
utils.log('AI suggestions skipped - configure your scopes', 'warning');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let commitTitle = null;
|
|
145
|
+
let useAISuggestion = false;
|
|
146
|
+
|
|
147
|
+
if (config.ai?.enabled && !hasDefaultScope) {
|
|
148
|
+
utils.log('generating suggestions...');
|
|
149
|
+
const aiService = await import('./ai-service.js');
|
|
150
|
+
let suggestions = [];
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
suggestions = await aiService.generateCommitSuggestions(config, testMode);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
utils.log(`AI suggestions failed (${err.message})`, 'warning');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (suggestions?.length) {
|
|
159
|
+
let aiCancelled = false;
|
|
160
|
+
const aiChoice = await prompts({
|
|
161
|
+
type: 'select',
|
|
162
|
+
name: 'selectedTitle',
|
|
163
|
+
message: 'Start with a suggested title?',
|
|
164
|
+
choices: [
|
|
165
|
+
...suggestions.map(s => ({ title: s, value: s })),
|
|
166
|
+
{ title: gray('Custom (enter manually)'), value: '__custom__' },
|
|
167
|
+
],
|
|
168
|
+
}, {
|
|
169
|
+
onCancel: () => {
|
|
170
|
+
aiCancelled = true;
|
|
171
|
+
return false;
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (aiCancelled) {
|
|
176
|
+
utils.log('commit cancelled', 'error');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (aiChoice.selectedTitle && aiChoice.selectedTitle !== '__custom__') {
|
|
181
|
+
try {
|
|
182
|
+
const rawTitle = await promptWithPrefill(
|
|
183
|
+
'Edit commit title or continue:',
|
|
184
|
+
aiChoice.selectedTitle,
|
|
185
|
+
val => {
|
|
186
|
+
if (!val || val.trim().length === 0) {
|
|
187
|
+
return 'Commit title cannot be empty';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Parse and validate format, type, and scope
|
|
191
|
+
const result = utils.parseAndNormalizeCommitTitle(val, config);
|
|
192
|
+
if (result.error) {
|
|
193
|
+
return result.error;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Validate length of normalized title
|
|
197
|
+
const lengthCheck = utils.validCommitTitle(result.normalized, config.minLength, config.maxLength);
|
|
198
|
+
if (lengthCheck !== true) {
|
|
199
|
+
return lengthCheck;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Normalize the final title (correct case)
|
|
207
|
+
const result = utils.parseAndNormalizeCommitTitle(rawTitle, config);
|
|
208
|
+
commitTitle = result.normalized;
|
|
209
|
+
useAISuggestion = true;
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (err.message === 'cancelled') {
|
|
212
|
+
utils.log('commit cancelled', 'error');
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
36
220
|
let cancelled = false;
|
|
37
|
-
|
|
221
|
+
|
|
222
|
+
// If AI suggestion was accepted, only ask for description, version, and push
|
|
223
|
+
if (useAISuggestion) {
|
|
224
|
+
const commit = await prompts([
|
|
225
|
+
{
|
|
226
|
+
type: 'text',
|
|
227
|
+
name: 'description',
|
|
228
|
+
message: 'Commit description?',
|
|
229
|
+
validate: val => val.length > 255 ? 'Commit description too long' : true,
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
type: ignoreVersion ? null : (askForVersion ? null : 'confirm'),
|
|
233
|
+
name: 'changeVersion',
|
|
234
|
+
message: 'Change package version?',
|
|
235
|
+
initial: false,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
type: prev => ignoreVersion ? null : (askForVersion | prev ? 'select' : null),
|
|
239
|
+
name: 'version',
|
|
240
|
+
message: 'Type of version change',
|
|
241
|
+
choices: config.showAllVersionTypes
|
|
242
|
+
? [...options.versionTypes, ...options.allVersionTypes]
|
|
243
|
+
: options.versionTypes,
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
type: prev => prev === 'custom' ? 'text' : null,
|
|
247
|
+
name: 'customVersion',
|
|
248
|
+
message: 'Version?',
|
|
249
|
+
validate: val => utils.validVersion(val),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
type: 'confirm',
|
|
253
|
+
name: 'pushAfterCommit',
|
|
254
|
+
message: 'Push changes?',
|
|
255
|
+
initial: false,
|
|
256
|
+
},
|
|
257
|
+
], {
|
|
258
|
+
onCancel: () => {
|
|
259
|
+
cancelled = true;
|
|
260
|
+
return false;
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
if (cancelled) {
|
|
265
|
+
utils.log('commit cancelled', 'error');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
await finalizeCommit(commitTitle, commit.description, commit, currentBranch, testMode);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Classic flow: ask for type and scope
|
|
274
|
+
const typeScope = await prompts([
|
|
38
275
|
{
|
|
39
276
|
type: 'select',
|
|
40
277
|
name: 'type',
|
|
@@ -47,11 +284,31 @@ module.exports = async (config, testMode) => {
|
|
|
47
284
|
message: 'Scope',
|
|
48
285
|
choices: config.scopes,
|
|
49
286
|
},
|
|
287
|
+
], {
|
|
288
|
+
onCancel: () => {
|
|
289
|
+
cancelled = true;
|
|
290
|
+
return false;
|
|
291
|
+
},
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (cancelled) {
|
|
295
|
+
utils.log('commit cancelled', 'error');
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Ask for title with full formatted length validation, then description, and options
|
|
300
|
+
const commit = await prompts([
|
|
50
301
|
{
|
|
51
302
|
type: 'text',
|
|
52
303
|
name: 'title',
|
|
53
304
|
message: 'Commit title?',
|
|
54
|
-
validate: val =>
|
|
305
|
+
validate: val => {
|
|
306
|
+
if (!val || val.trim().length === 0) {
|
|
307
|
+
return 'Commit title cannot be empty';
|
|
308
|
+
}
|
|
309
|
+
const fullTitle = utils.formatCommitTitle(typeScope.type, val, config.format, typeScope.scope);
|
|
310
|
+
return utils.validCommitTitle(fullTitle, config.minLength, config.maxLength);
|
|
311
|
+
},
|
|
55
312
|
},
|
|
56
313
|
{
|
|
57
314
|
type: 'text',
|
|
@@ -60,18 +317,16 @@ module.exports = async (config, testMode) => {
|
|
|
60
317
|
validate: val => val.length > 255 ? 'Commit description too long' : true,
|
|
61
318
|
},
|
|
62
319
|
{
|
|
63
|
-
type: askForVersion ? null : 'confirm',
|
|
320
|
+
type: ignoreVersion ? null : (askForVersion ? null : 'confirm'),
|
|
64
321
|
name: 'changeVersion',
|
|
65
322
|
message: 'Change package version?',
|
|
66
323
|
initial: false,
|
|
67
324
|
},
|
|
68
325
|
{
|
|
69
|
-
type: prev => askForVersion | prev ? 'select' : null,
|
|
326
|
+
type: prev => ignoreVersion ? null : (askForVersion | prev ? 'select' : null),
|
|
70
327
|
name: 'version',
|
|
71
328
|
message: 'Type of version change',
|
|
72
|
-
|
|
73
|
-
* Display only some npm version options or all depending on config
|
|
74
|
-
*/
|
|
329
|
+
// Display only some npm version options or all depending on config
|
|
75
330
|
choices: config.showAllVersionTypes
|
|
76
331
|
? [...options.versionTypes, ...options.allVersionTypes]
|
|
77
332
|
: options.versionTypes,
|
|
@@ -95,73 +350,17 @@ module.exports = async (config, testMode) => {
|
|
|
95
350
|
},
|
|
96
351
|
});
|
|
97
352
|
|
|
98
|
-
/**
|
|
99
|
-
* Handle prompt cancellation and stop commit execution
|
|
100
|
-
*/
|
|
101
353
|
if (cancelled) {
|
|
102
354
|
utils.log('commit cancelled', 'error');
|
|
103
355
|
return;
|
|
104
356
|
}
|
|
105
357
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
*/
|
|
109
|
-
utils.log('commit changes...');
|
|
110
|
-
const commitTitle = utils.formatCommitTitle(
|
|
111
|
-
commit.type,
|
|
358
|
+
commitTitle = utils.formatCommitTitle(
|
|
359
|
+
typeScope.type,
|
|
112
360
|
commit.title,
|
|
113
361
|
config.format,
|
|
114
|
-
|
|
362
|
+
typeScope.scope
|
|
115
363
|
);
|
|
116
364
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const commitRes = utils.handleCmdExec(`git commit -m "${commitTitle}" -m "${commit.description}"`);
|
|
123
|
-
if (!commitRes) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
utils.log('commit successfully completed', 'success');
|
|
127
|
-
console.log(commitRes);
|
|
128
|
-
|
|
129
|
-
let newVersion = null;
|
|
130
|
-
if (commit.version === 'prerelease') {
|
|
131
|
-
/**
|
|
132
|
-
* Ask tag if new version is a prerelease and update it
|
|
133
|
-
*/
|
|
134
|
-
const preRelease = await prompts([
|
|
135
|
-
{
|
|
136
|
-
type: 'text',
|
|
137
|
-
name: 'tag',
|
|
138
|
-
message: 'Pre-release tag?',
|
|
139
|
-
},
|
|
140
|
-
]);
|
|
141
|
-
utils.log('update version...');
|
|
142
|
-
newVersion = utils.handleCmdExec(`npm version ${commit.version} --preid=${preRelease.tag}`);
|
|
143
|
-
|
|
144
|
-
} else if (commit.version) {
|
|
145
|
-
/**
|
|
146
|
-
* Ask version if custom option selected and update it
|
|
147
|
-
*/
|
|
148
|
-
utils.log('update version...');
|
|
149
|
-
const version = commit.customVersion ? commit.customVersion : commit.version;
|
|
150
|
-
newVersion = utils.handleCmdExec(`npm version ${version} --allow-same-version`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (newVersion) {
|
|
154
|
-
utils.log(`package updated to ${newVersion}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Push commit if option selection
|
|
159
|
-
*/
|
|
160
|
-
if (commit.pushAfterCommit) {
|
|
161
|
-
utils.log('push changes...');
|
|
162
|
-
const gitPush = utils.handleCmdExec(`git push -u origin ${currentBranch}`);
|
|
163
|
-
console.log(gitPush);
|
|
164
|
-
}
|
|
165
|
-
const gitStatus = utils.handleCmdExec('git status');
|
|
166
|
-
console.log(gitStatus);
|
|
167
|
-
};
|
|
365
|
+
await finalizeCommit(commitTitle, commit.description, commit, currentBranch, testMode);
|
|
366
|
+
}
|
package/lib/create-branch.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import prompts from 'prompts';
|
|
2
|
+
import * as utils from './utils.js';
|
|
2
3
|
|
|
3
|
-
const prompts = require('prompts');
|
|
4
|
-
const utils = require('./utils');
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
module.exports = async (config, testMode) => {
|
|
5
|
+
export default async function createBranch(config, testMode) {
|
|
8
6
|
if (!config) {
|
|
9
7
|
return;
|
|
10
8
|
}
|
|
@@ -14,9 +12,6 @@ module.exports = async (config, testMode) => {
|
|
|
14
12
|
utils.log('test mode enabled - branch will not be created', 'warning');
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
/**
|
|
18
|
-
* Check if branchFormat is configured
|
|
19
|
-
*/
|
|
20
15
|
if (!config.branchFormat) {
|
|
21
16
|
utils.log('no branch format defined - please update config', 'error');
|
|
22
17
|
return;
|
|
@@ -67,17 +62,12 @@ module.exports = async (config, testMode) => {
|
|
|
67
62
|
},
|
|
68
63
|
});
|
|
69
64
|
|
|
70
|
-
/**
|
|
71
|
-
* Handle prompt cancellation and stop branch creation
|
|
72
|
-
*/
|
|
73
65
|
if (cancelled) {
|
|
74
66
|
utils.log('branch creation cancelled', 'error');
|
|
75
67
|
return;
|
|
76
68
|
}
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
* Format branch name and create it
|
|
80
|
-
*/
|
|
70
|
+
// Format branch name and create it
|
|
81
71
|
utils.log('create branch...');
|
|
82
72
|
const branchName = utils.formatBranchName(
|
|
83
73
|
branch.type,
|
|
@@ -87,22 +77,16 @@ module.exports = async (config, testMode) => {
|
|
|
87
77
|
);
|
|
88
78
|
|
|
89
79
|
if (testMode) {
|
|
90
|
-
utils.log(`
|
|
80
|
+
utils.log(`branch name: ${branchName}`, 'warning');
|
|
91
81
|
return;
|
|
92
82
|
}
|
|
93
83
|
|
|
94
|
-
/**
|
|
95
|
-
* Check if branch already exists
|
|
96
|
-
*/
|
|
97
84
|
const branchExists = utils.checkBranchExists(branchName);
|
|
98
85
|
if (branchExists) {
|
|
99
86
|
utils.log(`branch "${branchName}" already exists`, 'error');
|
|
100
87
|
return;
|
|
101
88
|
}
|
|
102
89
|
|
|
103
|
-
/**
|
|
104
|
-
* Create the branch
|
|
105
|
-
*/
|
|
106
90
|
const createCommand = branch.checkoutAfterCreate
|
|
107
91
|
? `git checkout -b ${branchName}`
|
|
108
92
|
: `git branch ${branchName}`;
|
|
@@ -119,9 +103,6 @@ module.exports = async (config, testMode) => {
|
|
|
119
103
|
utils.log(successMessage, 'success');
|
|
120
104
|
console.log(createRes);
|
|
121
105
|
|
|
122
|
-
/**
|
|
123
|
-
* Show current git status
|
|
124
|
-
*/
|
|
125
106
|
const gitStatus = utils.handleCmdExec('git status');
|
|
126
107
|
console.log(gitStatus);
|
|
127
|
-
}
|
|
108
|
+
}
|
package/lib/default-config.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"branchFormat": 1,
|
|
4
4
|
"minLength": 8,
|
|
5
5
|
"maxLength": 80,
|
|
6
|
-
"changeVersion": "
|
|
6
|
+
"changeVersion": "ignore",
|
|
7
7
|
"releaseBranch": "main",
|
|
8
8
|
"showAllVersionTypes": false,
|
|
9
9
|
"types": [
|
|
@@ -16,5 +16,10 @@
|
|
|
16
16
|
],
|
|
17
17
|
"scopes": [
|
|
18
18
|
{ "value": "example", "description": "Your scope's description" }
|
|
19
|
-
]
|
|
19
|
+
],
|
|
20
|
+
"ai": {
|
|
21
|
+
"enabled": false,
|
|
22
|
+
"envPath": ".env",
|
|
23
|
+
"largeDiffTokenThreshold": 20000
|
|
24
|
+
}
|
|
20
25
|
}
|
package/lib/env-utils.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
/** Check if a file path is in .gitignore */
|
|
6
|
+
const isInGitignore = (filePath) => {
|
|
7
|
+
try {
|
|
8
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
9
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
13
|
+
const lines = gitignoreContent.split('\n').map(l => l.trim());
|
|
14
|
+
|
|
15
|
+
const normalizedPath = filePath.startsWith('./') ? filePath.slice(2) : filePath;
|
|
16
|
+
|
|
17
|
+
return lines.some(line => {
|
|
18
|
+
if (!line || line.startsWith('#')) {return false;}
|
|
19
|
+
return line === normalizedPath || line === `/${normalizedPath}`;
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Add a file to .gitignore */
|
|
27
|
+
const addToGitignore = (filePath) => {
|
|
28
|
+
try {
|
|
29
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
30
|
+
const normalizedPath = filePath.startsWith('./') ? filePath.slice(2) : filePath;
|
|
31
|
+
|
|
32
|
+
let content = '';
|
|
33
|
+
if (fs.existsSync(gitignorePath)) {
|
|
34
|
+
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
35
|
+
if (!content.endsWith('\n')) {
|
|
36
|
+
content += '\n';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
content += `${normalizedPath}\n`;
|
|
41
|
+
fs.writeFileSync(gitignorePath, content);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** Check if a key exists in .env file */
|
|
49
|
+
const keyExistsInEnv = (envPath, keyName) => {
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(envPath)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
55
|
+
const regex = new RegExp(`^${keyName}=`, 'm');
|
|
56
|
+
return regex.test(content);
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Add or update a key in .env file */
|
|
63
|
+
const setEnvKey = (envPath, keyName, value) => {
|
|
64
|
+
try {
|
|
65
|
+
let content = '';
|
|
66
|
+
|
|
67
|
+
if (fs.existsSync(envPath)) {
|
|
68
|
+
content = fs.readFileSync(envPath, 'utf-8');
|
|
69
|
+
const regex = new RegExp(`^${keyName}=.*$`, 'm');
|
|
70
|
+
|
|
71
|
+
if (regex.test(content)) {
|
|
72
|
+
content = content.replace(regex, `${keyName}=${value}`);
|
|
73
|
+
} else {
|
|
74
|
+
if (!content.endsWith('\n')) {content += '\n';}
|
|
75
|
+
content += `${keyName}=${value}\n`;
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
content = `${keyName}=${value}\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
fs.writeFileSync(envPath, content);
|
|
82
|
+
return true;
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Read a key from .env file */
|
|
89
|
+
const getEnvKey = (envPath, keyName) => {
|
|
90
|
+
try {
|
|
91
|
+
if (!fs.existsSync(envPath)) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
95
|
+
const regex = new RegExp(`^${keyName}=(.*)$`, 'm');
|
|
96
|
+
const match = content.match(regex);
|
|
97
|
+
return match ? match[1].trim() : null;
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
isInGitignore,
|
|
105
|
+
addToGitignore,
|
|
106
|
+
keyExistsInEnv,
|
|
107
|
+
setEnvKey,
|
|
108
|
+
getEnvKey,
|
|
109
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import * as utils from './utils.js';
|
|
6
|
+
import setup from './setup.js';
|
|
7
|
+
import commit from './commit.js';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
const options = JSON.parse(
|
|
17
|
+
readFileSync(join(__dirname, 'options.json'), 'utf-8')
|
|
18
|
+
);
|
|
19
|
+
|
|
10
20
|
|
|
11
21
|
const program = new Command();
|
|
12
22
|
|
|
@@ -20,8 +30,8 @@ program
|
|
|
20
30
|
|
|
21
31
|
try {
|
|
22
32
|
program.parse(process.argv);
|
|
23
|
-
} catch (
|
|
24
|
-
console.error('Error parsing arguments:',
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error('Error parsing arguments:', err.message);
|
|
25
35
|
process.exit(1);
|
|
26
36
|
}
|
|
27
37
|
|
|
@@ -33,10 +43,7 @@ try {
|
|
|
33
43
|
return;
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
* Get config from consumer package root
|
|
38
|
-
* Generate new config file if not founded
|
|
39
|
-
*/
|
|
46
|
+
// Get config from consumer package root, generate new config file if not found
|
|
40
47
|
fs.readFile(`./${options.configFile}.json`, async (err, data) => {
|
|
41
48
|
if (err) {
|
|
42
49
|
utils.log('no configuration found', 'warning');
|
|
@@ -46,7 +53,7 @@ try {
|
|
|
46
53
|
}
|
|
47
54
|
} else {
|
|
48
55
|
if (opts.branch) {
|
|
49
|
-
const createBranch =
|
|
56
|
+
const { default: createBranch } = await import('./create-branch.js');
|
|
50
57
|
createBranch(JSON.parse(data), opts.test);
|
|
51
58
|
return;
|
|
52
59
|
}
|