cloving 0.3.3 → 0.3.5
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/dist/cloving_gpt/adapters/claude.js +7 -5
- package/dist/cloving_gpt/adapters/gemini.js +14 -9
- package/dist/cloving_gpt/adapters/mistral.js +9 -7
- package/dist/cloving_gpt/adapters/ollama.js +7 -7
- package/dist/cloving_gpt/adapters/openai.js +9 -7
- package/dist/cloving_gpt/index.js +12 -7
- package/dist/commands/config.js +7 -7
- package/dist/commands/generate/context.js +10 -3
- package/dist/commands/generate/unit_tests.js +3 -261
- package/dist/commands/init.js +6 -6
- package/dist/commands/tokens.js +12 -4
- package/dist/index.js +3 -6
- package/dist/managers/ChatManager.js +77 -33
- package/dist/managers/CodeManager.js +19 -5
- package/dist/managers/CommitManager.js +6 -3
- package/dist/managers/ProxyManager.js +22 -7
- package/dist/managers/ReviewManager.js +26 -10
- package/dist/managers/ShellManager.js +5 -2
- package/dist/managers/UnitTestManager.js +243 -0
- package/dist/package.json +13 -8
- package/dist/utils/command_utils.js +86 -34
- package/dist/utils/config_utils.js +15 -5
- package/dist/utils/git_utils.js +7 -4
- package/dist/utils/string_utils.js +206 -61
- package/package.json +13 -8
|
@@ -3,7 +3,7 @@ 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
|
-
exports.applyAndSaveCurrentNewBlocks = exports.extractCurrentNewBlocks = exports.parseMarkdownInstructions = exports.extractMarkdown = exports.extractJsonMetadata = exports.estimateTokens = void 0;
|
|
6
|
+
exports.checkBlocksApplicability = exports.applyAndSaveCurrentNewBlocks = exports.processBlock = exports.updateFileContent = exports.writeFileContent = exports.ensureDirectoryExists = exports.extractCurrentNewBlocks = exports.extractBlock = exports.findBlockIndices = exports.parseMarkdownInstructions = exports.extractMarkdown = exports.extractJsonMetadata = exports.estimateTokens = void 0;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const colors_1 = __importDefault(require("colors"));
|
|
@@ -35,13 +35,13 @@ exports.estimateTokens = estimateTokens;
|
|
|
35
35
|
* or a partial JSON string.
|
|
36
36
|
*
|
|
37
37
|
* @example
|
|
38
|
-
* const aiResponse = '```json\n{"key": "value"}\n```\nOther text'
|
|
39
|
-
* const jsonMetadata = extractJsonMetadata(aiResponse)
|
|
38
|
+
* const aiResponse = '```json\n{"key": "value"}\n```\nOther text'
|
|
39
|
+
* const jsonMetadata = extractJsonMetadata(aiResponse)
|
|
40
40
|
* // jsonMetadata will be '{"key": "value"}'
|
|
41
41
|
*
|
|
42
42
|
* @example
|
|
43
|
-
* const plainResponse = 'Some text {"key": "value"} more text'
|
|
44
|
-
* const jsonMetadata = extractJsonMetadata(plainResponse)
|
|
43
|
+
* const plainResponse = 'Some text {"key": "value"} more text'
|
|
44
|
+
* const jsonMetadata = extractJsonMetadata(plainResponse)
|
|
45
45
|
* // jsonMetadata will be '{"key": "value"}'
|
|
46
46
|
*/
|
|
47
47
|
const extractJsonMetadata = (response) => {
|
|
@@ -68,7 +68,20 @@ const extractJsonMetadata = (response) => {
|
|
|
68
68
|
return jsonString;
|
|
69
69
|
};
|
|
70
70
|
exports.extractJsonMetadata = extractJsonMetadata;
|
|
71
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Extracts markdown content from a given response string.
|
|
73
|
+
*
|
|
74
|
+
* This function attempts to extract markdown content from the input string,
|
|
75
|
+
* focusing on content within code blocks and removing any surrounding metadata.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} response - The input string containing markdown and possibly other content.
|
|
78
|
+
* @returns {string} The extracted markdown content.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* const response = '```markdown\n# Title\nContent\n```\nOther text';
|
|
82
|
+
* const markdown = extractMarkdown(response);
|
|
83
|
+
* console.log(markdown); // Outputs: "# Title\nContent"
|
|
84
|
+
*/
|
|
72
85
|
const extractMarkdown = (response) => {
|
|
73
86
|
let markdownString = response;
|
|
74
87
|
// Regular expression to match any ```<word> block
|
|
@@ -91,12 +104,28 @@ const extractMarkdown = (response) => {
|
|
|
91
104
|
return markdownString;
|
|
92
105
|
};
|
|
93
106
|
exports.extractMarkdown = extractMarkdown;
|
|
107
|
+
/**
|
|
108
|
+
* Parses markdown instructions into separate blocks.
|
|
109
|
+
*
|
|
110
|
+
* This function takes a string input containing markdown instructions and
|
|
111
|
+
* separates it into an array of strings, where each string represents a
|
|
112
|
+
* distinct block of content (either a code block or a text block).
|
|
113
|
+
*
|
|
114
|
+
* @param {string} input - The input string containing markdown instructions.
|
|
115
|
+
* @returns {string[]} An array of strings, each representing a distinct block of content.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* const input = "# Title\n```js\nconst x = 1;\n```\nSome text";
|
|
119
|
+
* const blocks = parseMarkdownInstructions(input);
|
|
120
|
+
* console.log(blocks);
|
|
121
|
+
* // Outputs: ["# Title", "```js\nconst x = 1;\n```", "Some text"]
|
|
122
|
+
*/
|
|
94
123
|
const parseMarkdownInstructions = (input) => {
|
|
95
124
|
const lines = input.split('\n');
|
|
96
125
|
const result = [];
|
|
97
126
|
let buffer = [];
|
|
98
127
|
let inCodeBlock = false;
|
|
99
|
-
lines.forEach(line => {
|
|
128
|
+
lines.forEach((line) => {
|
|
100
129
|
if (line.trim().startsWith('```')) {
|
|
101
130
|
if (inCodeBlock) {
|
|
102
131
|
// End of code block
|
|
@@ -126,6 +155,18 @@ const parseMarkdownInstructions = (input) => {
|
|
|
126
155
|
return result;
|
|
127
156
|
};
|
|
128
157
|
exports.parseMarkdownInstructions = parseMarkdownInstructions;
|
|
158
|
+
/**
|
|
159
|
+
* Finds the indices of a CURRENT/NEW block within the input string.
|
|
160
|
+
*
|
|
161
|
+
* This function searches for the start, divider, and end markers of a CURRENT/NEW block
|
|
162
|
+
* in the input string, starting from the given index. It returns the indices of these
|
|
163
|
+
* markers if a complete block is found.
|
|
164
|
+
*
|
|
165
|
+
* @param {string} input - The input string to search for block indices.
|
|
166
|
+
* @param {number} startIndex - The index in the input string to start searching from.
|
|
167
|
+
* @returns {BlockIndices | null} An object containing the indices of the block components,
|
|
168
|
+
* or null if a complete block is not found.
|
|
169
|
+
*/
|
|
129
170
|
const findBlockIndices = (input, startIndex) => {
|
|
130
171
|
const blockStart = input.indexOf(BLOCK_START, startIndex);
|
|
131
172
|
if (blockStart === -1)
|
|
@@ -142,15 +183,27 @@ const findBlockIndices = (input, startIndex) => {
|
|
|
142
183
|
start: blockStart,
|
|
143
184
|
filePathEnd,
|
|
144
185
|
divider: dividerIndex,
|
|
145
|
-
end: blockEnd
|
|
186
|
+
end: blockEnd,
|
|
146
187
|
};
|
|
147
188
|
};
|
|
189
|
+
exports.findBlockIndices = findBlockIndices;
|
|
190
|
+
/**
|
|
191
|
+
* Extracts the content of a CURRENT/NEW block using the provided indices.
|
|
192
|
+
*
|
|
193
|
+
* This function takes the input string and a set of block indices, and extracts
|
|
194
|
+
* the file path, current content, and new content from the block.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} input - The input string containing the CURRENT/NEW block.
|
|
197
|
+
* @param {BlockIndices} indices - An object containing the indices of the block components.
|
|
198
|
+
* @returns {CurrentNewBlock} An object containing the extracted file path, current content, and new content.
|
|
199
|
+
*/
|
|
148
200
|
const extractBlock = (input, indices) => {
|
|
149
201
|
const filePath = input.slice(indices.start + BLOCK_START.length, indices.filePathEnd).trim();
|
|
150
202
|
const currentContent = input.slice(indices.filePathEnd + 1, indices.divider).trim();
|
|
151
203
|
const newContent = input.slice(indices.divider + BLOCK_DIVIDER.length, indices.end).trim();
|
|
152
204
|
return { filePath, currentContent, newContent };
|
|
153
205
|
};
|
|
206
|
+
exports.extractBlock = extractBlock;
|
|
154
207
|
/**
|
|
155
208
|
* Extracts CURRENT/NEW blocks from the given input string.
|
|
156
209
|
*
|
|
@@ -169,12 +222,12 @@ const extractBlock = (input, indices) => {
|
|
|
169
222
|
* @example
|
|
170
223
|
* const input = `
|
|
171
224
|
* <<<<<<< CURRENT path/to/file.ts
|
|
172
|
-
* const oldCode = 'old'
|
|
225
|
+
* const oldCode = 'old'
|
|
173
226
|
* =======
|
|
174
|
-
* const newCode = 'new'
|
|
227
|
+
* const newCode = 'new'
|
|
175
228
|
* >>>>>>> NEW
|
|
176
|
-
*
|
|
177
|
-
* const blocks = extractCurrentNewBlocks(input)
|
|
229
|
+
* `
|
|
230
|
+
* const blocks = extractCurrentNewBlocks(input)
|
|
178
231
|
* // blocks will contain one CurrentNewBlock object with the parsed information
|
|
179
232
|
*/
|
|
180
233
|
const extractCurrentNewBlocks = (input) => {
|
|
@@ -183,10 +236,10 @@ const extractCurrentNewBlocks = (input) => {
|
|
|
183
236
|
const blocks = [];
|
|
184
237
|
let currentIndex = 0;
|
|
185
238
|
while (currentIndex < input.length) {
|
|
186
|
-
const indices = findBlockIndices(input, currentIndex);
|
|
239
|
+
const indices = (0, exports.findBlockIndices)(input, currentIndex);
|
|
187
240
|
if (!indices)
|
|
188
241
|
break;
|
|
189
|
-
blocks.push(extractBlock(input, indices));
|
|
242
|
+
blocks.push((0, exports.extractBlock)(input, indices));
|
|
190
243
|
currentIndex = indices.end + BLOCK_END.length;
|
|
191
244
|
}
|
|
192
245
|
return blocks;
|
|
@@ -206,10 +259,12 @@ const readFileContent = async (filePath) => {
|
|
|
206
259
|
const ensureDirectoryExists = async (filePath) => {
|
|
207
260
|
await fs_1.default.promises.mkdir(path_1.default.dirname(filePath), { recursive: true });
|
|
208
261
|
};
|
|
262
|
+
exports.ensureDirectoryExists = ensureDirectoryExists;
|
|
209
263
|
const writeFileContent = async (filePath, content) => {
|
|
210
|
-
await ensureDirectoryExists(filePath);
|
|
264
|
+
await (0, exports.ensureDirectoryExists)(filePath);
|
|
211
265
|
await fs_1.default.promises.writeFile(filePath, content);
|
|
212
266
|
};
|
|
267
|
+
exports.writeFileContent = writeFileContent;
|
|
213
268
|
/**
|
|
214
269
|
* Updates the content of a file by replacing a specified block of current content with new content.
|
|
215
270
|
*
|
|
@@ -224,72 +279,113 @@ const writeFileContent = async (filePath, content) => {
|
|
|
224
279
|
* @example
|
|
225
280
|
* const currentContent = `
|
|
226
281
|
* function example() {
|
|
227
|
-
* console.log('Old content')
|
|
282
|
+
* console.log('Old content')
|
|
228
283
|
* }
|
|
229
|
-
*
|
|
284
|
+
* `
|
|
230
285
|
* const block = {
|
|
231
|
-
* currentContent: "console.log('Old content')
|
|
232
|
-
* newContent: "console.log('New content')
|
|
233
|
-
* }
|
|
234
|
-
* const updatedContent = updateFileContent(currentContent, block)
|
|
286
|
+
* currentContent: "console.log('Old content')",
|
|
287
|
+
* newContent: "console.log('New content')"
|
|
288
|
+
* }
|
|
289
|
+
* const updatedContent = updateFileContent(currentContent, block)
|
|
235
290
|
* // updatedContent will be:
|
|
236
291
|
* // function example() {
|
|
237
|
-
* // console.log('New content')
|
|
292
|
+
* // console.log('New content')
|
|
238
293
|
* // }
|
|
239
294
|
*/
|
|
240
295
|
const updateFileContent = (currentContent, block) => {
|
|
296
|
+
// 1) if block.currentContent.trim() is empty then return block.newContent
|
|
241
297
|
if (block.currentContent.trim() === '') {
|
|
242
298
|
return block.newContent;
|
|
243
299
|
}
|
|
244
|
-
//
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
//
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
300
|
+
// 2) split currentContent and block.currentContent into new lines and trim() all the lines, then put back the new lines
|
|
301
|
+
const currentLines = currentContent.split('\n').map((line) => line.trim());
|
|
302
|
+
const blockLines = block.currentContent.split('\n').map((line) => line.trim());
|
|
303
|
+
// 3) see if the trimmed block.currentContent matches a section of code in the trimmed currentContent
|
|
304
|
+
const blockContent = blockLines.join('\n');
|
|
305
|
+
const currentContentTrimmed = currentLines.join('\n');
|
|
306
|
+
// 4) if so, make a variable named matchingLine with the line number of which line the block.currentContent starts on in currentContent
|
|
307
|
+
const matchingLine = currentContentTrimmed.indexOf(blockContent);
|
|
308
|
+
if (matchingLine === -1) {
|
|
309
|
+
return currentContent; // If no match is found, return the original content
|
|
310
|
+
}
|
|
311
|
+
// 5) compare the first line of block.currentContent with the matchingLine line of currentContent, putting any preceeding spaces that are in currentContent but not in block.currentContent into a variable named preceedingSpaces
|
|
312
|
+
const firstLineIndex = currentContent.indexOf(blockLines[0]);
|
|
313
|
+
const precedingSpaces = currentContent
|
|
314
|
+
.substring(firstLineIndex, firstLineIndex + currentLines[0].length)
|
|
315
|
+
.match(/^\s*/);
|
|
316
|
+
// 6) add preceedingSpaces to the start of each line in both block.currentContent and block.newContent
|
|
317
|
+
if (precedingSpaces) {
|
|
318
|
+
const precedingSpacesValue = precedingSpaces[0];
|
|
319
|
+
const indentedNewContent = block.newContent
|
|
320
|
+
.split('\n')
|
|
321
|
+
.map((line) => precedingSpacesValue + line)
|
|
322
|
+
.join('\n');
|
|
323
|
+
// 7) finally, do a simple currentContent.replace(block.currentContent, block.newContent) with the modified (added preceeding spaces to match) variables
|
|
324
|
+
return currentContent.replace(block.currentContent, indentedNewContent);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
return currentContent.replace(block.currentContent, block.newContent);
|
|
251
328
|
}
|
|
252
|
-
// Determine the indentation level of the current content in the file
|
|
253
|
-
const linesBefore = normalizedFileContent.substring(0, index).split('\n');
|
|
254
|
-
const lastLineBefore = linesBefore[linesBefore.length - 1];
|
|
255
|
-
const indentationMatch = lastLineBefore.match(/^\s*/);
|
|
256
|
-
const indentation = indentationMatch ? indentationMatch[0] : '';
|
|
257
|
-
// Calculate missing leading spaces in the current content
|
|
258
|
-
const currentContentLines = block.currentContent.split('\n');
|
|
259
|
-
const fileContentLines = normalizedFileContent.substring(index, index + normalizedCurrentContent.length).split('\n');
|
|
260
|
-
const missingSpaces = currentContentLines.map((line, i) => {
|
|
261
|
-
const fileLine = fileContentLines[i] || '';
|
|
262
|
-
const lineIndentationMatch = fileLine.match(/^\s*/);
|
|
263
|
-
const lineIndentation = lineIndentationMatch ? lineIndentationMatch[0] : '';
|
|
264
|
-
return Math.max(0, lineIndentation.length - line.length + line.trimStart().length);
|
|
265
|
-
});
|
|
266
|
-
// Apply the adjusted indentation to the new content
|
|
267
|
-
const indentedNewContent = block.newContent
|
|
268
|
-
.split('\n')
|
|
269
|
-
.map((line, i) => {
|
|
270
|
-
const additionalSpaces = ' '.repeat(missingSpaces[i] || 0);
|
|
271
|
-
return line.trim() ? indentation + additionalSpaces + line : line;
|
|
272
|
-
})
|
|
273
|
-
.join('\n');
|
|
274
|
-
// Replace the current content with the indented new content
|
|
275
|
-
return currentContent.replace(block.currentContent, indentedNewContent);
|
|
276
329
|
};
|
|
330
|
+
exports.updateFileContent = updateFileContent;
|
|
331
|
+
/**
|
|
332
|
+
* Analyzes the content of a block against the file content to find matching lines.
|
|
333
|
+
*
|
|
334
|
+
* This function compares the trimmed content of a block with the trimmed content of a file
|
|
335
|
+
* to identify all occurrences of the block content within the file.
|
|
336
|
+
*
|
|
337
|
+
* @param {CurrentNewBlock} block - The block containing the content to be searched for.
|
|
338
|
+
* @param {string} fileContent - The content of the file to be analyzed.
|
|
339
|
+
* @returns {Promise<{ matchingLines: number[]; currentContentTrimmed: string }>} A promise that resolves to an object containing:
|
|
340
|
+
* - matchingLines: An array of line numbers where the block content matches in the file.
|
|
341
|
+
* - currentContentTrimmed: The trimmed content of the file.
|
|
342
|
+
*/
|
|
343
|
+
const analyzeBlockContent = async (block, fileContent) => {
|
|
344
|
+
const currentLines = fileContent.split('\n').map((line) => line.trim());
|
|
345
|
+
const blockLines = block.currentContent.split('\n').map((line) => line.trim());
|
|
346
|
+
const blockContent = blockLines.join('\n');
|
|
347
|
+
const currentContentTrimmed = currentLines.join('\n');
|
|
348
|
+
const matchingLines = [];
|
|
349
|
+
let matchingLine = -1;
|
|
350
|
+
while ((matchingLine = currentContentTrimmed.indexOf(blockContent, matchingLine + 1)) !== -1) {
|
|
351
|
+
matchingLines.push(matchingLine);
|
|
352
|
+
}
|
|
353
|
+
return { matchingLines, currentContentTrimmed };
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Processes a single block by applying its changes to the corresponding file.
|
|
357
|
+
*
|
|
358
|
+
* This function handles the following scenarios:
|
|
359
|
+
* 1. Creating a new file if it doesn't exist.
|
|
360
|
+
* 2. Updating an existing file if the block's current content matches exactly once.
|
|
361
|
+
* 3. Logging errors if the current content is not found or matches multiple times.
|
|
362
|
+
*
|
|
363
|
+
* @param {CurrentNewBlock} block - The block containing the changes to be applied.
|
|
364
|
+
* @param {number} index - The index of the block being processed (used for logging).
|
|
365
|
+
* @returns {Promise<void>} A promise that resolves when the block has been processed.
|
|
366
|
+
*/
|
|
277
367
|
const processBlock = async (block, index) => {
|
|
278
368
|
const filePath = path_1.default.resolve(block.filePath);
|
|
279
369
|
let fileContent = await readFileContent(filePath);
|
|
280
370
|
if (fileContent === '') {
|
|
281
|
-
await writeFileContent(filePath, block.newContent);
|
|
371
|
+
await (0, exports.writeFileContent)(filePath, block.newContent);
|
|
282
372
|
console.log(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} has been ${colors_1.default.green.bold('created')}`);
|
|
283
373
|
return;
|
|
284
374
|
}
|
|
285
|
-
|
|
286
|
-
|
|
375
|
+
const { matchingLines } = await analyzeBlockContent(block, fileContent);
|
|
376
|
+
if (matchingLines.length === 0) {
|
|
377
|
+
console.log(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold('ERROR: Current content not found to replace in the file')}`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
else if (matchingLines.length > 1) {
|
|
381
|
+
console.log(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold(`ERROR: Current content matches multiple (${matchingLines.length}) parts in the file`)}`);
|
|
287
382
|
return;
|
|
288
383
|
}
|
|
289
|
-
const updatedContent = updateFileContent(fileContent, block);
|
|
290
|
-
await writeFileContent(filePath, updatedContent);
|
|
384
|
+
const updatedContent = (0, exports.updateFileContent)(fileContent, block);
|
|
385
|
+
await (0, exports.writeFileContent)(filePath, updatedContent);
|
|
291
386
|
console.log(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} has been ${colors_1.default.yellow.bold('updated')}`);
|
|
292
387
|
};
|
|
388
|
+
exports.processBlock = processBlock;
|
|
293
389
|
/**
|
|
294
390
|
* Applies and saves the changes specified in the CurrentNewBlock array.
|
|
295
391
|
*
|
|
@@ -310,14 +406,14 @@ const processBlock = async (block, index) => {
|
|
|
310
406
|
* const blocks = [
|
|
311
407
|
* { filePath: 'path/to/file.ts', currentContent: 'old code', newContent: 'new code' },
|
|
312
408
|
* { filePath: 'path/to/newfile.ts', currentContent: '', newContent: 'new file content' }
|
|
313
|
-
* ]
|
|
314
|
-
* await applyAndSaveCurrentNewBlocks(blocks)
|
|
409
|
+
* ]
|
|
410
|
+
* await applyAndSaveCurrentNewBlocks(blocks)
|
|
315
411
|
*/
|
|
316
412
|
const applyAndSaveCurrentNewBlocks = async (blocks) => {
|
|
317
413
|
console.log(`\nApplying and saving current/new blocks...\n`);
|
|
318
414
|
for (const [index, block] of blocks.entries()) {
|
|
319
415
|
try {
|
|
320
|
-
await processBlock(block, index);
|
|
416
|
+
await (0, exports.processBlock)(block, index);
|
|
321
417
|
}
|
|
322
418
|
catch (error) {
|
|
323
419
|
console.error(`Error processing ${colors_1.default.red(block.filePath)}:`, error);
|
|
@@ -325,3 +421,52 @@ const applyAndSaveCurrentNewBlocks = async (blocks) => {
|
|
|
325
421
|
}
|
|
326
422
|
};
|
|
327
423
|
exports.applyAndSaveCurrentNewBlocks = applyAndSaveCurrentNewBlocks;
|
|
424
|
+
/**
|
|
425
|
+
* Checks if all the blocks can be applied without making any changes.
|
|
426
|
+
*
|
|
427
|
+
* This function iterates through an array of CurrentNewBlock objects and checks
|
|
428
|
+
* if each block can be applied to its respective file. It returns a tuple where
|
|
429
|
+
* the first element is a boolean indicating if all blocks can be applied, and
|
|
430
|
+
* the second element is a string summarizing the success or failure of each block.
|
|
431
|
+
*
|
|
432
|
+
* @param {CurrentNewBlock[]} blocks - An array of CurrentNewBlock objects to be checked.
|
|
433
|
+
* @returns {Promise<[boolean, string]>} A promise that resolves to a tuple containing
|
|
434
|
+
* a boolean and a summary string.
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* const blocks = [
|
|
438
|
+
* { filePath: 'path/to/file.ts', currentContent: 'old code', newContent: 'new code' },
|
|
439
|
+
* { filePath: 'path/to/newfile.ts', currentContent: '', newContent: 'new file content' }
|
|
440
|
+
* ]
|
|
441
|
+
* const [canApply, summary] = await checkBlocksApplicability(blocks)
|
|
442
|
+
*/
|
|
443
|
+
const checkBlocksApplicability = async (blocks) => {
|
|
444
|
+
let allApplicable = true;
|
|
445
|
+
const summary = [];
|
|
446
|
+
for (const [index, block] of blocks.entries()) {
|
|
447
|
+
const filePath = path_1.default.resolve(block.filePath);
|
|
448
|
+
let fileContent = await readFileContent(filePath);
|
|
449
|
+
if (fileContent === '') {
|
|
450
|
+
summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.green.bold('created')}`);
|
|
451
|
+
}
|
|
452
|
+
else if (block.currentContent.trim() !== '') {
|
|
453
|
+
const { matchingLines } = await analyzeBlockContent(block, fileContent);
|
|
454
|
+
if (matchingLines.length === 0) {
|
|
455
|
+
allApplicable = false;
|
|
456
|
+
summary.push(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold('cannot be applied: Current content not found')}`);
|
|
457
|
+
}
|
|
458
|
+
else if (matchingLines.length > 1) {
|
|
459
|
+
allApplicable = false;
|
|
460
|
+
summary.push(`[${index + 1}] ${colors_1.default.red.bold(block.filePath)} ${colors_1.default.red.bold(`cannot be applied: Current content matches multiple (${matchingLines.length}) parts in the file`)}`);
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.yellow.bold('updated')}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
summary.push(`[${index + 1}] ${colors_1.default.green.bold(block.filePath)} can be ${colors_1.default.yellow.bold('updated')}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return [allApplicable, summary.join('\n')];
|
|
471
|
+
};
|
|
472
|
+
exports.checkBlocksApplicability = checkBlocksApplicability;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"packageManager": "yarn@1.22.22",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"watch": "tsc -w",
|
|
11
11
|
"check": "bash -c 'npx tsc --noEmit --skipLibCheck'",
|
|
12
12
|
"test": "bash -c jest",
|
|
13
|
-
"prepare": "husky"
|
|
13
|
+
"prepare": "husky",
|
|
14
|
+
"format": "prettier --write \"src/**/*.ts\""
|
|
14
15
|
},
|
|
15
16
|
"bin": "./dist/index.js",
|
|
16
17
|
"files": [
|
|
@@ -23,7 +24,8 @@
|
|
|
23
24
|
"description": "",
|
|
24
25
|
"lint-staged": {
|
|
25
26
|
"*.ts": [
|
|
26
|
-
"yarn run check"
|
|
27
|
+
"yarn run check",
|
|
28
|
+
"prettier --write"
|
|
27
29
|
]
|
|
28
30
|
},
|
|
29
31
|
"lint-prepush": {
|
|
@@ -31,7 +33,8 @@
|
|
|
31
33
|
"tasks": {
|
|
32
34
|
"*.ts": [
|
|
33
35
|
"yarn run check",
|
|
34
|
-
"yarn run test"
|
|
36
|
+
"yarn run test",
|
|
37
|
+
"yarn run format"
|
|
35
38
|
]
|
|
36
39
|
}
|
|
37
40
|
},
|
|
@@ -42,14 +45,14 @@
|
|
|
42
45
|
"colors": "^1.4.0",
|
|
43
46
|
"commander": "^12.1.0",
|
|
44
47
|
"copy-paste": "^1.5.3",
|
|
45
|
-
"eslint": "^
|
|
48
|
+
"eslint": "^8.57.0",
|
|
46
49
|
"eslint-config-prettier": "^9.1.0",
|
|
47
50
|
"eslint-config-raine": "^0.5.0",
|
|
48
51
|
"eslint-config-standard": "^17.1.0",
|
|
49
52
|
"eslint-plugin-import": "^2.29.1",
|
|
50
53
|
"eslint-plugin-jsdoc": "^50.1.0",
|
|
51
|
-
"eslint-plugin-n": "^
|
|
52
|
-
"eslint-plugin-promise": "^
|
|
54
|
+
"eslint-plugin-n": "^16.6.2",
|
|
55
|
+
"eslint-plugin-promise": "^6.6.0",
|
|
53
56
|
"express": "^4.19.2",
|
|
54
57
|
"ignore": "^5.3.2",
|
|
55
58
|
"ini": "^4.1.3",
|
|
@@ -70,12 +73,14 @@
|
|
|
70
73
|
"jest": "^29.7.0",
|
|
71
74
|
"lint-prepush": "^2.2.2",
|
|
72
75
|
"lint-staged": "^15.2.9",
|
|
76
|
+
"prettier": "^3.2.5",
|
|
73
77
|
"ts-jest": "^29.2.4",
|
|
74
78
|
"typescript": "^5.5.4"
|
|
75
79
|
},
|
|
76
80
|
"resolutions": {
|
|
77
81
|
"eslint": "8.57.0",
|
|
78
82
|
"eslint-plugin-n": "16.6.2",
|
|
79
|
-
"eslint-plugin-import": "2.29.1"
|
|
83
|
+
"eslint-plugin-import": "2.29.1",
|
|
84
|
+
"eslint-plugin-promise": "6.6.0"
|
|
80
85
|
}
|
|
81
86
|
}
|