codebakers 1.0.45
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/.vscodeignore +18 -0
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/codebakers-1.0.0.vsix +0 -0
- package/codebakers-1.0.10.vsix +0 -0
- package/codebakers-1.0.11.vsix +0 -0
- package/codebakers-1.0.12.vsix +0 -0
- package/codebakers-1.0.13.vsix +0 -0
- package/codebakers-1.0.14.vsix +0 -0
- package/codebakers-1.0.15.vsix +0 -0
- package/codebakers-1.0.16.vsix +0 -0
- package/codebakers-1.0.17.vsix +0 -0
- package/codebakers-1.0.18.vsix +0 -0
- package/codebakers-1.0.19.vsix +0 -0
- package/codebakers-1.0.20.vsix +0 -0
- package/codebakers-1.0.21.vsix +0 -0
- package/codebakers-1.0.22.vsix +0 -0
- package/codebakers-1.0.23.vsix +0 -0
- package/codebakers-1.0.24.vsix +0 -0
- package/codebakers-1.0.25.vsix +0 -0
- package/codebakers-1.0.26.vsix +0 -0
- package/codebakers-1.0.27.vsix +0 -0
- package/codebakers-1.0.28.vsix +0 -0
- package/codebakers-1.0.29.vsix +0 -0
- package/codebakers-1.0.30.vsix +0 -0
- package/codebakers-1.0.31.vsix +0 -0
- package/codebakers-1.0.32.vsix +0 -0
- package/codebakers-1.0.35.vsix +0 -0
- package/codebakers-1.0.36.vsix +0 -0
- package/codebakers-1.0.37.vsix +0 -0
- package/codebakers-1.0.38.vsix +0 -0
- package/codebakers-1.0.39.vsix +0 -0
- package/codebakers-1.0.40.vsix +0 -0
- package/codebakers-1.0.41.vsix +0 -0
- package/codebakers-1.0.42.vsix +0 -0
- package/codebakers-1.0.43.vsix +0 -0
- package/codebakers-1.0.44.vsix +0 -0
- package/codebakers-1.0.45.vsix +0 -0
- package/dist/extension.js +1394 -0
- package/esbuild.js +63 -0
- package/media/icon.png +0 -0
- package/media/icon.svg +7 -0
- package/nul +1 -0
- package/package.json +127 -0
- package/preview.html +547 -0
- package/src/ChatPanelProvider.ts +1815 -0
- package/src/ChatViewProvider.ts +749 -0
- package/src/CodeBakersClient.ts +1146 -0
- package/src/CodeValidator.ts +645 -0
- package/src/FileOperations.ts +410 -0
- package/src/ProjectContext.ts +526 -0
- package/src/extension.ts +332 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import * as vscode from 'vscode';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
|
|
5
|
+
export interface FileChange {
|
|
6
|
+
path: string;
|
|
7
|
+
action: 'create' | 'edit' | 'delete';
|
|
8
|
+
content?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CommandToRun {
|
|
13
|
+
command: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface OperationResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
error?: string;
|
|
20
|
+
backup?: FileBackup;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FileBackup {
|
|
24
|
+
path: string;
|
|
25
|
+
originalContent: string | null; // null if file didn't exist
|
|
26
|
+
timestamp: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class FileOperations {
|
|
30
|
+
private workspaceRoot: string | undefined;
|
|
31
|
+
private _backups: Map<string, FileBackup> = new Map();
|
|
32
|
+
private _operationLock: Set<string> = new Set(); // Prevent concurrent operations on same file
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a file exists
|
|
40
|
+
*/
|
|
41
|
+
async fileExists(relativePath: string): Promise<boolean> {
|
|
42
|
+
if (!this.workspaceRoot) return false;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const fullPath = path.join(this.workspaceRoot, relativePath);
|
|
46
|
+
await vscode.workspace.fs.stat(vscode.Uri.file(fullPath));
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get a lock on a file path to prevent concurrent operations
|
|
55
|
+
*/
|
|
56
|
+
private async acquireLock(relativePath: string, timeoutMs: number = 5000): Promise<boolean> {
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
|
|
59
|
+
while (this._operationLock.has(relativePath)) {
|
|
60
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
61
|
+
console.error(`FileOperations: Timeout waiting for lock on ${relativePath}`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._operationLock.add(relativePath);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private releaseLock(relativePath: string): void {
|
|
72
|
+
this._operationLock.delete(relativePath);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create a backup of a file before modifying it
|
|
77
|
+
*/
|
|
78
|
+
private async createBackup(relativePath: string): Promise<FileBackup> {
|
|
79
|
+
const content = await this.readFile(relativePath);
|
|
80
|
+
const backup: FileBackup = {
|
|
81
|
+
path: relativePath,
|
|
82
|
+
originalContent: content,
|
|
83
|
+
timestamp: Date.now()
|
|
84
|
+
};
|
|
85
|
+
this._backups.set(relativePath, backup);
|
|
86
|
+
return backup;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Restore a file from backup
|
|
91
|
+
*/
|
|
92
|
+
async restoreFromBackup(relativePath: string): Promise<boolean> {
|
|
93
|
+
const backup = this._backups.get(relativePath);
|
|
94
|
+
if (!backup) {
|
|
95
|
+
vscode.window.showWarningMessage(`No backup found for ${relativePath}`);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
if (backup.originalContent === null) {
|
|
101
|
+
// File didn't exist before - delete it
|
|
102
|
+
await this.deleteFile(relativePath);
|
|
103
|
+
} else {
|
|
104
|
+
// Restore original content
|
|
105
|
+
await this.writeFile(relativePath, backup.originalContent);
|
|
106
|
+
}
|
|
107
|
+
this._backups.delete(relativePath);
|
|
108
|
+
vscode.window.showInformationMessage(`✅ Restored ${relativePath}`);
|
|
109
|
+
return true;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
vscode.window.showErrorMessage(`Failed to restore ${relativePath}: ${error}`);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get all available backups
|
|
118
|
+
*/
|
|
119
|
+
getBackups(): FileBackup[] {
|
|
120
|
+
return Array.from(this._backups.values());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Clear old backups (older than 1 hour)
|
|
125
|
+
*/
|
|
126
|
+
cleanupOldBackups(): void {
|
|
127
|
+
const oneHourAgo = Date.now() - (60 * 60 * 1000);
|
|
128
|
+
for (const [path, backup] of this._backups) {
|
|
129
|
+
if (backup.timestamp < oneHourAgo) {
|
|
130
|
+
this._backups.delete(path);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Read a file from the workspace
|
|
137
|
+
*/
|
|
138
|
+
async readFile(relativePath: string): Promise<string | null> {
|
|
139
|
+
if (!this.workspaceRoot) return null;
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const fullPath = path.join(this.workspaceRoot, relativePath);
|
|
143
|
+
const uri = vscode.Uri.file(fullPath);
|
|
144
|
+
const content = await vscode.workspace.fs.readFile(uri);
|
|
145
|
+
return Buffer.from(content).toString('utf-8');
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error(`Failed to read file ${relativePath}:`, error);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Write content to a file
|
|
154
|
+
*/
|
|
155
|
+
async writeFile(relativePath: string, content: string): Promise<boolean> {
|
|
156
|
+
if (!this.workspaceRoot) {
|
|
157
|
+
vscode.window.showErrorMessage('No workspace folder open');
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const fullPath = path.join(this.workspaceRoot, relativePath);
|
|
163
|
+
const uri = vscode.Uri.file(fullPath);
|
|
164
|
+
|
|
165
|
+
// Ensure directory exists
|
|
166
|
+
const dir = path.dirname(fullPath);
|
|
167
|
+
if (!fs.existsSync(dir)) {
|
|
168
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await vscode.workspace.fs.writeFile(uri, Buffer.from(content, 'utf-8'));
|
|
172
|
+
return true;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`Failed to write file ${relativePath}:`, error);
|
|
175
|
+
vscode.window.showErrorMessage(`Failed to write ${relativePath}: ${error}`);
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Delete a file with proper error handling
|
|
182
|
+
*/
|
|
183
|
+
async deleteFile(relativePath: string): Promise<boolean> {
|
|
184
|
+
if (!this.workspaceRoot) {
|
|
185
|
+
console.error('FileOperations: No workspace root');
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const fullPath = path.join(this.workspaceRoot, relativePath);
|
|
191
|
+
const uri = vscode.Uri.file(fullPath);
|
|
192
|
+
|
|
193
|
+
// Check if file exists first
|
|
194
|
+
const exists = await this.fileExists(relativePath);
|
|
195
|
+
if (!exists) {
|
|
196
|
+
console.log(`FileOperations: File ${relativePath} doesn't exist, treating as success`);
|
|
197
|
+
return true; // File doesn't exist - that's fine, we wanted it gone anyway
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await vscode.workspace.fs.delete(uri);
|
|
201
|
+
console.log(`FileOperations: Deleted ${relativePath}`);
|
|
202
|
+
return true;
|
|
203
|
+
} catch (error: any) {
|
|
204
|
+
// Handle "file not found" as success (already deleted)
|
|
205
|
+
if (error?.code === 'FileNotFound' || error?.code === 'ENOENT') {
|
|
206
|
+
console.log(`FileOperations: File ${relativePath} already deleted`);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
console.error(`FileOperations: Failed to delete ${relativePath}:`, error);
|
|
210
|
+
vscode.window.showErrorMessage(`Failed to delete ${relativePath}: ${error.message || error}`);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Show diff between current file and proposed content
|
|
217
|
+
*/
|
|
218
|
+
async showDiff(relativePath: string, newContent: string, title?: string): Promise<void> {
|
|
219
|
+
if (!this.workspaceRoot) return;
|
|
220
|
+
|
|
221
|
+
const fullPath = path.join(this.workspaceRoot, relativePath);
|
|
222
|
+
const originalUri = vscode.Uri.file(fullPath);
|
|
223
|
+
|
|
224
|
+
// Create a virtual document for the new content
|
|
225
|
+
const newUri = vscode.Uri.parse(`codebakers-diff:${relativePath}?content=${encodeURIComponent(newContent)}`);
|
|
226
|
+
|
|
227
|
+
const diffTitle = title || `CodeBakers: ${relativePath}`;
|
|
228
|
+
|
|
229
|
+
await vscode.commands.executeCommand('vscode.diff', originalUri, newUri, diffTitle);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Apply a single file change with locking and backup
|
|
234
|
+
*/
|
|
235
|
+
async applyChange(change: FileChange): Promise<boolean> {
|
|
236
|
+
// Acquire lock to prevent concurrent operations on same file
|
|
237
|
+
const lockAcquired = await this.acquireLock(change.path);
|
|
238
|
+
if (!lockAcquired) {
|
|
239
|
+
vscode.window.showErrorMessage(`Cannot modify ${change.path} - another operation is in progress`);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
// Create backup before any modification
|
|
245
|
+
if (change.action === 'edit' || change.action === 'delete') {
|
|
246
|
+
await this.createBackup(change.path);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
switch (change.action) {
|
|
250
|
+
case 'create':
|
|
251
|
+
if (!change.content) {
|
|
252
|
+
vscode.window.showErrorMessage(`Cannot create ${change.path} - no content provided`);
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
// Check if file already exists
|
|
256
|
+
const existsForCreate = await this.fileExists(change.path);
|
|
257
|
+
if (existsForCreate) {
|
|
258
|
+
// It's actually an edit, create backup
|
|
259
|
+
await this.createBackup(change.path);
|
|
260
|
+
}
|
|
261
|
+
return this.writeFile(change.path, change.content);
|
|
262
|
+
|
|
263
|
+
case 'edit':
|
|
264
|
+
if (!change.content) {
|
|
265
|
+
vscode.window.showErrorMessage(`Cannot edit ${change.path} - no content provided`);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
// Check if file exists
|
|
269
|
+
const existsForEdit = await this.fileExists(change.path);
|
|
270
|
+
if (!existsForEdit) {
|
|
271
|
+
console.log(`FileOperations: File ${change.path} doesn't exist, creating instead of editing`);
|
|
272
|
+
}
|
|
273
|
+
return this.writeFile(change.path, change.content);
|
|
274
|
+
|
|
275
|
+
case 'delete':
|
|
276
|
+
return this.deleteFile(change.path);
|
|
277
|
+
|
|
278
|
+
default:
|
|
279
|
+
vscode.window.showErrorMessage(`Unknown action: ${(change as any).action}`);
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
} finally {
|
|
283
|
+
// Always release the lock
|
|
284
|
+
this.releaseLock(change.path);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Apply multiple file changes
|
|
290
|
+
*/
|
|
291
|
+
async applyChanges(changes: FileChange[]): Promise<{ success: number; failed: number }> {
|
|
292
|
+
let success = 0;
|
|
293
|
+
let failed = 0;
|
|
294
|
+
|
|
295
|
+
for (const change of changes) {
|
|
296
|
+
const result = await this.applyChange(change);
|
|
297
|
+
if (result) {
|
|
298
|
+
success++;
|
|
299
|
+
} else {
|
|
300
|
+
failed++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { success, failed };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Run a command in the integrated terminal
|
|
309
|
+
*/
|
|
310
|
+
async runCommand(command: string, name?: string): Promise<void> {
|
|
311
|
+
const terminal = vscode.window.createTerminal({
|
|
312
|
+
name: name || 'CodeBakers',
|
|
313
|
+
cwd: this.workspaceRoot
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
terminal.show();
|
|
317
|
+
terminal.sendText(command);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Run a command and capture output (for quick commands)
|
|
322
|
+
*/
|
|
323
|
+
async runCommandWithOutput(command: string): Promise<string> {
|
|
324
|
+
return new Promise((resolve, reject) => {
|
|
325
|
+
const { exec } = require('child_process');
|
|
326
|
+
|
|
327
|
+
exec(command, { cwd: this.workspaceRoot, timeout: 30000 }, (error: any, stdout: string, stderr: string) => {
|
|
328
|
+
if (error) {
|
|
329
|
+
reject(new Error(stderr || error.message));
|
|
330
|
+
} else {
|
|
331
|
+
resolve(stdout);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get list of files in workspace matching a pattern
|
|
339
|
+
*/
|
|
340
|
+
async findFiles(pattern: string, exclude?: string): Promise<string[]> {
|
|
341
|
+
const files = await vscode.workspace.findFiles(pattern, exclude || '**/node_modules/**');
|
|
342
|
+
return files.map(f => vscode.workspace.asRelativePath(f));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Open a file in the editor
|
|
347
|
+
*/
|
|
348
|
+
async openFile(relativePath: string, selection?: { line: number; column?: number }): Promise<void> {
|
|
349
|
+
if (!this.workspaceRoot) return;
|
|
350
|
+
|
|
351
|
+
const fullPath = path.join(this.workspaceRoot, relativePath);
|
|
352
|
+
const uri = vscode.Uri.file(fullPath);
|
|
353
|
+
const doc = await vscode.workspace.openTextDocument(uri);
|
|
354
|
+
const editor = await vscode.window.showTextDocument(doc);
|
|
355
|
+
|
|
356
|
+
if (selection) {
|
|
357
|
+
const pos = new vscode.Position(selection.line - 1, (selection.column || 1) - 1);
|
|
358
|
+
editor.selection = new vscode.Selection(pos, pos);
|
|
359
|
+
editor.revealRange(new vscode.Range(pos, pos), vscode.TextEditorRevealType.InCenter);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Get the currently open file content and path
|
|
365
|
+
*/
|
|
366
|
+
getCurrentFile(): { path: string; content: string; selection?: string } | null {
|
|
367
|
+
const editor = vscode.window.activeTextEditor;
|
|
368
|
+
if (!editor) return null;
|
|
369
|
+
|
|
370
|
+
const relativePath = vscode.workspace.asRelativePath(editor.document.uri);
|
|
371
|
+
const content = editor.document.getText();
|
|
372
|
+
const selection = editor.selection.isEmpty ? undefined : editor.document.getText(editor.selection);
|
|
373
|
+
|
|
374
|
+
return { path: relativePath, content, selection };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Get workspace file tree (top-level structure)
|
|
379
|
+
*/
|
|
380
|
+
async getFileTree(maxDepth: number = 2): Promise<string[]> {
|
|
381
|
+
const files: string[] = [];
|
|
382
|
+
|
|
383
|
+
const addFiles = async (dir: string, depth: number) => {
|
|
384
|
+
if (depth > maxDepth) return;
|
|
385
|
+
|
|
386
|
+
const pattern = new vscode.RelativePattern(dir, '*');
|
|
387
|
+
const entries = await vscode.workspace.findFiles(pattern, '**/node_modules/**', 100);
|
|
388
|
+
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
const relativePath = vscode.workspace.asRelativePath(entry);
|
|
391
|
+
files.push(relativePath);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
if (this.workspaceRoot) {
|
|
396
|
+
await addFiles(this.workspaceRoot, 0);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return files.sort();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Virtual document provider for diff view
|
|
404
|
+
export class DiffContentProvider implements vscode.TextDocumentContentProvider {
|
|
405
|
+
provideTextDocumentContent(uri: vscode.Uri): string {
|
|
406
|
+
const params = new URLSearchParams(uri.query);
|
|
407
|
+
const content = params.get('content');
|
|
408
|
+
return content ? decodeURIComponent(content) : '';
|
|
409
|
+
}
|
|
410
|
+
}
|