format-commit 0.3.0 → 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 CHANGED
@@ -1,8 +1,13 @@
1
1
  # format-commit
2
2
 
3
- 🚀 Lightweight CLI for consistent commit message formatting.
3
+ [![npm version](https://badge.fury.io/js/format-commit.svg)](https://badge.fury.io/js/format-commit)
4
+ [![Node.js Version](https://img.shields.io/node/v/format-commit.svg)](https://nodejs.org/)
5
+ [![npm downloads](https://img.shields.io/npm/dm/format-commit.svg)](https://www.npmjs.com/package/format-commit)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
7
 
5
- Standardize your commit naming with basic rules, and guide your workflow through an automated script. No bloat, no complexity — just clean, consistent commits.
8
+ 🚀 Lightweight CLI for consistent Git workflow formatting.
9
+
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.
6
11
 
7
12
  ## Installation
8
13
 
@@ -15,22 +20,29 @@ npm i format-commit --save-dev
15
20
  Add to your `package.json` scripts:
16
21
  ```json
17
22
  "scripts": {
18
- "commit": "format-commit"
23
+ "commit": "format-commit",
24
+ "branch": "format-commit --branch"
19
25
  }
20
26
  ```
21
27
 
22
28
  Then use:
23
29
  ```sh
24
- npm run commit
30
+ npm run commit # to commit
31
+ npm run branch # to create a branch
25
32
  ```
26
33
 
34
+ ### Global Installation
35
+
27
36
  Or install globally:
28
37
  ```sh
29
38
  npm i -g format-commit
30
39
  format-commit
40
+ format-commit --branch
31
41
  ```
32
42
 
33
- On first use, format-commit will prompt you to configure your commit format and create a `commit-config.json` file.
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.
34
46
 
35
47
  To reconfigure later, run:
36
48
  ```sh
@@ -42,11 +54,23 @@ format-commit --config
42
54
  | Property | Description |
43
55
  | :------- | :---------- |
44
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` |
45
- | **types** | Allowed commit types (default: `feat`, `fix`, `core`, `test`, `config`, `doc`) |
46
- | **scopes** | Application scopes for commit categorization (formats 5-8 only) |
47
- | **minLength** | Minimum commit title length |
48
- | **maxLength** | Maximum commit title length |
49
- | **changeVersion** | Version change policy:<br>`always` - All commits require version change<br>`only on release branch` - Only release branch commits require version change<br>`never` - Always prompt for version change |
50
- | **releaseBranch** | Main/release branch name (used with `only on release branch`) |
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) |
60
+ | **minLength** | Minimum length required for the commit title |
61
+ | **maxLength** | Maximum length required for the commit title and branch description |
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 |
63
+ | **releaseBranch** | Main/release branch name (used if changeVersion = `only on release branch`) |
51
64
  | **showAllVersionTypes** | Show all version types or only main ones (`major`/`minor`/`patch`/`custom`) |
52
- | **stageAllChanges** | Auto-stage all changes before commit |
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 |
73
+
74
+ ## Contributing
75
+
76
+ Contributions are welcome! Feel free to open issues or submit pull requests.
package/lib/commit.js CHANGED
@@ -6,166 +6,162 @@ const options = require('./options.json');
6
6
 
7
7
 
8
8
  module.exports = async (config, testMode) => {
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
- initial: currentBranch === config.releaseBranch ? 1 : 2,
79
- },
80
- {
81
- type: prev => prev === 'custom' ? 'text' : null,
82
- name: 'customVersion',
83
- message: 'Version?',
84
- validate: val => utils.validVersion(val),
85
- },
86
- {
87
- type: 'confirm',
88
- name: 'pushAfterCommit',
89
- message: 'Push changes?',
90
- initial: false,
91
- },
92
- ], {
93
- onCancel: () => {
94
- cancelled = true;
95
- return false;
96
- },
97
- });
98
-
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') {
99
131
  /**
100
- * Handle prompt cancellation and stop commit execution
132
+ * Ask tag if new version is a prerelease and update it
101
133
  */
102
- if (cancelled) {
103
- utils.log('commit cancelled', 'error');
104
- return;
105
- }
106
-
107
- /**
108
- * Format changes message and commit it
109
- */
110
- utils.log('commit changes...');
111
- if (config.stageAllChanges) {
112
- utils.handleCmdExec('git add -A');
113
- }
114
- const commitTitle = utils.formatCommitTitle(
115
- commit.type,
116
- commit.title,
117
- config.format,
118
- commit.scope
119
- );
120
-
121
- if (testMode) {
122
- utils.log(commitTitle, 'warning');
123
- return;
124
- }
125
-
126
- const commitRes = utils.handleCmdExec(`git commit -m "${commitTitle}" -m "${commit.description}"`);
127
- if (!commitRes) {
128
- return;
129
- }
130
- utils.log('commit successfully completed', 'success');
131
- console.log(commitRes);
132
-
133
- let newVersion = null;
134
- if (commit.version === 'prerelease') {
135
- /**
136
- * Ask tag if new version is a prerelease and update it
137
- */
138
- const preRelease = await prompts([
139
- {
140
- type: 'text',
141
- name: 'tag',
142
- message: 'Pre-release tag?',
143
- },
144
- ]);
145
- utils.log('update version...');
146
- newVersion = utils.handleCmdExec(`npm version ${commit.version} --preid=${preRelease.tag}`);
147
-
148
- } else if (commit.version) {
149
- /**
150
- * Ask version if custom option selected and update it
151
- */
152
- utils.log('update version...');
153
- const version = commit.customVersion ? commit.customVersion : commit.version;
154
- newVersion = utils.handleCmdExec(`npm version ${version} --allow-same-version`);
155
- }
156
-
157
- if (newVersion) {
158
- utils.log(`package updated to ${newVersion}`);
159
- }
160
-
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) {
161
145
  /**
162
- * Push commit if option selection
146
+ * Ask version if custom option selected and update it
163
147
  */
164
- if (commit.pushAfterCommit) {
165
- utils.log('push changes...');
166
- const gitPush = utils.handleCmdExec(`git push -u origin ${currentBranch}`);
167
- console.log(gitPush);
168
- }
169
- const gitStatus = utils.handleCmdExec('git status');
170
- console.log(gitStatus);
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);
171
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
+ };
@@ -1,20 +1,20 @@
1
1
  {
2
- "format": 1,
3
- "minLength": 8,
4
- "maxLength": 80,
5
- "changeVersion": "never",
6
- "releaseBranch": "master",
7
- "showAllVersionTypes": false,
8
- "stageAllChanges": 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
- ]
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
- .name('format-commit')
15
- .description('CLI to standardize commit nomenclature')
16
- .version('0.3.0')
17
- .option('-c, --config', 'generate a configuration file on your project for format-commit')
18
- .option('-t, --test', 'start script without finalize commit (for tests)');
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
- program.parse(process.argv);
22
+ program.parse(process.argv);
22
23
  } catch (error) {
23
- console.error('Error parsing arguments:', error.message);
24
- process.exit(1);
24
+ console.error('Error parsing arguments:', error.message);
25
+ process.exit(1);
25
26
  }
26
27
 
27
28
  (async () => {
28
- const opts = program.opts();
29
+ const opts = program.opts();
29
30
 
30
- if (opts.config) {
31
- await setup(false);
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
- "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
- "versionChangeMode": [
14
- { "value": "never", "title": "never (always ask)" },
15
- { "value": "releaseBranch", "title": "only on release branch" },
16
- { "value": "always" }
17
- ],
18
- "versionTypes": [
19
- { "value": "patch" },
20
- { "value": "minor" },
21
- { "value": "major" },
22
- { "value": "custom", "title": "<custom>" }
23
- ],
24
- "allVersionTypes": [
25
- { "value": "prepatch" },
26
- { "value": "preminor" },
27
- { "value": "premajor" },
28
- { "value": "prerelease", "title": "prerelease <tag>" },
29
- { "value": "from-git" }
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
- utils.log('create config file');
11
+ utils.log('create config file');
12
12
 
13
- /**
14
- * Get current git branch to pre-fill release branch option
15
- */
16
- const currentBranch = utils.getCurrentBranch();
13
+ /**
14
+ * Get current git branch to pre-fill release branch option
15
+ */
16
+ const currentBranch = utils.getCurrentBranch();
17
17
 
18
- let cancelled = false;
19
- const configChoices = await prompts([
20
- {
21
- type: 'select',
22
- name: 'format',
23
- message: 'Commit format',
24
- choices: options.commitFormats,
25
- },
26
- {
27
- type: 'number',
28
- name: 'minLength',
29
- message: 'Commit minimum length?',
30
- validate: val => utils.validCommitTitleSetupLength(val),
31
- initial: defaultConfig.minLength,
32
- },
33
- {
34
- type: 'number',
35
- name: 'maxLength',
36
- message: 'Commit maximum length?',
37
- validate: val => utils.validCommitTitleSetupLength(val),
38
- initial: defaultConfig.maxLength,
39
- },
40
- {
41
- type: 'confirm',
42
- name: 'stageAllChanges',
43
- message: 'Stage all changes before each commit?',
44
- initial: defaultConfig.stageAllChanges,
45
- },
46
- {
47
- type: 'select',
48
- name: 'changeVersion',
49
- message: 'Change package version',
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
- });
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
- * Handle prompt cancellation and stop setup execution
79
- */
80
- if (cancelled) {
81
- utils.log('setup cancelled', 'error');
82
- return;
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
- * Parse prompt data and write config file
87
- */
88
- const config = {
89
- format: configChoices.format,
90
- types: defaultConfig.types,
91
- scopes: configChoices.format >= 5
92
- ? defaultConfig.scopes
93
- : undefined,
94
- minLength: configChoices.minLength,
95
- maxLength: configChoices.maxLength,
96
- changeVersion: configChoices.changeVersion,
97
- releaseBranch: configChoices.releaseBranch,
98
- showAllVersionTypes: configChoices.showAllVersionTypes,
99
- stageAllChanges: configChoices.stageAllChanges,
100
- };
101
- const parsedConfig = JSON.stringify(config, null, 2);
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
- utils.log(`save ${options.configFile}.json file...`);
103
+ utils.log(`save ${options.configFile}.json file...`);
104
104
 
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
- }
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
- return {
114
- config,
115
- commitAfter: configChoices.commitAfter
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
- if (
9
- config.changeVersion === 'always'
10
- || (config.changeVersion === 'releaseBranch' && branch === config.releaseBranch)
11
- ) {
12
- return true;
13
- }
14
- return false;
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
- return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
18
+ return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
19
19
  };
20
20
 
21
21
  const validCommitTitle = (title, lenMin, lenMax) => {
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;
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
- 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;
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
- 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;
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
- 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
- }
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
- try {
71
- const output = execSync(command);
72
- return output.toString();
73
- } catch (err) {
74
- log(`Error\n${err.message ? err.message : err}`, 'error');
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
- 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;
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
- console.log(`${date} ${type === 'error' ? red(msg) : (type === 'success' ? green(msg) : msg)}`);
140
+ return false;
141
+ } catch {
142
+ return false;
143
+ }
93
144
  };
94
145
 
95
146
 
96
147
  module.exports = {
97
- askForVersion,
98
- getCurrentBranch,
99
- validCommitTitle,
100
- validCommitTitleSetupLength,
101
- validVersion,
102
- formatCommitTitle,
103
- handleCmdExec,
104
- log,
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.0",
3
+ "version": "0.4.0",
4
4
  "description": "Lightweight CLI to standardize commit messages",
5
5
  "license": "ISC",
6
6
  "author": "Thomas BARKATS",
@@ -28,9 +28,11 @@
28
28
  },
29
29
  "main": "./lib/index.js",
30
30
  "type": "commonjs",
31
+ "engines": {
32
+ "node": ">=16.0.0"
33
+ },
31
34
  "scripts": {
32
35
  "start": "node lib/index.js",
33
- "test": "echo \"Error: no test specified\" && exit 1",
34
36
  "lint": "eslint . --ext .js --fix",
35
37
  "prepublishOnly": "npm run lint",
36
38
  "preversion": "npm run lint",
@@ -44,11 +46,11 @@
44
46
  },
45
47
  "dependencies": {
46
48
  "commander": "^14.0.0",
47
- "kleur": "^4.1.4",
49
+ "kleur": "^4.1.5",
48
50
  "prompts": "^2.4.2"
49
51
  },
50
52
  "devDependencies": {
51
- "@eslint/js": "^9.27.0",
52
- "eslint": "^9.27.0"
53
+ "@eslint/js": "^9.31.0",
54
+ "eslint": "^9.31.0"
53
55
  }
54
56
  }