format-commit 0.3.1 → 1.0.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 +48 -10
- package/lib/ai-service.js +272 -0
- package/lib/commit.js +419 -148
- package/lib/create-branch.js +108 -0
- package/lib/default-config.json +23 -18
- package/lib/env-utils.js +109 -0
- package/lib/index.js +47 -34
- package/lib/options.json +60 -29
- package/lib/setup.js +260 -110
- package/lib/utils.js +146 -75
- package/package.json +14 -10
package/lib/default-config.json
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
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": "ignore",
|
|
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
|
+
"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,50 +1,63 @@
|
|
|
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
|
|
|
13
23
|
program
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
.name('format-commit')
|
|
25
|
+
.description('CLI to standardize commit nomenclature')
|
|
26
|
+
.version('0.3.1')
|
|
27
|
+
.option('-b, --branch', 'create a new branch with standardized naming')
|
|
28
|
+
.option('-c, --config', 'generate a configuration file on your project for format-commit')
|
|
29
|
+
.option('-t, --test', 'start without finalize commit (for tests)');
|
|
19
30
|
|
|
20
31
|
try {
|
|
21
|
-
|
|
32
|
+
program.parse(process.argv);
|
|
22
33
|
} catch (error) {
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
console.error('Error parsing arguments:', error.message);
|
|
35
|
+
process.exit(1);
|
|
25
36
|
}
|
|
26
37
|
|
|
27
38
|
(async () => {
|
|
28
|
-
|
|
39
|
+
const opts = program.opts();
|
|
40
|
+
|
|
41
|
+
if (opts.config) {
|
|
42
|
+
await setup(false);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
29
45
|
|
|
30
|
-
|
|
31
|
-
|
|
46
|
+
// Get config from consumer package root, generate new config file if not found
|
|
47
|
+
fs.readFile(`./${options.configFile}.json`, async (err, data) => {
|
|
48
|
+
if (err) {
|
|
49
|
+
utils.log('no configuration found', 'warning');
|
|
50
|
+
const setupResult = await setup(true);
|
|
51
|
+
if (setupResult && setupResult.commitAfter) {
|
|
52
|
+
commit(setupResult.config, opts.test);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
if (opts.branch) {
|
|
56
|
+
const { default: createBranch } = await import('./create-branch.js');
|
|
57
|
+
createBranch(JSON.parse(data), opts.test);
|
|
32
58
|
return;
|
|
59
|
+
}
|
|
60
|
+
commit(JSON.parse(data), opts.test);
|
|
33
61
|
}
|
|
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
|
-
});
|
|
62
|
+
});
|
|
50
63
|
})();
|
package/lib/options.json
CHANGED
|
@@ -1,31 +1,62 @@
|
|
|
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": "ignore", "title": "never (ignore)" },
|
|
19
|
+
{ "value": "never", "title": "never (always ask)" },
|
|
20
|
+
{ "value": "releaseBranch", "title": "only on release branch" },
|
|
21
|
+
{ "value": "always" }
|
|
22
|
+
],
|
|
23
|
+
"versionTypes": [
|
|
24
|
+
{ "value": "patch" },
|
|
25
|
+
{ "value": "minor" },
|
|
26
|
+
{ "value": "major" },
|
|
27
|
+
{ "value": "custom", "title": "<custom>" }
|
|
28
|
+
],
|
|
29
|
+
"allVersionTypes": [
|
|
30
|
+
{ "value": "prepatch" },
|
|
31
|
+
{ "value": "preminor" },
|
|
32
|
+
{ "value": "premajor" },
|
|
33
|
+
{ "value": "prerelease", "title": "prerelease <tag>" },
|
|
34
|
+
{ "value": "from-git" }
|
|
35
|
+
],
|
|
36
|
+
"aiProviders": [
|
|
37
|
+
{
|
|
38
|
+
"value": "anthropic",
|
|
39
|
+
"title": "Anthropic (Claude)",
|
|
40
|
+
"models": [
|
|
41
|
+
{ "value": "claude-haiku-4-5", "title": "Claude Haiku 4.5 (Fast & Cheap)" },
|
|
42
|
+
{ "value": "claude-sonnet-4-5", "title": "Claude Sonnet 4.5 (Balanced)" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"value": "openai",
|
|
47
|
+
"title": "OpenAI (GPT)",
|
|
48
|
+
"models": [
|
|
49
|
+
{ "value": "gpt-4o-mini", "title": "GPT-4o Mini (Fast & Cheap)" },
|
|
50
|
+
{ "value": "gpt-4o", "title": "GPT-4o (More Capable)" }
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"value": "google",
|
|
55
|
+
"title": "Google (Gemini)",
|
|
56
|
+
"models": [
|
|
57
|
+
{ "value": "gemini-2.0-flash", "title": "Gemini 2.0 Flash (Fast & Free)" },
|
|
58
|
+
{ "value": "gemini-1.5-pro", "title": "Gemini 1.5 Pro (More Capable)" }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
]
|
|
31
62
|
}
|