cloving 0.1.14 → 0.1.16
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 +69 -20
- package/dist/cloving_gpt/adapters/openai.js +1 -0
- package/dist/commands/generate/context.js +27 -13
- package/dist/commands/generate/unit_tests.js +133 -28
- package/dist/index.js +2 -0
- package/dist/package.json +1 -1
- package/dist/utils/git_utils.js +3 -1
- package/dist/utils/string_utils.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
|
-
# Cloving
|
|
1
|
+
# Cloving - CLI Code Generation with AI
|
|
2
2
|
|
|
3
|
-
Cloving is
|
|
3
|
+
Cloving CLI is a command line utility which integrates AI into your development workflow. It provides AI-powered code generation, commit message generation, code reviews, unit test generation, and more.
|
|
4
|
+
|
|
5
|
+
What makes the Cloving CLI different from other AI tools is that it builds sophisticated context-aware prompts to generate code, commit messages, and more.
|
|
6
|
+
|
|
7
|
+
Cloving gets to know your specific projects, the frameworks you use, the coding style you prefer, and the patterns you follow, and does its best to generate code that fits your existing project so that it actually works.
|
|
8
|
+
|
|
9
|
+
It delivers usable code that you can save directly into your project instead of copying and pasting code snippets from chat interactions.
|
|
10
|
+
|
|
11
|
+
**Read the [introduction announcement](https://cloving.ai/tutorials/introducing-the-cloving-cli).**
|
|
12
|
+
|
|
13
|
+
> [!WARNING]
|
|
14
|
+
>
|
|
15
|
+
> This project is still in the early stages of development and is not yet stable.
|
|
16
|
+
> Please use it with caution and [report any issues](https://github.com/cloving-ai/cloving-cli/issues) you encounter
|
|
4
17
|
|
|
5
18
|
## Features
|
|
6
19
|
|
|
20
|
+
- **Context-aware AI code generation**
|
|
7
21
|
- **AI-powered Git commit message generation**
|
|
8
22
|
- **Automated code reviews with AI insights**
|
|
9
23
|
- **AI-assisted unit test generation**
|
|
10
|
-
- **Seamless integration with existing development workflows**
|
|
11
|
-
- **Customizable AI prompts for different tasks**
|
|
12
|
-
- **Project initialization, planning, and building with AI assistance**
|
|
13
24
|
- **Support for multiple AI models including OpenAI, Claude, and Ollama**
|
|
14
25
|
|
|
26
|
+
## Important
|
|
27
|
+
|
|
28
|
+
The cloving will ask you before submitting any prompt to the AI model. You can use the `-s` or `--silent` flag to run the command without asking for confirmation, but by default you will have the chance to review all prompts with all the context before submitting them. This is to ensure that you are aware of what is being sent to the AI model and can make any necessary changes before submitting the prompt.
|
|
29
|
+
|
|
15
30
|
## Prerequisites
|
|
16
31
|
|
|
17
32
|
- Node.js (version 20 or higher)
|
|
@@ -91,13 +106,6 @@ Options:
|
|
|
91
106
|
- `-s, --silent`: Run without asking for confirmation of submitting prompts
|
|
92
107
|
- `-m, --model <model>`: Select the model to use
|
|
93
108
|
|
|
94
|
-
### Listing Available Models
|
|
95
|
-
|
|
96
|
-
List the available AI models:
|
|
97
|
-
```bash
|
|
98
|
-
cloving models
|
|
99
|
-
```
|
|
100
|
-
|
|
101
109
|
### Generating Commit Messages
|
|
102
110
|
|
|
103
111
|
Generate an AI-powered commit message:
|
|
@@ -112,25 +120,63 @@ Options:
|
|
|
112
120
|
- `-s, --silent`: Run without asking for confirmation
|
|
113
121
|
- `-m, --model <model>`: Select the model to use
|
|
114
122
|
|
|
115
|
-
###
|
|
123
|
+
### Code Reviews
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
Get an AI-powered code review:
|
|
118
126
|
```bash
|
|
119
|
-
cloving generate
|
|
127
|
+
cloving generate review
|
|
120
128
|
```
|
|
121
129
|
Options:
|
|
122
130
|
- `-s, --silent`: Run without asking for confirmation
|
|
123
|
-
- `-f, --files <filenames...>`: Specify filenames for the unit tests
|
|
124
131
|
- `-m, --model <model>`: Select the model to use
|
|
125
132
|
|
|
126
|
-
### Code
|
|
133
|
+
### Generating Code
|
|
127
134
|
|
|
128
|
-
|
|
135
|
+
Generate code based on a prompt:
|
|
129
136
|
```bash
|
|
130
|
-
cloving generate
|
|
137
|
+
cloving generate code
|
|
138
|
+
```
|
|
139
|
+
Options:
|
|
140
|
+
- `--save`: Automatically save all files after generating the code
|
|
141
|
+
- `-i, --interactive`: Auto-save generated code and then show a new prompt with existing context to revise the code further
|
|
142
|
+
- `-s, --silent`: Run the command without asking for confirmation of submitting prompts
|
|
143
|
+
- `-m, --model <model>`: Select the model to use
|
|
144
|
+
- `-p, --prompt <prompt>`: Specify the prompt to use
|
|
145
|
+
- `-f, --files <filenames...>`: Specify filenames of files with context to use for generating code
|
|
146
|
+
|
|
147
|
+
### Generating Shell Commands
|
|
148
|
+
|
|
149
|
+
Generate a shell command based on a prompt:
|
|
150
|
+
```bash
|
|
151
|
+
cloving generate shell
|
|
152
|
+
```
|
|
153
|
+
or
|
|
154
|
+
```bash
|
|
155
|
+
cloving generate sh
|
|
156
|
+
```
|
|
157
|
+
Options:
|
|
158
|
+
- `-s, --silent`: Run without asking for confirmation of submitting prompts
|
|
159
|
+
- `-m, --model <model>`: Select the model to use
|
|
160
|
+
- `-p, --prompt <prompt>`: Specify the prompt to use
|
|
161
|
+
|
|
162
|
+
### Generating Context
|
|
163
|
+
|
|
164
|
+
Create a context string for generating prompts outside of the cloving cli:
|
|
165
|
+
```bash
|
|
166
|
+
cloving generate context
|
|
167
|
+
```
|
|
168
|
+
Options:
|
|
169
|
+
- `-f, --files <filenames...>`: Specify filenames of files with context
|
|
170
|
+
|
|
171
|
+
### Generating Unit Tests
|
|
172
|
+
|
|
173
|
+
Generate unit tests for changes or specific files:
|
|
174
|
+
```bash
|
|
175
|
+
cloving generate unit-tests
|
|
131
176
|
```
|
|
132
177
|
Options:
|
|
133
178
|
- `-s, --silent`: Run without asking for confirmation
|
|
179
|
+
- `-f, --files <filenames...>`: Specify filenames for the unit tests
|
|
134
180
|
- `-m, --model <model>`: Select the model to use
|
|
135
181
|
|
|
136
182
|
### Project Commands
|
|
@@ -159,7 +205,7 @@ Cloving now includes commands for managing entire projects:
|
|
|
159
205
|
|
|
160
206
|
## Command Line Options
|
|
161
207
|
|
|
162
|
-
Usage: `cloving [
|
|
208
|
+
Usage: `cloving [command] [options]`
|
|
163
209
|
|
|
164
210
|
Options:
|
|
165
211
|
- `-V, --version`: Output the version number
|
|
@@ -172,6 +218,9 @@ Commands:
|
|
|
172
218
|
- `commit`: Alias for cloving generate commit
|
|
173
219
|
- `generate`: Generate various items like unit-tests and code reviews
|
|
174
220
|
- `commit`: Generate a commit message and commit the changes
|
|
221
|
+
- `code`: Generate code based on a prompt
|
|
222
|
+
- `context`: Generate context for building a prompt outside of cloving
|
|
223
|
+
- `shell`: Generate a shell command based on a prompt
|
|
175
224
|
- `unit-tests`: Generate unit tests
|
|
176
225
|
- `review`: Review the code for committed changes
|
|
177
226
|
- `project`: Commands for managing Cloving projects
|
|
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
7
7
|
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
8
8
|
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
9
11
|
const command_utils_1 = require("../../utils/command_utils");
|
|
10
12
|
const config_utils_1 = require("../../utils/config_utils");
|
|
11
13
|
const generateContextPrompt = (files, contextFiles) => {
|
|
@@ -25,24 +27,36 @@ ${contextFileContents}
|
|
|
25
27
|
${files.join('\n')}`;
|
|
26
28
|
};
|
|
27
29
|
const context = async (options) => {
|
|
30
|
+
let { files } = options;
|
|
28
31
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
29
32
|
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
30
33
|
let contextFiles = {};
|
|
31
34
|
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
]);
|
|
41
|
-
if (contextFile) {
|
|
42
|
-
contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
|
|
35
|
+
if (files) {
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const filePath = path_1.default.resolve(file);
|
|
38
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
39
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
40
|
+
contextFiles[file] = content;
|
|
41
|
+
}
|
|
43
42
|
}
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
let includeMoreFiles = true;
|
|
46
|
+
while (includeMoreFiles) {
|
|
47
|
+
const { contextFile } = await inquirer_1.default.prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'contextFile',
|
|
51
|
+
message: `Enter the relative path of a file or directory you would like to include as context (or press enter to continue):`,
|
|
52
|
+
},
|
|
53
|
+
]);
|
|
54
|
+
if (contextFile) {
|
|
55
|
+
contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, contextFiles, options);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
includeMoreFiles = false;
|
|
59
|
+
}
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
const contextPrompt = generateContextPrompt(allSrcFiles, contextFiles);
|
|
@@ -23,7 +23,10 @@ const extractChangedFiles = (gitDiff) => {
|
|
|
23
23
|
};
|
|
24
24
|
const generatePrompt = (files, srcFiles, testFiles, gitDiff) => {
|
|
25
25
|
const filesList = files.length > 0 ? files.join('\n') : 'No files provided';
|
|
26
|
-
return
|
|
26
|
+
return `Enumerate all the files in the provided list${gitDiff ? ' and git diff' : ''} as well as the file names of anything that these files interact with.
|
|
27
|
+
Also, list any test files that might be relevant to these files.
|
|
28
|
+
|
|
29
|
+
### List of Code Files
|
|
27
30
|
|
|
28
31
|
${filesList}
|
|
29
32
|
|
|
@@ -35,13 +38,13 @@ ${srcFiles}
|
|
|
35
38
|
|
|
36
39
|
${testFiles}
|
|
37
40
|
|
|
38
|
-
### Git Diff Content
|
|
41
|
+
${gitDiff ? '### Git Diff Content' : ''}
|
|
39
42
|
|
|
40
|
-
${gitDiff}
|
|
43
|
+
${gitDiff || ''}
|
|
41
44
|
|
|
42
45
|
## Example Output
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
Here are the files that are relevant:
|
|
45
48
|
|
|
46
49
|
\`\`\`plaintext
|
|
47
50
|
## files
|
|
@@ -55,14 +58,28 @@ test/models/foo_test.rb
|
|
|
55
58
|
test/controllers/baz_controller_test.rb
|
|
56
59
|
\`\`\`
|
|
57
60
|
|
|
58
|
-
###
|
|
59
|
-
|
|
60
|
-
Please enumerate all the files in the provided list${gitDiff ? ' and git diff' : ''} as well as the file names of anything that these files interact with.
|
|
61
|
+
### Prompt
|
|
61
62
|
|
|
63
|
+
Enumerate all the files in the provided list${gitDiff ? ' and git diff' : ''} as well as the file names of anything that these files interact with.
|
|
62
64
|
Also, list any test files that might be relevant to these files.`;
|
|
63
65
|
};
|
|
64
|
-
const handleUserAction = async (analysis) => {
|
|
66
|
+
const handleUserAction = async (analysis, autoSave = false) => {
|
|
65
67
|
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(analysis);
|
|
68
|
+
if (autoSave) {
|
|
69
|
+
for (const file of files) {
|
|
70
|
+
if (fileContents[file]) {
|
|
71
|
+
const filePath = path_1.default.resolve(file);
|
|
72
|
+
await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
73
|
+
await fs_1.promises.writeFile(filePath, fileContents[file]);
|
|
74
|
+
console.log(`${file} has been saved.`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
console.log(`File content not found for ${file}.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
console.log('All unit test files have been saved.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
66
83
|
const { action } = await inquirer_1.default.prompt([
|
|
67
84
|
{
|
|
68
85
|
type: 'list',
|
|
@@ -91,10 +108,9 @@ const handleUserAction = async (analysis) => {
|
|
|
91
108
|
]);
|
|
92
109
|
if (fileContents[fileToSave]) {
|
|
93
110
|
const filePath = path_1.default.resolve(fileToSave);
|
|
94
|
-
fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true })
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
.catch((error) => console.error(`Error saving ${fileToSave}:`, error));
|
|
111
|
+
await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
112
|
+
await fs_1.promises.writeFile(filePath, fileContents[fileToSave]);
|
|
113
|
+
console.log(`${fileToSave} has been saved.`);
|
|
98
114
|
}
|
|
99
115
|
else {
|
|
100
116
|
console.log('File content not found.');
|
|
@@ -114,10 +130,9 @@ const handleUserAction = async (analysis) => {
|
|
|
114
130
|
for (const file of files) {
|
|
115
131
|
if (fileContents[file]) {
|
|
116
132
|
const filePath = path_1.default.resolve(file);
|
|
117
|
-
fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true })
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.catch((error) => console.error(`Error saving ${file}:`, error));
|
|
133
|
+
await fs_1.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
134
|
+
await fs_1.promises.writeFile(filePath, fileContents[file]);
|
|
135
|
+
console.log(`${file} has been saved.`);
|
|
121
136
|
}
|
|
122
137
|
else {
|
|
123
138
|
console.log(`File content not found for ${file}.`);
|
|
@@ -157,7 +172,7 @@ const handleUserAction = async (analysis) => {
|
|
|
157
172
|
}
|
|
158
173
|
};
|
|
159
174
|
const unitTests = async (options) => {
|
|
160
|
-
const { files } = options;
|
|
175
|
+
const { files, save } = options;
|
|
161
176
|
const gpt = new cloving_gpt_1.default(options);
|
|
162
177
|
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, true);
|
|
163
178
|
const testingDirectory = (0, config_utils_1.getTestingDirectory)();
|
|
@@ -168,15 +183,22 @@ const unitTests = async (options) => {
|
|
|
168
183
|
const testFiles = (0, child_process_1.execFileSync)('find', [testingDirectory, '-type', 'f']).toString().trim();
|
|
169
184
|
let contextFiles;
|
|
170
185
|
if (files) {
|
|
186
|
+
console.log('Generating unit tests for the provided files...');
|
|
187
|
+
console.log('Gathering a list of files related to the changes...');
|
|
171
188
|
const prompt = generatePrompt(files, allSrcFiles.join('\n'), testFiles);
|
|
172
189
|
contextFiles = await gpt.generateText({ prompt });
|
|
173
190
|
}
|
|
174
191
|
else {
|
|
192
|
+
console.log('Generating unit tests for the git diff between this branch and the default branch...');
|
|
193
|
+
console.log('Gathering a list of files related to the changes...');
|
|
175
194
|
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
176
195
|
const changedFiles = extractChangedFiles(gitDiff);
|
|
177
196
|
const prompt = generatePrompt(changedFiles, allSrcFiles.join('\n'), testFiles, gitDiff);
|
|
178
197
|
contextFiles = await gpt.generateText({ prompt });
|
|
179
198
|
}
|
|
199
|
+
contextFiles = (0, string_utils_1.extractMarkdown)(contextFiles);
|
|
200
|
+
console.log(contextFiles);
|
|
201
|
+
console.log('\nBuilding the tests for these files...');
|
|
180
202
|
// Initialize variables
|
|
181
203
|
const lines = [];
|
|
182
204
|
const context = [];
|
|
@@ -189,38 +211,121 @@ const unitTests = async (options) => {
|
|
|
189
211
|
for (const codeFile of lines) {
|
|
190
212
|
if (await fs_1.promises.stat(codeFile).then(stat => stat.isFile()).catch(() => false)) {
|
|
191
213
|
const fileContents = await fs_1.promises.readFile(codeFile, 'utf-8');
|
|
192
|
-
context.push(`### Contents of
|
|
214
|
+
context.push(`### Contents of **${codeFile}\n\n${fileContents.split('\n').map(line => ` ${line}`).join('\n')}**\n\n`);
|
|
193
215
|
}
|
|
194
216
|
}
|
|
195
217
|
// Generate the message for unit test creation
|
|
196
218
|
let analysis = '';
|
|
197
219
|
if ((files || []).length > 0) {
|
|
198
|
-
const message = `
|
|
220
|
+
const message = `Create unit tests for the List of Files. Always show filenames for the generated code.
|
|
199
221
|
|
|
200
|
-
##
|
|
222
|
+
## List of Files
|
|
201
223
|
${(files || []).join('\n')}
|
|
202
|
-
|
|
224
|
+
|
|
225
|
+
## Example Response Structure
|
|
226
|
+
|
|
227
|
+
Here are the unit tests for the provided list of files:
|
|
228
|
+
|
|
229
|
+
1. **tests/utils/model_utils.test.ts**
|
|
230
|
+
|
|
231
|
+
\`\`\`typescript
|
|
232
|
+
import { getModel } from '../../src/utils/model_utils'
|
|
233
|
+
|
|
234
|
+
describe('modelUtils', () => {
|
|
235
|
+
const originalEnv = process.env
|
|
236
|
+
|
|
237
|
+
beforeEach(() => {
|
|
238
|
+
jest.resetModules()
|
|
239
|
+
process.env = { ...originalEnv }
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
afterAll(() => {
|
|
243
|
+
process.env = originalEnv
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('getModel should return default model when CLOVING_MODEL is not set', () => {
|
|
247
|
+
delete process.env.CLOVING_MODEL
|
|
248
|
+
const result = getModel()
|
|
249
|
+
expect(result).toBe('claude:claude-3-5-sonnet-20240620')
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
test('getModel should return CLOVING_MODEL when it is set', () => {
|
|
253
|
+
process.env.CLOVING_MODEL = 'openai:gpt-4'
|
|
254
|
+
const result = getModel()
|
|
255
|
+
expect(result).toBe('openai:gpt-4')
|
|
256
|
+
})
|
|
257
|
+
})
|
|
258
|
+
\`\`\`
|
|
203
259
|
|
|
204
260
|
## Context
|
|
205
|
-
|
|
261
|
+
|
|
262
|
+
${context.join('\n\n')}
|
|
263
|
+
|
|
264
|
+
## Prompt
|
|
265
|
+
|
|
266
|
+
Create unit tests for the List of Files. Always show filenames for the generated code.`;
|
|
206
267
|
// Get the model and analysis using ClovingGPT
|
|
207
268
|
analysis = await gpt.generateText({ prompt: message });
|
|
208
269
|
}
|
|
209
270
|
else {
|
|
210
|
-
const message = `
|
|
271
|
+
const message = `Create unit tests for the Code Diff. Always show filenames for the generated code.
|
|
272
|
+
|
|
273
|
+
## Code Diff
|
|
211
274
|
|
|
212
|
-
## Begin diff
|
|
213
275
|
${await (0, git_utils_1.getGitDiff)()}
|
|
214
|
-
## End diff
|
|
215
276
|
|
|
216
277
|
## Context
|
|
217
278
|
|
|
218
|
-
${context.join('\n\n')}
|
|
279
|
+
${context.join('\n\n')}
|
|
280
|
+
|
|
281
|
+
## Example Response Structure
|
|
282
|
+
|
|
283
|
+
Here are the unit tests for the code diff:
|
|
284
|
+
|
|
285
|
+
1. **tests/utils/model_utils.test.ts**
|
|
286
|
+
|
|
287
|
+
\`\`\`typescript
|
|
288
|
+
import { getModel } from '../../src/utils/model_utils'
|
|
289
|
+
|
|
290
|
+
describe('modelUtils', () => {
|
|
291
|
+
const originalEnv = process.env
|
|
292
|
+
|
|
293
|
+
beforeEach(() => {
|
|
294
|
+
jest.resetModules()
|
|
295
|
+
process.env = { ...originalEnv }
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
afterAll(() => {
|
|
299
|
+
process.env = originalEnv
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test('getModel should return default model when CLOVING_MODEL is not set', () => {
|
|
303
|
+
delete process.env.CLOVING_MODEL
|
|
304
|
+
const result = getModel()
|
|
305
|
+
expect(result).toBe('claude:claude-3-5-sonnet-20240620')
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
test('getModel should return CLOVING_MODEL when it is set', () => {
|
|
309
|
+
process.env.CLOVING_MODEL = 'openai:gpt-4'
|
|
310
|
+
const result = getModel()
|
|
311
|
+
expect(result).toBe('openai:gpt-4')
|
|
312
|
+
})
|
|
313
|
+
})
|
|
314
|
+
\`\`\`
|
|
315
|
+
|
|
316
|
+
## Prompt
|
|
317
|
+
|
|
318
|
+
Create unit tests for the Code Diff. Always show filenames for the generated code.`;
|
|
219
319
|
// Get the model and analysis using ClovingGPT
|
|
220
320
|
analysis = await gpt.generateText({ prompt: message });
|
|
221
321
|
}
|
|
222
|
-
|
|
322
|
+
if (analysis) {
|
|
323
|
+
console.log((0, cli_highlight_1.default)(analysis));
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
console.log('No unit tests generated.');
|
|
327
|
+
}
|
|
223
328
|
// Add the new user action handling
|
|
224
|
-
await handleUserAction(analysis);
|
|
329
|
+
await handleUserAction(analysis, save);
|
|
225
330
|
};
|
|
226
331
|
exports.default = unitTests;
|
package/dist/index.js
CHANGED
|
@@ -86,11 +86,13 @@ generate
|
|
|
86
86
|
generate
|
|
87
87
|
.command('context')
|
|
88
88
|
.description('Create a context string for generating code')
|
|
89
|
+
.option('-f, --files <filenames...>', 'Specify filenames of files with context')
|
|
89
90
|
.action(context_1.default);
|
|
90
91
|
generate
|
|
91
92
|
.command('unit-tests')
|
|
92
93
|
.alias('u')
|
|
93
94
|
.description('Generate unit tests (if you don\'t specify filenames, it will generate tests for commited changes that differ from the main/master branch)')
|
|
95
|
+
.option('--save', 'Automatically save all files after generating the unit tests')
|
|
94
96
|
.option('-s, --silent', 'Run the command without asking for confirmation of submitting prompts')
|
|
95
97
|
.option('-f, --files <filenames...>', 'Specify filenames for the unit tests')
|
|
96
98
|
.option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
|
package/dist/package.json
CHANGED
package/dist/utils/git_utils.js
CHANGED
|
@@ -20,7 +20,9 @@ const getGitDiff = async () => {
|
|
|
20
20
|
try {
|
|
21
21
|
const defaultBranchName = await (0, exports.getDefaultBranchName)();
|
|
22
22
|
const gitDiff = (0, child_process_1.execSync)(`git diff ${defaultBranchName} --`).toString().trim();
|
|
23
|
-
|
|
23
|
+
// Indent each line by four spaces
|
|
24
|
+
const indentedDiff = gitDiff.split('\n').map(line => ` ${line}`).join('\n');
|
|
25
|
+
return indentedDiff;
|
|
24
26
|
}
|
|
25
27
|
catch (error) {
|
|
26
28
|
console.error('Error getting git diff:', error.message);
|
|
@@ -92,6 +92,8 @@ const parseMarkdownInstructions = (input) => {
|
|
|
92
92
|
};
|
|
93
93
|
exports.parseMarkdownInstructions = parseMarkdownInstructions;
|
|
94
94
|
const extractFilesAndContent = (rawCodeCommand) => {
|
|
95
|
+
if (!rawCodeCommand)
|
|
96
|
+
return [[], {}];
|
|
95
97
|
const files = [];
|
|
96
98
|
const fileContents = {};
|
|
97
99
|
const matches = rawCodeCommand.match(/(\*{2})([^\*]+)(\*{2})/g);
|
|
@@ -107,7 +109,7 @@ const extractFilesAndContent = (rawCodeCommand) => {
|
|
|
107
109
|
let content = contentMatch[1];
|
|
108
110
|
// Remove the first word after the opening triple backticks
|
|
109
111
|
content = content.split('\n').map((line, idx) => idx === 0 ? line.replace(/^\w+\s*/, '') : line).join('\n');
|
|
110
|
-
fileContents[fileName] = content;
|
|
112
|
+
fileContents[fileName] = content.trim();
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
return [files, fileContents];
|