format-commit 0.3.1 → 0.4.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 +24 -9
- package/lib/commit.js +154 -157
- package/lib/create-branch.js +127 -0
- package/lib/default-config.json +18 -18
- package/lib/index.js +33 -27
- package/lib/options.json +33 -29
- package/lib/setup.js +99 -99
- package/lib/utils.js +125 -71
- package/package.json +4 -5
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/format-commit)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
🚀 Lightweight CLI for consistent
|
|
8
|
+
🚀 Lightweight CLI for consistent Git workflow formatting.
|
|
9
9
|
|
|
10
|
-
Standardize your commit naming with
|
|
10
|
+
Standardize your commit messages and branch naming with configurable rules, and guide your development workflow through automated scripts. No bloat, no complexity — just clean, consistent Git practices.
|
|
11
11
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
@@ -20,22 +20,29 @@ npm i format-commit --save-dev
|
|
|
20
20
|
Add to your `package.json` scripts:
|
|
21
21
|
```json
|
|
22
22
|
"scripts": {
|
|
23
|
-
"commit": "format-commit"
|
|
23
|
+
"commit": "format-commit",
|
|
24
|
+
"branch": "format-commit --branch"
|
|
24
25
|
}
|
|
25
26
|
```
|
|
26
27
|
|
|
27
28
|
Then use:
|
|
28
29
|
```sh
|
|
29
|
-
npm run commit
|
|
30
|
+
npm run commit # to commit
|
|
31
|
+
npm run branch # to create a branch
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
### Global Installation
|
|
35
|
+
|
|
32
36
|
Or install globally:
|
|
33
37
|
```sh
|
|
34
38
|
npm i -g format-commit
|
|
35
39
|
format-commit
|
|
40
|
+
format-commit --branch
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
### Initial Setup
|
|
44
|
+
|
|
45
|
+
On first use, format-commit will prompt you to configure your commit and branch formats, then create a `commit-config.json` file.
|
|
39
46
|
|
|
40
47
|
To reconfigure later, run:
|
|
41
48
|
```sh
|
|
@@ -47,14 +54,22 @@ format-commit --config
|
|
|
47
54
|
| Property | Description |
|
|
48
55
|
| :------- | :---------- |
|
|
49
56
|
| **format** | Commit title format:<br>1 - `(type) Name` / 2 - `(type) name`<br>3 - `type: Name` / 4 - `type: name`<br>5 - `type(scope) Name` / 6 - `type(scope) name`<br>7 - `type(scope): Name` / 8 - `type(scope): name` |
|
|
50
|
-
| **
|
|
51
|
-
| **
|
|
57
|
+
| **branchFormat** | Branch naming format:<br>1 - `type/description`<br>2 - `type/scope/description` |
|
|
58
|
+
| **types** | Allowed commit and branch types (default: `feat`, `fix`, `core`, `test`, `config`, `doc`) |
|
|
59
|
+
| **scopes** | Scopes for commit/branch categorization (used in formats 5-8 for commits, format 2 for branches) |
|
|
52
60
|
| **minLength** | Minimum length required for the commit title |
|
|
53
|
-
| **maxLength** | Maximum length required for the commit title |
|
|
61
|
+
| **maxLength** | Maximum length required for the commit title and branch description |
|
|
54
62
|
| **changeVersion** | Version change policy:<br>`never` - Always prompt for version change<br>`only on release branch` - Only release branch commits require version change<br>`always` - All commits require version change |
|
|
55
63
|
| **releaseBranch** | Main/release branch name (used if changeVersion = `only on release branch`) |
|
|
56
64
|
| **showAllVersionTypes** | Show all version types or only main ones (`major`/`minor`/`patch`/`custom`) |
|
|
57
|
-
|
|
65
|
+
|
|
66
|
+
## CLI Options
|
|
67
|
+
|
|
68
|
+
| Option | Description |
|
|
69
|
+
| :----- | :---------- |
|
|
70
|
+
| `--config` / `-c` | Generate or update configuration file |
|
|
71
|
+
| `--branch` / `-b` | Create a new standardized branch |
|
|
72
|
+
| `--test` / `-t` | Test mode - preview without executing Git commands |
|
|
58
73
|
|
|
59
74
|
## Contributing
|
|
60
75
|
|
package/lib/commit.js
CHANGED
|
@@ -6,165 +6,162 @@ const options = require('./options.json');
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
module.exports = async (config, testMode) => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
9
|
+
if (!config) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
utils.log('new commit');
|
|
13
|
+
|
|
14
|
+
if (testMode) {
|
|
15
|
+
utils.log('test mode enabled - commit will not be performed', 'warning');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get current git branch for version change option "only on release branch"
|
|
20
|
+
*/
|
|
21
|
+
const currentBranch = utils.getCurrentBranch();
|
|
22
|
+
const askForVersion = utils.askForVersion(config, currentBranch);
|
|
23
|
+
|
|
24
|
+
const noType = !config.types || (config.types && config.types.length === 0);
|
|
25
|
+
if (noType) {
|
|
26
|
+
utils.log('no types defined - please update config', 'error');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const noScope = !config.scopes || (config.scopes && config.scopes.length === 0);
|
|
31
|
+
if (config.format >= 5 && noScope) {
|
|
32
|
+
utils.log('no scopes defined - update config or format option', 'error');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
const commit = await prompts([
|
|
38
|
+
{
|
|
39
|
+
type: 'select',
|
|
40
|
+
name: 'type',
|
|
41
|
+
message: 'Type of changes',
|
|
42
|
+
choices: config.types,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: config.format >= 5 ? 'select' : null,
|
|
46
|
+
name: 'scope',
|
|
47
|
+
message: 'Scope',
|
|
48
|
+
choices: config.scopes,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'text',
|
|
52
|
+
name: 'title',
|
|
53
|
+
message: 'Commit title?',
|
|
54
|
+
validate: val => utils.validCommitTitle(val, config.minLength, config.maxLength),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'text',
|
|
58
|
+
name: 'description',
|
|
59
|
+
message: 'Commit description?',
|
|
60
|
+
validate: val => val.length > 255 ? 'Commit description too long' : true,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: askForVersion ? null : 'confirm',
|
|
64
|
+
name: 'changeVersion',
|
|
65
|
+
message: 'Change package version?',
|
|
66
|
+
initial: false,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
type: prev => askForVersion | prev ? 'select' : null,
|
|
70
|
+
name: 'version',
|
|
71
|
+
message: 'Type of version change',
|
|
72
|
+
/**
|
|
73
|
+
* Display only some npm version options or all depending on config
|
|
74
|
+
*/
|
|
75
|
+
choices: config.showAllVersionTypes
|
|
76
|
+
? [...options.versionTypes, ...options.allVersionTypes]
|
|
77
|
+
: options.versionTypes,
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: prev => prev === 'custom' ? 'text' : null,
|
|
81
|
+
name: 'customVersion',
|
|
82
|
+
message: 'Version?',
|
|
83
|
+
validate: val => utils.validVersion(val),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'confirm',
|
|
87
|
+
name: 'pushAfterCommit',
|
|
88
|
+
message: 'Push changes?',
|
|
89
|
+
initial: false,
|
|
90
|
+
},
|
|
91
|
+
], {
|
|
92
|
+
onCancel: () => {
|
|
93
|
+
cancelled = true;
|
|
94
|
+
return false;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Handle prompt cancellation and stop commit execution
|
|
100
|
+
*/
|
|
101
|
+
if (cancelled) {
|
|
102
|
+
utils.log('commit cancelled', 'error');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Format changes message and commit it
|
|
108
|
+
*/
|
|
109
|
+
utils.log('commit changes...');
|
|
110
|
+
const commitTitle = utils.formatCommitTitle(
|
|
111
|
+
commit.type,
|
|
112
|
+
commit.title,
|
|
113
|
+
config.format,
|
|
114
|
+
commit.scope
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (testMode) {
|
|
118
|
+
utils.log(commitTitle, 'warning');
|
|
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') {
|
|
98
131
|
/**
|
|
99
|
-
*
|
|
132
|
+
* Ask tag if new version is a prerelease and update it
|
|
100
133
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
utils.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
const commitTitle = utils.formatCommitTitle(
|
|
114
|
-
commit.type,
|
|
115
|
-
commit.title,
|
|
116
|
-
config.format,
|
|
117
|
-
commit.scope
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
if (testMode) {
|
|
121
|
-
utils.log(commitTitle, 'warning');
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const commitRes = utils.handleCmdExec(`git commit -m "${commitTitle}" -m "${commit.description}"`);
|
|
126
|
-
if (!commitRes) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
utils.log('commit successfully completed', 'success');
|
|
130
|
-
console.log(commitRes);
|
|
131
|
-
|
|
132
|
-
let newVersion = null;
|
|
133
|
-
if (commit.version === 'prerelease') {
|
|
134
|
-
/**
|
|
135
|
-
* Ask tag if new version is a prerelease and update it
|
|
136
|
-
*/
|
|
137
|
-
const preRelease = await prompts([
|
|
138
|
-
{
|
|
139
|
-
type: 'text',
|
|
140
|
-
name: 'tag',
|
|
141
|
-
message: 'Pre-release tag?',
|
|
142
|
-
},
|
|
143
|
-
]);
|
|
144
|
-
utils.log('update version...');
|
|
145
|
-
newVersion = utils.handleCmdExec(`npm version ${commit.version} --preid=${preRelease.tag}`);
|
|
146
|
-
|
|
147
|
-
} else if (commit.version) {
|
|
148
|
-
/**
|
|
149
|
-
* Ask version if custom option selected and update it
|
|
150
|
-
*/
|
|
151
|
-
utils.log('update version...');
|
|
152
|
-
const version = commit.customVersion ? commit.customVersion : commit.version;
|
|
153
|
-
newVersion = utils.handleCmdExec(`npm version ${version} --allow-same-version`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (newVersion) {
|
|
157
|
-
utils.log(`package updated to ${newVersion}`);
|
|
158
|
-
}
|
|
159
|
-
|
|
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) {
|
|
160
145
|
/**
|
|
161
|
-
*
|
|
146
|
+
* Ask version if custom option selected and update it
|
|
162
147
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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);
|
|
170
167
|
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const prompts = require('prompts');
|
|
4
|
+
const utils = require('./utils');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
module.exports = async (config, testMode) => {
|
|
8
|
+
if (!config) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
utils.log('new branch');
|
|
12
|
+
|
|
13
|
+
if (testMode) {
|
|
14
|
+
utils.log('test mode enabled - branch will not be created', 'warning');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Check if branchFormat is configured
|
|
19
|
+
*/
|
|
20
|
+
if (!config.branchFormat) {
|
|
21
|
+
utils.log('no branch format defined - please update config', 'error');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const noType = !config.types || (config.types && config.types.length === 0);
|
|
26
|
+
if (noType) {
|
|
27
|
+
utils.log('no types defined - please update config', 'error');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const noScope = !config.scopes || (config.scopes && config.scopes.length === 0);
|
|
32
|
+
if (config.branchFormat === 2 && noScope) {
|
|
33
|
+
utils.log('no scopes defined - update config or branch format option', 'error');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let cancelled = false;
|
|
38
|
+
const branch = await prompts([
|
|
39
|
+
{
|
|
40
|
+
type: 'select',
|
|
41
|
+
name: 'type',
|
|
42
|
+
message: 'Type of branch',
|
|
43
|
+
choices: config.types,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: config.branchFormat === 2 ? 'select' : null,
|
|
47
|
+
name: 'scope',
|
|
48
|
+
message: 'Scope',
|
|
49
|
+
choices: config.scopes,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'text',
|
|
53
|
+
name: 'description',
|
|
54
|
+
message: 'Branch description?',
|
|
55
|
+
validate: val => utils.validBranchDescription(val, config.maxLength),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'confirm',
|
|
59
|
+
name: 'checkoutAfterCreate',
|
|
60
|
+
message: 'Switch to the new branch after creation?',
|
|
61
|
+
initial: true,
|
|
62
|
+
},
|
|
63
|
+
], {
|
|
64
|
+
onCancel: () => {
|
|
65
|
+
cancelled = true;
|
|
66
|
+
return false;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle prompt cancellation and stop branch creation
|
|
72
|
+
*/
|
|
73
|
+
if (cancelled) {
|
|
74
|
+
utils.log('branch creation cancelled', 'error');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format branch name and create it
|
|
80
|
+
*/
|
|
81
|
+
utils.log('create branch...');
|
|
82
|
+
const branchName = utils.formatBranchName(
|
|
83
|
+
branch.type,
|
|
84
|
+
branch.description,
|
|
85
|
+
config.branchFormat,
|
|
86
|
+
branch.scope
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (testMode) {
|
|
90
|
+
utils.log(`Branch name: ${branchName}`, 'warning');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if branch already exists
|
|
96
|
+
*/
|
|
97
|
+
const branchExists = utils.checkBranchExists(branchName);
|
|
98
|
+
if (branchExists) {
|
|
99
|
+
utils.log(`branch "${branchName}" already exists`, 'error');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create the branch
|
|
105
|
+
*/
|
|
106
|
+
const createCommand = branch.checkoutAfterCreate
|
|
107
|
+
? `git checkout -b ${branchName}`
|
|
108
|
+
: `git branch ${branchName}`;
|
|
109
|
+
|
|
110
|
+
const createRes = utils.handleCmdExec(createCommand);
|
|
111
|
+
if (!createRes) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const successMessage = branch.checkoutAfterCreate
|
|
116
|
+
? `branch "${branchName}" successfully created and checked out`
|
|
117
|
+
: `branch "${branchName}" successfully created`;
|
|
118
|
+
|
|
119
|
+
utils.log(successMessage, 'success');
|
|
120
|
+
console.log(createRes);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Show current git status
|
|
124
|
+
*/
|
|
125
|
+
const gitStatus = utils.handleCmdExec('git status');
|
|
126
|
+
console.log(gitStatus);
|
|
127
|
+
};
|
package/lib/default-config.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
2
|
+
"format": 1,
|
|
3
|
+
"branchFormat": 1,
|
|
4
|
+
"minLength": 8,
|
|
5
|
+
"maxLength": 80,
|
|
6
|
+
"changeVersion": "never",
|
|
7
|
+
"releaseBranch": "main",
|
|
8
|
+
"showAllVersionTypes": false,
|
|
9
|
+
"types": [
|
|
10
|
+
{ "value": "fix", "description": "Issue(s) fixing" },
|
|
11
|
+
{ "value": "feat", "description": "New feature(s)" },
|
|
12
|
+
{ "value": "core", "description": "Change(s) on the application core" },
|
|
13
|
+
{ "value": "test" , "description": "Change(s) related to tests" },
|
|
14
|
+
{ "value": "config" , "description": "Project configuration" },
|
|
15
|
+
{ "value": "doc" , "description": "Documentation / comment(s)" }
|
|
16
|
+
],
|
|
17
|
+
"scopes": [
|
|
18
|
+
{ "value": "example", "description": "Your scope's description" }
|
|
19
|
+
]
|
|
20
20
|
}
|
package/lib/index.js
CHANGED
|
@@ -11,40 +11,46 @@ const options = require('./options.json');
|
|
|
11
11
|
const program = new Command();
|
|
12
12
|
|
|
13
13
|
program
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
.name('format-commit')
|
|
15
|
+
.description('CLI to standardize commit nomenclature')
|
|
16
|
+
.version('0.3.1')
|
|
17
|
+
.option('-b, --branch', 'create a new branch with standardized naming')
|
|
18
|
+
.option('-c, --config', 'generate a configuration file on your project for format-commit')
|
|
19
|
+
.option('-t, --test', 'start without finalize commit (for tests)');
|
|
19
20
|
|
|
20
21
|
try {
|
|
21
|
-
|
|
22
|
+
program.parse(process.argv);
|
|
22
23
|
} catch (error) {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
console.error('Error parsing arguments:', error.message);
|
|
25
|
+
process.exit(1);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
(async () => {
|
|
28
|
-
|
|
29
|
+
const opts = program.opts();
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
if (opts.config) {
|
|
32
|
+
await setup(false);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get config from consumer package root
|
|
38
|
+
* Generate new config file if not founded
|
|
39
|
+
*/
|
|
40
|
+
fs.readFile(`./${options.configFile}.json`, async (err, data) => {
|
|
41
|
+
if (err) {
|
|
42
|
+
utils.log('no configuration found', 'warning');
|
|
43
|
+
const setupResult = await setup(true);
|
|
44
|
+
if (setupResult && setupResult.commitAfter) {
|
|
45
|
+
commit(setupResult.config, opts.test);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
if (opts.branch) {
|
|
49
|
+
const createBranch = require('./create-branch');
|
|
50
|
+
createBranch(JSON.parse(data), opts.test);
|
|
32
51
|
return;
|
|
52
|
+
}
|
|
53
|
+
commit(JSON.parse(data), opts.test);
|
|
33
54
|
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Get config from consumer package root
|
|
37
|
-
* Generate new config file if not founded
|
|
38
|
-
*/
|
|
39
|
-
fs.readFile(`./${options.configFile}.json`, async (err, data) => {
|
|
40
|
-
if (err) {
|
|
41
|
-
utils.log('no configuration found', 'warning');
|
|
42
|
-
const setupResult = await setup(true);
|
|
43
|
-
if (setupResult && setupResult.commitAfter) {
|
|
44
|
-
commit(setupResult.config, opts.test);
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
commit(JSON.parse(data), opts.test);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
55
|
+
});
|
|
50
56
|
})();
|
package/lib/options.json
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
2
|
+
"configFile": "commit-config",
|
|
3
|
+
"commitFormats": [
|
|
4
|
+
{ "value": 1, "title": "(type) Name" },
|
|
5
|
+
{ "value": 2, "title": "(type) name" },
|
|
6
|
+
{ "value": 3, "title": "type: Name" },
|
|
7
|
+
{ "value": 4, "title": "type: name" },
|
|
8
|
+
{ "value": 5, "title": "type(scope) Name" },
|
|
9
|
+
{ "value": 6, "title": "type(scope) name" },
|
|
10
|
+
{ "value": 7, "title": "type(scope): Name" },
|
|
11
|
+
{ "value": 8, "title": "type(scope): name" }
|
|
12
|
+
],
|
|
13
|
+
"branchFormats": [
|
|
14
|
+
{ "value": 1, "title": "type/description" },
|
|
15
|
+
{ "value": 2, "title": "type/scope/description" }
|
|
16
|
+
],
|
|
17
|
+
"versionChangeMode": [
|
|
18
|
+
{ "value": "never", "title": "never (always ask)" },
|
|
19
|
+
{ "value": "releaseBranch", "title": "only on release branch" },
|
|
20
|
+
{ "value": "always" }
|
|
21
|
+
],
|
|
22
|
+
"versionTypes": [
|
|
23
|
+
{ "value": "patch" },
|
|
24
|
+
{ "value": "minor" },
|
|
25
|
+
{ "value": "major" },
|
|
26
|
+
{ "value": "custom", "title": "<custom>" }
|
|
27
|
+
],
|
|
28
|
+
"allVersionTypes": [
|
|
29
|
+
{ "value": "prepatch" },
|
|
30
|
+
{ "value": "preminor" },
|
|
31
|
+
{ "value": "premajor" },
|
|
32
|
+
{ "value": "prerelease", "title": "prerelease <tag>" },
|
|
33
|
+
{ "value": "from-git" }
|
|
34
|
+
]
|
|
31
35
|
}
|
package/lib/setup.js
CHANGED
|
@@ -8,110 +8,110 @@ const options = require('./options.json');
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
module.exports = async (askForCommitAfter) => {
|
|
11
|
-
|
|
11
|
+
utils.log('create config file');
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Get current git branch to pre-fill release branch option
|
|
15
|
+
*/
|
|
16
|
+
const currentBranch = utils.getCurrentBranch();
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
18
|
+
let cancelled = false;
|
|
19
|
+
const configChoices = await prompts([
|
|
20
|
+
{
|
|
21
|
+
type: 'select',
|
|
22
|
+
name: 'format',
|
|
23
|
+
message: 'Commit messages nomenclature',
|
|
24
|
+
choices: options.commitFormats,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
type: 'select',
|
|
28
|
+
name: 'branchFormat',
|
|
29
|
+
message: 'Branch names nomenclature',
|
|
30
|
+
choices: options.branchFormats,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'number',
|
|
34
|
+
name: 'minLength',
|
|
35
|
+
message: 'Commit messages minimum length?',
|
|
36
|
+
validate: val => utils.validCommitTitleSetupLength(val),
|
|
37
|
+
initial: defaultConfig.minLength,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'number',
|
|
41
|
+
name: 'maxLength',
|
|
42
|
+
message: 'Commit messages maximum length?',
|
|
43
|
+
validate: val => utils.validCommitTitleSetupLength(val),
|
|
44
|
+
initial: defaultConfig.maxLength,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'select',
|
|
48
|
+
name: 'changeVersion',
|
|
49
|
+
message: 'Change package version when committing',
|
|
50
|
+
choices: options.versionChangeMode,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: prev => prev === 'releaseBranch' ? 'text' : null,
|
|
54
|
+
name: 'releaseBranch',
|
|
55
|
+
message: 'Release git branch?',
|
|
56
|
+
initial: currentBranch,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
type: 'confirm',
|
|
60
|
+
name: 'showAllVersionTypes',
|
|
61
|
+
message: 'Display all npm version types?',
|
|
62
|
+
initial: defaultConfig.showAllVersionTypes,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: askForCommitAfter ? 'confirm' : null,
|
|
66
|
+
name: 'commitAfter',
|
|
67
|
+
message: 'Commit your changes now? (or exit the configuration without committing)',
|
|
68
|
+
initial: false,
|
|
69
|
+
},
|
|
70
|
+
], {
|
|
71
|
+
onCancel: () => {
|
|
72
|
+
cancelled = true;
|
|
73
|
+
return false;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Handle prompt cancellation and stop setup execution
|
|
79
|
+
*/
|
|
80
|
+
if (cancelled) {
|
|
81
|
+
utils.log('setup cancelled', 'error');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Parse prompt data and write config file
|
|
87
|
+
*/
|
|
88
|
+
const config = {
|
|
89
|
+
format: configChoices.format,
|
|
90
|
+
branchFormat: configChoices.branchFormat,
|
|
91
|
+
types: defaultConfig.types,
|
|
92
|
+
scopes: (configChoices.format >= 5 || configChoices.branchFormat === 2)
|
|
93
|
+
? defaultConfig.scopes
|
|
94
|
+
: undefined,
|
|
95
|
+
minLength: configChoices.minLength,
|
|
96
|
+
maxLength: configChoices.maxLength,
|
|
97
|
+
changeVersion: configChoices.changeVersion,
|
|
98
|
+
releaseBranch: configChoices.releaseBranch,
|
|
99
|
+
showAllVersionTypes: configChoices.showAllVersionTypes,
|
|
100
|
+
};
|
|
101
|
+
const parsedConfig = JSON.stringify(config, null, 2);
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
utils.log(`save ${options.configFile}.json file...`);
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
105
|
+
try {
|
|
106
|
+
fs.writeFileSync(`./${options.configFile}.json`, parsedConfig);
|
|
107
|
+
utils.log('config file successfully created', 'success');
|
|
108
|
+
} catch (err) {
|
|
109
|
+
utils.log(`unable to save config file: ${err}`, 'error');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
return {
|
|
114
|
+
config,
|
|
115
|
+
commitAfter: configChoices.commitAfter
|
|
116
|
+
};
|
|
117
117
|
};
|
package/lib/utils.js
CHANGED
|
@@ -5,101 +5,155 @@ const { gray, bold, red, green, yellow } = require('kleur');
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
const askForVersion = (config, branch) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
if (
|
|
9
|
+
config.changeVersion === 'always'
|
|
10
|
+
|| (config.changeVersion === 'releaseBranch' && branch === config.releaseBranch)
|
|
11
|
+
) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
const getCurrentBranch = () => {
|
|
18
|
-
|
|
18
|
+
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
|
19
19
|
};
|
|
20
20
|
|
|
21
21
|
const validCommitTitle = (title, lenMin, lenMax) => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
if (title.length < lenMin) {
|
|
23
|
+
return 'Commit title too short';
|
|
24
|
+
} else if (title.length > lenMax) {
|
|
25
|
+
return 'Commit title too long';
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const validCommitTitleSetupLength = (len) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
if (len < 1) {
|
|
32
|
+
return `${len} isn't a valid length`;
|
|
33
|
+
} else if (len > 255) {
|
|
34
|
+
return 'length cannot be higher than 255';
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const validVersion = (version) => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
const regex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
41
|
+
if (!regex.test(version)) {
|
|
42
|
+
return 'Version does not respect semantic versioning';
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const formatCommitTitle = (type, title, format, scope = '*') => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
switch (format) {
|
|
49
|
+
case 1:
|
|
50
|
+
default:
|
|
51
|
+
return `(${type}) ${title[0].toUpperCase()}${title.substr(1).toLowerCase()}`;
|
|
52
|
+
case 2:
|
|
53
|
+
return `(${type}) ${title.toLowerCase()}`;
|
|
54
|
+
case 3:
|
|
55
|
+
return `${type}: ${title[0].toUpperCase()}${title.substr(1).toLowerCase()}`;
|
|
56
|
+
case 4:
|
|
57
|
+
return `${type}: ${title.toLowerCase()}`;
|
|
58
|
+
case 5:
|
|
59
|
+
return `${type}(${scope}) ${title[0].toUpperCase()}${title.substr(1).toLowerCase()}`;
|
|
60
|
+
case 6:
|
|
61
|
+
return `${type}(${scope}) ${title.toLowerCase()}`;
|
|
62
|
+
case 7:
|
|
63
|
+
return `${type}(${scope}): ${title[0].toUpperCase()}${title.substr(1).toLowerCase()}`;
|
|
64
|
+
case 8:
|
|
65
|
+
return `${type}(${scope}): ${title.toLowerCase()}`;
|
|
66
|
+
}
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
const handleCmdExec = (command) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
try {
|
|
71
|
+
const output = execSync(command);
|
|
72
|
+
return output.toString();
|
|
73
|
+
} catch (err) {
|
|
74
|
+
log(`Error\n${err.message ? err.message : err}`, 'error');
|
|
75
|
+
}
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
const log = (message, type) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
const date = gray(`[${new Date().toISOString()}]`);
|
|
80
|
+
let msg = `${bold('format-commit')}: ${message}`;
|
|
81
|
+
switch (type) {
|
|
82
|
+
case 'error':
|
|
83
|
+
msg = red(msg);
|
|
84
|
+
break;
|
|
85
|
+
case 'success':
|
|
86
|
+
msg = green(msg);
|
|
87
|
+
break;
|
|
88
|
+
case 'warning':
|
|
89
|
+
msg = yellow(msg);
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
console.log(`${date} ${type === 'error' ? red(msg) : (type === 'success' ? green(msg) : msg)}`);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const validBranchDescription = (description, maxLength) => {
|
|
96
|
+
if (description.length < 1) {
|
|
97
|
+
return 'Branch description cannot be empty';
|
|
98
|
+
}
|
|
99
|
+
if (description.length > maxLength) {
|
|
100
|
+
return `Branch description too long (max ${maxLength} characters)`;
|
|
101
|
+
}
|
|
102
|
+
const invalidChars = /[~^:?*[\\\s]/;
|
|
103
|
+
if (invalidChars.test(description)) {
|
|
104
|
+
return 'Branch description contains invalid characters (spaces, ~, ^, :, ?, *, [, \\)';
|
|
105
|
+
}
|
|
106
|
+
if (description.startsWith('.') || description.startsWith('-') ||
|
|
107
|
+
description.endsWith('.') || description.endsWith('-')) {
|
|
108
|
+
return 'Branch description cannot start or end with . or -';
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const formatBranchName = (type, description, format, scope = null) => {
|
|
114
|
+
const cleanDescription = description
|
|
115
|
+
.toLowerCase()
|
|
116
|
+
.replace(/\s+/g, '-')
|
|
117
|
+
.replace(/[^a-z0-9-]/g, '')
|
|
118
|
+
.replace(/-+/g, '-')
|
|
119
|
+
.replace(/^-|-$/g, '');
|
|
120
|
+
|
|
121
|
+
switch (format) {
|
|
122
|
+
case 1:
|
|
123
|
+
default:
|
|
124
|
+
return `${type}/${cleanDescription}`;
|
|
125
|
+
case 2:
|
|
126
|
+
return scope ? `${type}/${scope}/${cleanDescription}` : `${type}/${cleanDescription}`;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const checkBranchExists = (branchName) => {
|
|
131
|
+
try {
|
|
132
|
+
const localBranches = execSync('git branch --list').toString();
|
|
133
|
+
if (localBranches.includes(branchName)) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
const remoteBranches = execSync('git branch -r --list').toString();
|
|
137
|
+
if (remoteBranches.includes(`origin/${branchName}`)) {
|
|
138
|
+
return true;
|
|
91
139
|
}
|
|
92
|
-
|
|
140
|
+
return false;
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
93
144
|
};
|
|
94
145
|
|
|
95
146
|
|
|
96
147
|
module.exports = {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
148
|
+
askForVersion,
|
|
149
|
+
getCurrentBranch,
|
|
150
|
+
validCommitTitle,
|
|
151
|
+
validCommitTitleSetupLength,
|
|
152
|
+
validBranchDescription,
|
|
153
|
+
validVersion,
|
|
154
|
+
formatCommitTitle,
|
|
155
|
+
formatBranchName,
|
|
156
|
+
checkBranchExists,
|
|
157
|
+
handleCmdExec,
|
|
158
|
+
log,
|
|
105
159
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "format-commit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Lightweight CLI to standardize commit messages",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "Thomas BARKATS",
|
|
@@ -33,7 +33,6 @@
|
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"start": "node lib/index.js",
|
|
36
|
-
"test": "echo \"Error: no test specified\" && exit 1",
|
|
37
36
|
"lint": "eslint . --ext .js --fix",
|
|
38
37
|
"prepublishOnly": "npm run lint",
|
|
39
38
|
"preversion": "npm run lint",
|
|
@@ -47,11 +46,11 @@
|
|
|
47
46
|
},
|
|
48
47
|
"dependencies": {
|
|
49
48
|
"commander": "^14.0.0",
|
|
50
|
-
"kleur": "^4.1.
|
|
49
|
+
"kleur": "^4.1.5",
|
|
51
50
|
"prompts": "^2.4.2"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
54
|
-
"@eslint/js": "^9.
|
|
55
|
-
"eslint": "^9.
|
|
53
|
+
"@eslint/js": "^9.31.0",
|
|
54
|
+
"eslint": "^9.31.0"
|
|
56
55
|
}
|
|
57
56
|
}
|