cloving 0.1.23 → 0.2.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 +40 -29
- package/dist/cloving_gpt/index.js +3 -2
- package/dist/commands/generate/code.js +14 -5
- package/dist/commands/generate/review.js +3 -194
- package/dist/commands/generate/unit_tests.js +2 -1
- package/dist/commands/init.js +2 -5
- package/dist/index.js +0 -29
- package/dist/managers/ChatManager.js +19 -3
- package/dist/managers/CodeManager.js +12 -46
- package/dist/managers/ReviewManager.js +208 -0
- package/dist/package.json +4 -1
- package/dist/utils/string_utils.js +47 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -159,6 +159,43 @@ Options:
|
|
|
159
159
|
- `-m, --model <model>`: Select the model to use
|
|
160
160
|
- `-p, --prompt <prompt>`: Specify the prompt to use
|
|
161
161
|
|
|
162
|
+
### Interactive Chat
|
|
163
|
+
|
|
164
|
+
Start an interactive chat session with the AI:
|
|
165
|
+
```bash
|
|
166
|
+
cloving chat
|
|
167
|
+
```
|
|
168
|
+
Options:
|
|
169
|
+
- `-f, --files <filenames...>`: Specify filenames of files with context to use for the chat
|
|
170
|
+
- `-t, --temperature <temperature>`: Set the temperature for the model (default 0.2)
|
|
171
|
+
- `-m, --model <model>`: Select the model to use
|
|
172
|
+
|
|
173
|
+
### Proxy Server
|
|
174
|
+
|
|
175
|
+
Start a proxy server to use with Cloving:
|
|
176
|
+
```bash
|
|
177
|
+
cloving proxy
|
|
178
|
+
```
|
|
179
|
+
Options:
|
|
180
|
+
- `-f, --files <filenames...>`: Specify filenames of files with context to use for generating code
|
|
181
|
+
- `-t, --temperature <temperature>`: Set the temperature for the model (default 0.2)
|
|
182
|
+
- `-m, --model <model>`: Select the model to use
|
|
183
|
+
|
|
184
|
+
The proxy server allows you to integrate Cloving with other tools or applications. It starts a local server that can receive requests and forward them to the AI model, returning the responses. This is useful for creating custom integrations or using Cloving in scenarios where a direct CLI interaction isn't suitable.
|
|
185
|
+
|
|
186
|
+
### Token Estimation
|
|
187
|
+
|
|
188
|
+
Estimate the number of tokens in the current working directory or specified files:
|
|
189
|
+
```bash
|
|
190
|
+
cloving tokens
|
|
191
|
+
```
|
|
192
|
+
or
|
|
193
|
+
```bash
|
|
194
|
+
cloving t
|
|
195
|
+
```
|
|
196
|
+
Options:
|
|
197
|
+
- `-f, --files <filenames...>`: Specify filenames of files to estimate tokens for
|
|
198
|
+
|
|
162
199
|
### Generating Context
|
|
163
200
|
|
|
164
201
|
Create a context string for generating prompts outside of the cloving cli:
|
|
@@ -179,30 +216,6 @@ Options:
|
|
|
179
216
|
- `-f, --files <filenames...>`: Specify filenames for the unit tests
|
|
180
217
|
- `-m, --model <model>`: Select the model to use
|
|
181
218
|
|
|
182
|
-
### Project Commands
|
|
183
|
-
|
|
184
|
-
Cloving now includes commands for managing entire projects:
|
|
185
|
-
|
|
186
|
-
- Initialize a new project inside a git branch:
|
|
187
|
-
```bash
|
|
188
|
-
cloving project init
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
- Plan the steps to complete the project:
|
|
192
|
-
```bash
|
|
193
|
-
cloving project plan
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
- Generate code to build the project:
|
|
197
|
-
```bash
|
|
198
|
-
cloving project build
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
- Clean up and finalize the project:
|
|
202
|
-
```bash
|
|
203
|
-
cloving project complete
|
|
204
|
-
```
|
|
205
|
-
|
|
206
219
|
## Command Line Options
|
|
207
220
|
|
|
208
221
|
Usage: `cloving [command] [options]`
|
|
@@ -214,6 +227,9 @@ Options:
|
|
|
214
227
|
Commands:
|
|
215
228
|
- `config`: Configure Cloving with your API key and models to use
|
|
216
229
|
- `init`: Setup Cloving in the current directory
|
|
230
|
+
- `chat`: Start an interactive chat with Cloving
|
|
231
|
+
- `tokens`: Estimate the number of tokens in the current working directory or specified files
|
|
232
|
+
- `proxy`: Start a proxy server to use with Cloving
|
|
217
233
|
- `models`: List available models
|
|
218
234
|
- `commit`: Alias for cloving generate commit
|
|
219
235
|
- `generate`: Generate various items like unit-tests and code reviews
|
|
@@ -223,11 +239,6 @@ Commands:
|
|
|
223
239
|
- `shell`: Generate a shell command based on a prompt
|
|
224
240
|
- `unit-tests`: Generate unit tests
|
|
225
241
|
- `review`: Review the code for committed changes
|
|
226
|
-
- `project`: Commands for managing Cloving projects
|
|
227
|
-
- `init`: Setup a new Cloving project inside a git branch
|
|
228
|
-
- `plan`: Plan the steps to complete the project
|
|
229
|
-
- `build`: Generate code to build the project
|
|
230
|
-
- `complete`: Clean up and finalize the project
|
|
231
242
|
|
|
232
243
|
Most commands support the following options:
|
|
233
244
|
- `-s, --silent`: Run the command without asking for confirmation of submitting prompts
|
|
@@ -149,12 +149,13 @@ class ClovingGPT {
|
|
|
149
149
|
const response = await axios_1.default.post(endpoint, payload, { headers });
|
|
150
150
|
return this.adapter.extractResponse(response.data);
|
|
151
151
|
}
|
|
152
|
-
catch (
|
|
152
|
+
catch (err) {
|
|
153
|
+
const error = err;
|
|
153
154
|
let errorMessage = error instanceof Error ? error.message : 'connection error';
|
|
154
155
|
if (errorMessage === '') {
|
|
155
156
|
errorMessage = 'connection error';
|
|
156
157
|
}
|
|
157
|
-
console.error(`Error communicating with the GPT server (${this.adapter.getEndpoint(this.stream)})
|
|
158
|
+
console.error(`Error communicating with the GPT server (${this.adapter.getEndpoint(this.stream)})\n\n${JSON.stringify(error.response?.data || "''", null, 2)}\n`);
|
|
158
159
|
throw error;
|
|
159
160
|
}
|
|
160
161
|
}
|
|
@@ -6,17 +6,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const config_utils_1 = require("../../utils/config_utils");
|
|
7
7
|
const config_utils_2 = require("../../utils/config_utils");
|
|
8
8
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
9
|
+
const ChatManager_1 = __importDefault(require("../../managers/ChatManager"));
|
|
9
10
|
const CodeManager_1 = __importDefault(require("../../managers/CodeManager"));
|
|
10
11
|
const code = async (options) => {
|
|
11
12
|
options.silent = (0, config_utils_2.getConfig)(options).globalSilent || false;
|
|
12
13
|
const gpt = new cloving_gpt_1.default(options);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (options.interactive) {
|
|
15
|
+
options.silent = (0, config_utils_2.getConfig)(options).globalSilent || false;
|
|
16
|
+
options.stream = true;
|
|
17
|
+
const chatManager = new ChatManager_1.default(options);
|
|
18
|
+
await chatManager.initialize();
|
|
17
19
|
}
|
|
18
20
|
else {
|
|
19
|
-
|
|
21
|
+
const allSrcFiles = await (0, config_utils_1.getAllFiles)(options, false);
|
|
22
|
+
const codeManager = new CodeManager_1.default(gpt, options, allSrcFiles);
|
|
23
|
+
if (options.prompt) {
|
|
24
|
+
await codeManager.processCode(options.prompt);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error('No prompt provided.');
|
|
28
|
+
}
|
|
20
29
|
}
|
|
21
30
|
};
|
|
22
31
|
exports.default = code;
|
|
@@ -3,204 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
7
|
-
const prompts_1 = require("@inquirer/prompts");
|
|
8
|
-
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const path_1 = __importDefault(require("path"));
|
|
11
6
|
const cloving_gpt_1 = __importDefault(require("../../cloving_gpt"));
|
|
12
|
-
const
|
|
13
|
-
const string_utils_1 = require("../../utils/string_utils");
|
|
14
|
-
const command_utils_1 = require("../../utils/command_utils");
|
|
7
|
+
const ReviewManager_1 = __importDefault(require("../../managers/ReviewManager"));
|
|
15
8
|
const config_utils_1 = require("../../utils/config_utils");
|
|
16
9
|
const review = async (options) => {
|
|
17
|
-
let { prompt, files } = options;
|
|
18
10
|
options.silent = (0, config_utils_1.getConfig)(options).globalSilent || false;
|
|
19
11
|
const gpt = new cloving_gpt_1.default(options);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
${prompt || 'Please provide a code review.'}
|
|
23
|
-
|
|
24
|
-
### Example of a well-structured response
|
|
25
|
-
|
|
26
|
-
# Code Review: Enhanced Code Generation and File Handling
|
|
27
|
-
|
|
28
|
-
## Changes Overview
|
|
29
|
-
|
|
30
|
-
These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
|
|
31
|
-
|
|
32
|
-
1. Enhancing the code generation prompt with a structured example response.
|
|
33
|
-
2. Implementing a new function to extract file names and contents from the generated code.
|
|
34
|
-
3. Adding a new option to save generated code directly to files.
|
|
35
|
-
4. Improving the copy-to-clipboard functionality to allow copying individual files.
|
|
36
|
-
5. Removing an unused import in the review.ts file.
|
|
37
|
-
|
|
38
|
-
## Reason for Changes
|
|
39
|
-
|
|
40
|
-
The changes aim to enhance the usability and flexibility of the code generation tool by providing clearer prompts, better file management, and more control over the generated code.
|
|
41
|
-
|
|
42
|
-
## Detailed Description
|
|
43
|
-
|
|
44
|
-
1. **Enhanced Code Generation Prompt** - The \`generateCodePrompt\` function now includes a structured example of a well-formatted response. This helps guide the AI to produce more consistent and usable output, making it easier to parse and use the generated code.
|
|
45
|
-
2. **File Extraction Function** - A new function \`extractFilesAndContent\` has been added to parse the raw code command and extract individual file names and their contents. This enables more granular control over the generated code, allowing for file-specific operations.
|
|
46
|
-
3. **Save to File Option** - A new 'Save Source Code to Files' option has been added to the user action menu. This feature allows users to save generated code directly to files in the appropriate directory structure.
|
|
47
|
-
4. **Improved Copy to Clipboard** - The 'Copy Source Code to Clipboard' option has been enhanced to allow users to choose specific files to copy, rather than copying all generated code at once.
|
|
48
|
-
5. **Removed Unused Import** - The unused import of \`execFileSync\` from 'child_process' has been removed from the review.ts file, improving code cleanliness.
|
|
49
|
-
|
|
50
|
-
## Potential Bugs and Recommended Fixes
|
|
51
|
-
|
|
52
|
-
### 1. Possible undefined access in \`extractFilesAndContent\`
|
|
53
|
-
|
|
54
|
-
\`\`\`typescript
|
|
55
|
-
const contentMatch = regex.exec(rawCodeCommand)
|
|
56
|
-
if (contentMatch) {
|
|
57
|
-
files.push(fileName)
|
|
58
|
-
let content = contentMatch[1]
|
|
59
|
-
// ...
|
|
60
|
-
}
|
|
61
|
-
\`\`\`
|
|
62
|
-
|
|
63
|
-
There's a potential for \`contentMatch[1]\` to be undefined if the regex doesn't capture any groups. To fix this, add a null check:
|
|
64
|
-
|
|
65
|
-
\`\`\`typescript
|
|
66
|
-
const contentMatch = regex.exec(rawCodeCommand)
|
|
67
|
-
if (contentMatch && contentMatch[1]) {
|
|
68
|
-
files.push(fileName)
|
|
69
|
-
let content = contentMatch[1]
|
|
70
|
-
// ...
|
|
71
|
-
}
|
|
72
|
-
\`\`\`
|
|
73
|
-
`;
|
|
74
|
-
let contextFiles = {};
|
|
75
|
-
try {
|
|
76
|
-
// Define the prompt for analysis
|
|
77
|
-
if (files) {
|
|
78
|
-
let expandedFiles = [];
|
|
79
|
-
for (const file of files) {
|
|
80
|
-
const filePath = path_1.default.resolve(file);
|
|
81
|
-
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
82
|
-
const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
|
|
83
|
-
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
files = expandedFiles;
|
|
90
|
-
for (const file of files) {
|
|
91
|
-
const filePath = path_1.default.resolve(file);
|
|
92
|
-
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
93
|
-
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
94
|
-
contextFiles[file] = content;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const contextFileContents = Object.keys(contextFiles).map((file) => `### Contents of ${file}\n\n${contextFiles[file]}\n\n`).join('\n');
|
|
98
|
-
promptText += `
|
|
99
|
-
### Description of App
|
|
100
|
-
|
|
101
|
-
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
102
|
-
|
|
103
|
-
${contextFileContents}
|
|
104
|
-
|
|
105
|
-
`;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
109
|
-
promptText = `==== begin diff =====
|
|
110
|
-
${gitDiff}
|
|
111
|
-
==== end diff =====`;
|
|
112
|
-
}
|
|
113
|
-
if (files) {
|
|
114
|
-
promptText += `
|
|
115
|
-
|
|
116
|
-
### Request
|
|
117
|
-
|
|
118
|
-
I would like you to explain the code and document a description of it.
|
|
119
|
-
List any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
120
|
-
Format the output of this code review in Markdown format.`;
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
promptText += `
|
|
124
|
-
|
|
125
|
-
### Request
|
|
126
|
-
|
|
127
|
-
Do not use any data from the example response structure, only use the structure.
|
|
128
|
-
I would like you to explain why these change are being made and document a description of these changes.
|
|
129
|
-
Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
130
|
-
Format the output of this code review in Markdown format.`;
|
|
131
|
-
}
|
|
132
|
-
if (prompt) {
|
|
133
|
-
promptText += `
|
|
134
|
-
|
|
135
|
-
### Additional Context
|
|
136
|
-
|
|
137
|
-
${prompt}`;
|
|
138
|
-
}
|
|
139
|
-
// get the analysis
|
|
140
|
-
const analysis = await gpt.generateText({ prompt: promptText });
|
|
141
|
-
// Print the analysis to the console
|
|
142
|
-
(0, string_utils_1.parseMarkdownInstructions)(analysis).map(code => {
|
|
143
|
-
if (code.trim().startsWith('```')) {
|
|
144
|
-
const lines = code.split('\n');
|
|
145
|
-
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
146
|
-
console.log(lines[0]);
|
|
147
|
-
try {
|
|
148
|
-
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
// don't highlight if it fails
|
|
152
|
-
console.log(lines.slice(1, -1).join('\n'));
|
|
153
|
-
}
|
|
154
|
-
console.log(lines.slice(-1)[0]);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
console.log((0, cli_highlight_1.default)(code, { language: 'markdown' }));
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
// Modified Function to extract specific sections from the analysis
|
|
161
|
-
const extractSection = (section) => {
|
|
162
|
-
const regex = new RegExp(`## ${section}[\\s\\S]*?(?=\\n## Potential Bugs and Recommended Fixes|$)`, 'g');
|
|
163
|
-
const match = analysis.match(regex);
|
|
164
|
-
return match ? match[0].trim() : '';
|
|
165
|
-
};
|
|
166
|
-
// Prompt the user with options for copying to clipboard
|
|
167
|
-
const clipboardOption = await (0, prompts_1.select)({
|
|
168
|
-
message: 'What would you like to copy to the clipboard?',
|
|
169
|
-
choices: [
|
|
170
|
-
{ name: 'Copy only the Changes Overview', value: 'Changes Overview' },
|
|
171
|
-
{ name: 'Copy only the Potential Bugs and Recommended Fixes', value: 'Potential Bugs and Recommended Fixes' },
|
|
172
|
-
{ name: 'Copy everything', value: 'Everything' },
|
|
173
|
-
{ name: 'Done', value: 'Done' }
|
|
174
|
-
]
|
|
175
|
-
});
|
|
176
|
-
let contentToCopy = '';
|
|
177
|
-
switch (clipboardOption) {
|
|
178
|
-
case 'Changes Overview':
|
|
179
|
-
contentToCopy = extractSection('Changes Overview');
|
|
180
|
-
break;
|
|
181
|
-
case 'Potential Bugs and Recommended Fixes':
|
|
182
|
-
contentToCopy = extractSection('Potential Bugs and Recommended Fixes');
|
|
183
|
-
break;
|
|
184
|
-
case 'Everything':
|
|
185
|
-
contentToCopy = analysis;
|
|
186
|
-
break;
|
|
187
|
-
case 'Done':
|
|
188
|
-
console.log('No content copied to clipboard.');
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (contentToCopy) {
|
|
192
|
-
copy_paste_1.default.copy(contentToCopy, (err) => {
|
|
193
|
-
if (err) {
|
|
194
|
-
console.error('Error: Unable to copy to clipboard.', err);
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
console.log('Selected content copied to clipboard');
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
catch (error) {
|
|
203
|
-
console.error('Error during analysis:', error.message);
|
|
204
|
-
}
|
|
12
|
+
const reviewManager = new ReviewManager_1.default(gpt, options);
|
|
13
|
+
await reviewManager.review();
|
|
205
14
|
};
|
|
206
15
|
exports.default = review;
|
|
@@ -64,7 +64,8 @@ Enumerate all the files in the provided list${gitDiff ? ' and git diff' : ''} as
|
|
|
64
64
|
Also, list any test files that might be relevant to these files.`;
|
|
65
65
|
};
|
|
66
66
|
const handleUserAction = async (analysis, autoSave = false) => {
|
|
67
|
-
const
|
|
67
|
+
const fileContents = (0, string_utils_1.extractFilesAndContent)(analysis);
|
|
68
|
+
const files = Object.keys(fileContents);
|
|
68
69
|
if (autoSave) {
|
|
69
70
|
for (const file of files) {
|
|
70
71
|
if (fileContents[file]) {
|
package/dist/commands/init.js
CHANGED
|
@@ -42,11 +42,8 @@ technologies used. This will provide better context for future Cloving requests.
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
if (!(0, command_utils_1.checkForSpecialFiles)()) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
validate: (input) => input.trim().length > 0 || 'Please provide a description of the technologies used.',
|
|
48
|
-
});
|
|
49
|
-
specialFileContents['description'] = technologies;
|
|
45
|
+
console.error('No dependencies files detected. Please add a dependency file (e.g. package.json, Gemfile, requirements.txt, etc.) to your project and run `cloving init` again.');
|
|
46
|
+
return;
|
|
50
47
|
}
|
|
51
48
|
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `describe_${Date.now()}.tmp`);
|
|
52
49
|
try {
|
package/dist/index.js
CHANGED
|
@@ -19,10 +19,6 @@ const commit_1 = __importDefault(require("./commands/generate/commit"));
|
|
|
19
19
|
const context_1 = __importDefault(require("./commands/generate/context"));
|
|
20
20
|
const unit_tests_1 = __importDefault(require("./commands/generate/unit_tests"));
|
|
21
21
|
const review_1 = __importDefault(require("./commands/generate/review"));
|
|
22
|
-
const init_2 = __importDefault(require("./commands/project/init"));
|
|
23
|
-
const plan_1 = __importDefault(require("./commands/project/plan"));
|
|
24
|
-
const build_1 = __importDefault(require("./commands/project/build"));
|
|
25
|
-
const complete_1 = __importDefault(require("./commands/project/complete"));
|
|
26
22
|
// Function to get version from package.json
|
|
27
23
|
const getPackageVersion = () => {
|
|
28
24
|
const packagePath = (0, path_1.join)(__dirname, 'package.json');
|
|
@@ -134,29 +130,4 @@ generate
|
|
|
134
130
|
.option('-m, --model <model>', 'Select the model to use (e.g., openai, claude, ollama, ollama:llama3, claude:claude-3-5-sonnet-20240620)')
|
|
135
131
|
.option('-t, --temperature <temperature>', 'Temperature for the model (default 0.2)')
|
|
136
132
|
.action(review_1.default);
|
|
137
|
-
// Project commands
|
|
138
|
-
const project = program
|
|
139
|
-
.command('project')
|
|
140
|
-
.alias('p')
|
|
141
|
-
.description('Commands for cloving a project');
|
|
142
|
-
project
|
|
143
|
-
.command('init')
|
|
144
|
-
.alias('i')
|
|
145
|
-
.description('Setup a new cloving project inside a git branch')
|
|
146
|
-
.action(init_2.default);
|
|
147
|
-
project
|
|
148
|
-
.command('plan')
|
|
149
|
-
.alias('p')
|
|
150
|
-
.description('Plan the steps to complete the project')
|
|
151
|
-
.action(plan_1.default);
|
|
152
|
-
project
|
|
153
|
-
.command('build')
|
|
154
|
-
.alias('b')
|
|
155
|
-
.description('Generate code to build the project')
|
|
156
|
-
.action(build_1.default);
|
|
157
|
-
project
|
|
158
|
-
.command('complete')
|
|
159
|
-
.alias('c')
|
|
160
|
-
.description('Clean up and finalize the project')
|
|
161
|
-
.action(complete_1.default);
|
|
162
133
|
program.parse(process.argv);
|
|
@@ -9,6 +9,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
9
9
|
const readline_1 = __importDefault(require("readline"));
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
11
|
const ChunkManager_1 = __importDefault(require("./ChunkManager"));
|
|
12
|
+
const ReviewManager_1 = __importDefault(require("./ReviewManager"));
|
|
12
13
|
const cloving_gpt_1 = __importDefault(require("../cloving_gpt"));
|
|
13
14
|
const git_utils_1 = require("../utils/git_utils");
|
|
14
15
|
const string_utils_1 = require("../utils/string_utils");
|
|
@@ -124,6 +125,9 @@ class ChatManager {
|
|
|
124
125
|
case 'commit':
|
|
125
126
|
await this.handleCommit();
|
|
126
127
|
break;
|
|
128
|
+
case 'review':
|
|
129
|
+
await this.handleReview();
|
|
130
|
+
break;
|
|
127
131
|
default:
|
|
128
132
|
if (this.isGitCommand(command)) {
|
|
129
133
|
this.executeGitCommand(command);
|
|
@@ -144,26 +148,36 @@ class ChatManager {
|
|
|
144
148
|
console.error('No response to copy.');
|
|
145
149
|
}
|
|
146
150
|
}
|
|
151
|
+
async handleReview() {
|
|
152
|
+
this.gpt.stream = false;
|
|
153
|
+
const reviewManager = new ReviewManager_1.default(this.gpt, this.options);
|
|
154
|
+
await reviewManager.review();
|
|
155
|
+
this.gpt.stream = true;
|
|
156
|
+
}
|
|
147
157
|
async handleSave() {
|
|
148
158
|
const lastResponse = this.chatHistory.filter(msg => msg.role === 'assistant').pop();
|
|
149
159
|
if (lastResponse) {
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
await (0, string_utils_1.saveGeneratedFiles)(
|
|
160
|
+
const fileContents = (0, string_utils_1.extractFilesAndContent)(lastResponse.content);
|
|
161
|
+
if (Object.keys(fileContents).length > 0) {
|
|
162
|
+
await (0, string_utils_1.saveGeneratedFiles)(fileContents);
|
|
153
163
|
console.info('Files have been saved.');
|
|
164
|
+
this.rl.prompt();
|
|
154
165
|
}
|
|
155
166
|
else {
|
|
156
167
|
console.info('No files found to save in the last response.');
|
|
168
|
+
this.rl.prompt();
|
|
157
169
|
}
|
|
158
170
|
}
|
|
159
171
|
else {
|
|
160
172
|
console.error('No response to save files from.');
|
|
173
|
+
this.rl.prompt();
|
|
161
174
|
}
|
|
162
175
|
}
|
|
163
176
|
async handleCommit() {
|
|
164
177
|
const diff = (0, child_process_1.execSync)('git diff HEAD').toString().trim();
|
|
165
178
|
if (!diff) {
|
|
166
179
|
console.error('No changes to commit.');
|
|
180
|
+
this.rl.prompt();
|
|
167
181
|
return;
|
|
168
182
|
}
|
|
169
183
|
this.prompt = (0, git_utils_1.generateCommitMessagePrompt)(diff);
|
|
@@ -183,6 +197,7 @@ class ChatManager {
|
|
|
183
197
|
if (err)
|
|
184
198
|
throw err;
|
|
185
199
|
});
|
|
200
|
+
this.rl.prompt();
|
|
186
201
|
}
|
|
187
202
|
isGitCommand(command) {
|
|
188
203
|
return command.split(' ').length <= 3 && command.startsWith('git ');
|
|
@@ -194,6 +209,7 @@ class ChatManager {
|
|
|
194
209
|
catch (error) {
|
|
195
210
|
console.error('Error running command:', error);
|
|
196
211
|
}
|
|
212
|
+
this.rl.prompt();
|
|
197
213
|
}
|
|
198
214
|
async processUserInput(input) {
|
|
199
215
|
if (this.isProcessing) {
|
|
@@ -99,18 +99,19 @@ Do not use any data from the example response structure, only use the structure.
|
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
101
|
}
|
|
102
|
-
async updateContextFiles(
|
|
103
|
-
for (const file of
|
|
102
|
+
async updateContextFiles(fileContents) {
|
|
103
|
+
for (const file of Object.keys(fileContents)) {
|
|
104
104
|
if (fileContents[file]) {
|
|
105
105
|
this.contextFiles[file] = fileContents[file];
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
async handleUserAction(rawCodeCommand, prompt) {
|
|
110
|
-
const
|
|
110
|
+
const fileContents = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
111
|
+
const files = Object.keys(fileContents);
|
|
111
112
|
if (this.options.save) {
|
|
112
|
-
await (0, string_utils_1.saveGeneratedFiles)(
|
|
113
|
-
await this.updateContextFiles(
|
|
113
|
+
await (0, string_utils_1.saveGeneratedFiles)(fileContents);
|
|
114
|
+
await this.updateContextFiles(fileContents);
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
117
|
const action = await (0, prompts_1.select)({
|
|
@@ -190,7 +191,7 @@ Do not use any data from the example response structure, only use the structure.
|
|
|
190
191
|
await fs_1.default.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
191
192
|
await fs_1.default.promises.writeFile(filePath, fileContents[fileToSave]);
|
|
192
193
|
console.log(`${fileToSave} has been saved.`);
|
|
193
|
-
await this.updateContextFiles([fileToSave]
|
|
194
|
+
await this.updateContextFiles({ [fileToSave]: fileContents[fileToSave] });
|
|
194
195
|
}
|
|
195
196
|
else {
|
|
196
197
|
console.log('File content not found.');
|
|
@@ -203,8 +204,8 @@ Do not use any data from the example response structure, only use the structure.
|
|
|
203
204
|
}
|
|
204
205
|
break;
|
|
205
206
|
case 'saveAll':
|
|
206
|
-
await (0, string_utils_1.saveGeneratedFiles)(
|
|
207
|
-
await this.updateContextFiles(
|
|
207
|
+
await (0, string_utils_1.saveGeneratedFiles)(fileContents);
|
|
208
|
+
await this.updateContextFiles(fileContents);
|
|
208
209
|
console.log('All files have been saved.');
|
|
209
210
|
break;
|
|
210
211
|
case 'done':
|
|
@@ -268,48 +269,13 @@ Please briefly explain how the code works in this.`;
|
|
|
268
269
|
let rawCodeCommand = await this.generateCode(prompt);
|
|
269
270
|
this.displayGeneratedCode(rawCodeCommand);
|
|
270
271
|
if (this.options.save) {
|
|
271
|
-
const
|
|
272
|
-
await (0, string_utils_1.saveGeneratedFiles)(
|
|
273
|
-
await this.updateContextFiles(
|
|
272
|
+
const fileContents = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
273
|
+
await (0, string_utils_1.saveGeneratedFiles)(fileContents);
|
|
274
|
+
await this.updateContextFiles(fileContents);
|
|
274
275
|
}
|
|
275
276
|
else {
|
|
276
277
|
await this.handleUserAction(rawCodeCommand, prompt);
|
|
277
278
|
}
|
|
278
|
-
if (this.options.interactive) {
|
|
279
|
-
let continueInteractive = true;
|
|
280
|
-
while (continueInteractive) {
|
|
281
|
-
const newPrompt = await (0, prompts_1.input)({
|
|
282
|
-
message: 'Revise the code (or press enter to finish):',
|
|
283
|
-
});
|
|
284
|
-
if (newPrompt.trim() === '') {
|
|
285
|
-
continueInteractive = false;
|
|
286
|
-
}
|
|
287
|
-
else {
|
|
288
|
-
let includeMoreFiles = true;
|
|
289
|
-
while (includeMoreFiles) {
|
|
290
|
-
const contextFile = await (0, prompts_1.input)({
|
|
291
|
-
message: 'Enter the relative path of a file or directory you would like to include as context (or press enter to continue):',
|
|
292
|
-
});
|
|
293
|
-
if (contextFile) {
|
|
294
|
-
this.contextFiles = await (0, command_utils_1.addFileOrDirectoryToContext)(contextFile, this.contextFiles, this.options);
|
|
295
|
-
}
|
|
296
|
-
else {
|
|
297
|
-
includeMoreFiles = false;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
rawCodeCommand = await this.generateCode(newPrompt, rawCodeCommand);
|
|
301
|
-
this.displayGeneratedCode(rawCodeCommand);
|
|
302
|
-
if (this.options.save || this.options.interactive) {
|
|
303
|
-
const [files, fileContents] = (0, string_utils_1.extractFilesAndContent)(rawCodeCommand);
|
|
304
|
-
await (0, string_utils_1.saveGeneratedFiles)(files, fileContents);
|
|
305
|
-
await this.updateContextFiles(files, fileContents);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
await this.handleUserAction(rawCodeCommand, newPrompt);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
279
|
}
|
|
314
280
|
catch (error) {
|
|
315
281
|
console.error('Could not generate code', error);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cli_highlight_1 = __importDefault(require("cli-highlight"));
|
|
7
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const copy_paste_1 = __importDefault(require("copy-paste"));
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const git_utils_1 = require("../utils/git_utils");
|
|
12
|
+
const string_utils_1 = require("../utils/string_utils");
|
|
13
|
+
const command_utils_1 = require("../utils/command_utils");
|
|
14
|
+
const config_utils_1 = require("../utils/config_utils");
|
|
15
|
+
class ReviewManager {
|
|
16
|
+
constructor(gpt, options) {
|
|
17
|
+
this.contextFiles = {};
|
|
18
|
+
this.gpt = gpt;
|
|
19
|
+
this.options = options;
|
|
20
|
+
}
|
|
21
|
+
async loadContextFiles() {
|
|
22
|
+
if (this.options.files) {
|
|
23
|
+
let expandedFiles = [];
|
|
24
|
+
for (const file of this.options.files) {
|
|
25
|
+
const filePath = path_1.default.resolve(file);
|
|
26
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isDirectory()).catch(() => false)) {
|
|
27
|
+
const dirFiles = await (0, command_utils_1.getAllFilesInDirectory)(filePath);
|
|
28
|
+
expandedFiles = expandedFiles.concat(dirFiles.map(f => path_1.default.relative(process.cwd(), f)));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
expandedFiles.push(path_1.default.relative(process.cwd(), filePath));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.options.files = expandedFiles;
|
|
35
|
+
for (const file of this.options.files) {
|
|
36
|
+
const filePath = path_1.default.resolve(file);
|
|
37
|
+
if (await fs_1.default.promises.stat(filePath).then(stat => stat.isFile()).catch(() => false)) {
|
|
38
|
+
const content = await fs_1.default.promises.readFile(filePath, 'utf-8');
|
|
39
|
+
this.contextFiles[file] = content;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
generatePrompt() {
|
|
45
|
+
let promptText = `### Prompt
|
|
46
|
+
|
|
47
|
+
${this.options.prompt || 'Please provide a code review.'}
|
|
48
|
+
|
|
49
|
+
### Example of a well-structured response
|
|
50
|
+
|
|
51
|
+
# Code Review: Enhanced Code Generation and File Handling
|
|
52
|
+
|
|
53
|
+
## Changes Overview
|
|
54
|
+
|
|
55
|
+
These changes primarily focus on improving the code generation process and adding more robust file handling capabilities. The main modifications include:
|
|
56
|
+
|
|
57
|
+
1. Enhancing the code generation prompt with a structured example response.
|
|
58
|
+
2. Implementing a new function to extract file names and contents from the generated code.
|
|
59
|
+
3. Adding a new option to save generated code directly to files.
|
|
60
|
+
4. Improving the copy-to-clipboard functionality to allow copying individual files.
|
|
61
|
+
5. Removing an unused import in the review.ts file.
|
|
62
|
+
|
|
63
|
+
## Reason for Changes
|
|
64
|
+
|
|
65
|
+
The changes aim to enhance the usability and flexibility of the code generation tool by providing clearer prompts, better file management, and more control over the generated code.
|
|
66
|
+
|
|
67
|
+
## Detailed Description
|
|
68
|
+
|
|
69
|
+
1. **Enhanced Code Generation Prompt** - The \`generateCodePrompt\` function now includes a structured example of a well-formatted response. This helps guide the AI to produce more consistent and usable output, making it easier to parse and use the generated code.
|
|
70
|
+
2. **File Extraction Function** - A new function \`extractFilesAndContent\` has been added to parse the raw code command and extract individual file names and their contents. This enables more granular control over the generated code, allowing for file-specific operations.
|
|
71
|
+
3. **Save to File Option** - A new 'Save Source Code to Files' option has been added to the user action menu. This feature allows users to save generated code directly to files in the appropriate directory structure.
|
|
72
|
+
4. **Improved Copy to Clipboard** - The 'Copy Source Code to Clipboard' option has been enhanced to allow users to choose specific files to copy, rather than copying all generated code at once.
|
|
73
|
+
5. **Removed Unused Import** - The unused import of \`execFileSync\` from 'child_process' has been removed from the review.ts file, improving code cleanliness.
|
|
74
|
+
|
|
75
|
+
## Potential Bugs and Recommended Fixes
|
|
76
|
+
|
|
77
|
+
### 1. Possible undefined access in \`extractFilesAndContent\`
|
|
78
|
+
|
|
79
|
+
\`\`\`typescript
|
|
80
|
+
const contentMatch = regex.exec(rawCodeCommand)
|
|
81
|
+
if (contentMatch) {
|
|
82
|
+
files.push(fileName)
|
|
83
|
+
let content = contentMatch[1]
|
|
84
|
+
// ...
|
|
85
|
+
}
|
|
86
|
+
\`\`\`
|
|
87
|
+
|
|
88
|
+
There's a potential for \`contentMatch[1]\` to be undefined if the regex doesn't capture any groups. To fix this, add a null check:
|
|
89
|
+
|
|
90
|
+
\`\`\`typescript
|
|
91
|
+
const contentMatch = regex.exec(rawCodeCommand)
|
|
92
|
+
if (contentMatch && contentMatch[1]) {
|
|
93
|
+
files.push(fileName)
|
|
94
|
+
let content = contentMatch[1]
|
|
95
|
+
// ...
|
|
96
|
+
}
|
|
97
|
+
\`\`\`
|
|
98
|
+
`;
|
|
99
|
+
if (this.options.files) {
|
|
100
|
+
const contextFileContents = Object.keys(this.contextFiles).map((file) => `### Contents of ${file}\n\n${this.contextFiles[file]}\n\n`).join('\n');
|
|
101
|
+
promptText += `
|
|
102
|
+
### Description of App
|
|
103
|
+
|
|
104
|
+
${JSON.stringify((0, config_utils_1.getClovingConfig)(), null, 2)}
|
|
105
|
+
|
|
106
|
+
${contextFileContents}
|
|
107
|
+
|
|
108
|
+
### Request
|
|
109
|
+
|
|
110
|
+
I would like you to explain the code and document a description of it.
|
|
111
|
+
List any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
112
|
+
Format the output of this code review in Markdown format.`;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
promptText += `
|
|
116
|
+
|
|
117
|
+
### Request
|
|
118
|
+
|
|
119
|
+
Do not use any data from the example response structure, only use the structure.
|
|
120
|
+
I would like you to explain why these change are being made and document a description of these changes.
|
|
121
|
+
Also list any bugs in the new code as well as recommended fixes for those bugs with code examples.
|
|
122
|
+
Format the output of this code review in Markdown format.`;
|
|
123
|
+
}
|
|
124
|
+
return promptText;
|
|
125
|
+
}
|
|
126
|
+
async getGitDiffPrompt() {
|
|
127
|
+
const gitDiff = await (0, git_utils_1.getGitDiff)();
|
|
128
|
+
return `==== begin diff =====
|
|
129
|
+
${gitDiff}
|
|
130
|
+
==== end diff =====
|
|
131
|
+
|
|
132
|
+
${this.generatePrompt()}`;
|
|
133
|
+
}
|
|
134
|
+
displayAnalysis(analysis) {
|
|
135
|
+
(0, string_utils_1.parseMarkdownInstructions)(analysis).map(code => {
|
|
136
|
+
if (code.trim().startsWith('```')) {
|
|
137
|
+
const lines = code.split('\n');
|
|
138
|
+
const language = code.match(/```(\w+)/)?.[1] || 'plaintext';
|
|
139
|
+
console.log(lines[0]);
|
|
140
|
+
try {
|
|
141
|
+
console.log((0, cli_highlight_1.default)(lines.slice(1, -1).join('\n'), { language }));
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
// don't highlight if it fails
|
|
145
|
+
console.log(lines.slice(1, -1).join('\n'));
|
|
146
|
+
}
|
|
147
|
+
console.log(lines.slice(-1)[0]);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log((0, cli_highlight_1.default)(code, { language: 'markdown' }));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async handleUserAction(analysis) {
|
|
155
|
+
const extractSection = (section) => {
|
|
156
|
+
const regex = new RegExp(`## ${section}[\\s\\S]*?(?=\\n## Potential Bugs and Recommended Fixes|$)`, 'g');
|
|
157
|
+
const match = analysis.match(regex);
|
|
158
|
+
return match ? match[0].trim() : '';
|
|
159
|
+
};
|
|
160
|
+
const clipboardOption = await (0, prompts_1.select)({
|
|
161
|
+
message: 'What would you like to copy to the clipboard?',
|
|
162
|
+
choices: [
|
|
163
|
+
{ name: 'Copy only the Changes Overview', value: 'Changes Overview' },
|
|
164
|
+
{ name: 'Copy only the Potential Bugs and Recommended Fixes', value: 'Potential Bugs and Recommended Fixes' },
|
|
165
|
+
{ name: 'Copy everything', value: 'Everything' },
|
|
166
|
+
{ name: 'Done', value: 'Done' }
|
|
167
|
+
]
|
|
168
|
+
});
|
|
169
|
+
let contentToCopy = '';
|
|
170
|
+
switch (clipboardOption) {
|
|
171
|
+
case 'Changes Overview':
|
|
172
|
+
contentToCopy = extractSection('Changes Overview');
|
|
173
|
+
break;
|
|
174
|
+
case 'Potential Bugs and Recommended Fixes':
|
|
175
|
+
contentToCopy = extractSection('Potential Bugs and Recommended Fixes');
|
|
176
|
+
break;
|
|
177
|
+
case 'Everything':
|
|
178
|
+
contentToCopy = analysis;
|
|
179
|
+
break;
|
|
180
|
+
case 'Done':
|
|
181
|
+
console.log('No content copied to clipboard.');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (contentToCopy) {
|
|
185
|
+
copy_paste_1.default.copy(contentToCopy, (err) => {
|
|
186
|
+
if (err) {
|
|
187
|
+
console.error('Error: Unable to copy to clipboard.', err);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log('Selected content copied to clipboard');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async review() {
|
|
196
|
+
try {
|
|
197
|
+
await this.loadContextFiles();
|
|
198
|
+
const prompt = this.options.files ? this.generatePrompt() : await this.getGitDiffPrompt();
|
|
199
|
+
const analysis = await this.gpt.generateText({ prompt });
|
|
200
|
+
this.displayAnalysis(analysis);
|
|
201
|
+
await this.handleUserAction(analysis);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.error('Error during analysis:', error.message);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
exports.default = ReviewManager;
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.25",
|
|
4
4
|
"packageManager": "yarn@1.22.22",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"eslint-plugin-jsdoc": "^48.11.0",
|
|
50
50
|
"eslint-plugin-n": "^17.10.1",
|
|
51
51
|
"eslint-plugin-promise": "^7.0.0",
|
|
52
|
+
"express": "^4.19.2",
|
|
52
53
|
"ignore": "^5.3.1",
|
|
53
54
|
"ini": "^4.1.3",
|
|
54
55
|
"isbinaryfile": "^5.0.2",
|
|
@@ -57,7 +58,9 @@
|
|
|
57
58
|
"vite-node": "^2.0.5"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
61
|
+
"@types/body-parser": "^1.19.5",
|
|
60
62
|
"@types/copy-paste": "^1.1.33",
|
|
63
|
+
"@types/express": "^4.17.21",
|
|
61
64
|
"@types/ini": "^4.1.1",
|
|
62
65
|
"@types/inquirer": "^9.0.7",
|
|
63
66
|
"@types/jest": "^29.5.12",
|
|
@@ -98,30 +98,59 @@ const parseMarkdownInstructions = (input) => {
|
|
|
98
98
|
exports.parseMarkdownInstructions = parseMarkdownInstructions;
|
|
99
99
|
const extractFilesAndContent = (rawCodeCommand) => {
|
|
100
100
|
if (!rawCodeCommand)
|
|
101
|
-
return
|
|
101
|
+
return {};
|
|
102
102
|
const files = [];
|
|
103
103
|
const fileContents = {};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
104
|
+
let currentIndex = 0;
|
|
105
|
+
const commandLength = rawCodeCommand.length;
|
|
106
|
+
while (currentIndex < commandLength) {
|
|
107
|
+
let fileNameStart = rawCodeCommand.indexOf('**', currentIndex);
|
|
108
|
+
if (fileNameStart === -1)
|
|
109
|
+
break;
|
|
110
|
+
let fileNameEnd = rawCodeCommand.indexOf('**', fileNameStart + 2);
|
|
111
|
+
if (fileNameEnd === -1)
|
|
112
|
+
break;
|
|
113
|
+
let nextBoldStart = rawCodeCommand.indexOf('**', fileNameEnd + 2);
|
|
114
|
+
let nextCodeStart = rawCodeCommand.indexOf('\n```', fileNameEnd + 2);
|
|
115
|
+
while (nextBoldStart !== -1 && nextCodeStart > nextBoldStart) {
|
|
116
|
+
fileNameStart = nextBoldStart;
|
|
117
|
+
fileNameEnd = rawCodeCommand.indexOf('**', fileNameStart + 2);
|
|
118
|
+
nextBoldStart = rawCodeCommand.indexOf('**', fileNameEnd + 2);
|
|
119
|
+
nextCodeStart = rawCodeCommand.indexOf('\n```', fileNameEnd + 2);
|
|
118
120
|
}
|
|
121
|
+
const fileName = rawCodeCommand.slice(fileNameStart + 2, fileNameEnd).trim();
|
|
122
|
+
files.push(fileName);
|
|
123
|
+
const codeBlockStart = rawCodeCommand.indexOf('\n```', fileNameEnd);
|
|
124
|
+
if (codeBlockStart === -1)
|
|
125
|
+
break;
|
|
126
|
+
let codeBlockEnd = codeBlockStart + 4;
|
|
127
|
+
while (codeBlockEnd < commandLength) {
|
|
128
|
+
if (rawCodeCommand.slice(codeBlockEnd, codeBlockEnd + 5) === '\n```\n') {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
else if (rawCodeCommand.slice(codeBlockEnd, codeBlockEnd + 4) === '\n```' && codeBlockEnd + 4 === commandLength) {
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
codeBlockEnd++;
|
|
135
|
+
}
|
|
136
|
+
if (codeBlockEnd >= commandLength)
|
|
137
|
+
break;
|
|
138
|
+
let content = rawCodeCommand.slice(codeBlockStart + 4, codeBlockEnd).trim();
|
|
139
|
+
const firstNewlineIndex = content.indexOf('\n');
|
|
140
|
+
if (firstNewlineIndex !== -1) {
|
|
141
|
+
content = content.slice(firstNewlineIndex + 1);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
content = content.replace(/^\w+\s*/, '');
|
|
145
|
+
}
|
|
146
|
+
fileContents[fileName] = content.trim();
|
|
147
|
+
currentIndex = codeBlockEnd + 5;
|
|
119
148
|
}
|
|
120
|
-
return
|
|
149
|
+
return fileContents;
|
|
121
150
|
};
|
|
122
151
|
exports.extractFilesAndContent = extractFilesAndContent;
|
|
123
|
-
const saveGeneratedFiles = async (
|
|
124
|
-
for (const file of
|
|
152
|
+
const saveGeneratedFiles = async (fileContents) => {
|
|
153
|
+
for (const file of Object.keys(fileContents)) {
|
|
125
154
|
if (fileContents[file]) {
|
|
126
155
|
const filePath = path_1.default.resolve(file);
|
|
127
156
|
await fs_1.default.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|