@tng-sh/js 0.0.9 â 0.1.1
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/bin/tng.js +85 -21
- package/binaries/go-ui-darwin-amd64 +0 -0
- package/binaries/go-ui-darwin-arm64 +0 -0
- package/binaries/go-ui-linux-amd64 +0 -0
- package/binaries/go-ui-linux-arm64 +0 -0
- package/index.d.ts +35 -1
- package/index.js +5 -2
- package/lib/auditWorker.js +28 -0
- package/lib/config.js +1 -0
- package/lib/fixApplier.js +59 -0
- package/lib/generateTestsUi.js +289 -98
- package/lib/goUiSession.js +140 -71
- package/lib/jsonSession.js +49 -27
- package/package.json +1 -1
- package/tng_sh_js.darwin-arm64.node +0 -0
- package/tng_sh_js.darwin-x64.node +0 -0
- package/tng_sh_js.linux-arm64-gnu.node +0 -0
- package/tng_sh_js.linux-x64-gnu.node +0 -0
package/lib/generateTestsUi.js
CHANGED
|
@@ -3,9 +3,10 @@ const path = require('path');
|
|
|
3
3
|
const glob = require('fast-glob');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const GoUISession = require('./goUiSession');
|
|
6
|
-
const { getFileOutline,
|
|
6
|
+
const { getFileOutline, generateTest, ping, getUserStats } = require('../index');
|
|
7
7
|
const { loadConfig } = require('./config');
|
|
8
8
|
const { saveTestFile } = require('./saveFile');
|
|
9
|
+
const { applyFix } = require('./fixApplier');
|
|
9
10
|
|
|
10
11
|
class GenerateTestsUI {
|
|
11
12
|
constructor(cliMode = false) {
|
|
@@ -52,16 +53,19 @@ class GenerateTestsUI {
|
|
|
52
53
|
config_file: "tng.config.js"
|
|
53
54
|
});
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
try {
|
|
57
|
+
spawnSync(this.goUiSession._binaryPath, ['config-missing', '--data', dataJson], {
|
|
58
|
+
stdio: 'inherit'
|
|
59
|
+
});
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error(chalk.red('\nFailed to show configuration error UI.\n'));
|
|
62
|
+
}
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
async _showStats() {
|
|
61
66
|
const config = loadConfig();
|
|
62
67
|
if (!config.API_KEY) {
|
|
63
68
|
console.log(chalk.red('\nNo API key configured. Run: tng init\n'));
|
|
64
|
-
console.log(chalk.yellow('Or edit tng.config.js and set your API_KEY\n'));
|
|
65
69
|
return;
|
|
66
70
|
}
|
|
67
71
|
|
|
@@ -78,7 +82,6 @@ class GenerateTestsUI {
|
|
|
78
82
|
const config = loadConfig();
|
|
79
83
|
if (!config.API_KEY) {
|
|
80
84
|
console.log(chalk.red('\nNo API key configured. Run: tng init\n'));
|
|
81
|
-
console.log(chalk.yellow('Or edit tng.config.js and set your API_KEY\n'));
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
87
|
|
|
@@ -96,7 +99,7 @@ class GenerateTestsUI {
|
|
|
96
99
|
const files = await this._getUserFiles();
|
|
97
100
|
|
|
98
101
|
if (files.length === 0) {
|
|
99
|
-
console.log(chalk.yellow('
|
|
102
|
+
console.log(chalk.yellow('\nNo JavaScript or TypeScript files found in your project.\n'));
|
|
100
103
|
return 'back';
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -124,8 +127,8 @@ class GenerateTestsUI {
|
|
|
124
127
|
const result = getFileOutline(filePath);
|
|
125
128
|
outline = JSON.parse(result);
|
|
126
129
|
} catch (e) {
|
|
127
|
-
console.error(chalk.red(
|
|
128
|
-
return this._showFileSelection();
|
|
130
|
+
console.error(chalk.red(`\nError parsing file: ${e.message}\n`));
|
|
131
|
+
return this._showFileSelection(isAudit);
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
const methods = outline.methods || [];
|
|
@@ -144,8 +147,7 @@ class GenerateTestsUI {
|
|
|
144
147
|
const title = isAudit ? `Select Method to Audit for ${fileName}` : `Select Method for ${fileName}`;
|
|
145
148
|
const selectedDisplay = this.goUiSession.showListView(title, items);
|
|
146
149
|
|
|
147
|
-
if (selectedDisplay === 'back') return this._showFileSelection(isAudit);
|
|
148
|
-
if (!selectedDisplay) return this._showFileSelection(isAudit);
|
|
150
|
+
if (selectedDisplay === 'back' || !selectedDisplay) return this._showFileSelection(isAudit);
|
|
149
151
|
|
|
150
152
|
const selectedMethod = items.find(i => i.name === selectedDisplay)?.methodData;
|
|
151
153
|
|
|
@@ -154,7 +156,6 @@ class GenerateTestsUI {
|
|
|
154
156
|
if (testType === 'back') return this._showFileSelection(isAudit);
|
|
155
157
|
|
|
156
158
|
const finalType = testType === 'auto' ? null : testType;
|
|
157
|
-
|
|
158
159
|
const choice = await this._generateTestsForMethod(filePath, selectedMethod, finalType, isAudit);
|
|
159
160
|
|
|
160
161
|
if (isAudit) {
|
|
@@ -170,88 +171,215 @@ class GenerateTestsUI {
|
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
async _generateTestsForMethod(filePath, method, testType, isAudit = false) {
|
|
174
|
+
if (!this._hasApiKey()) {
|
|
175
|
+
return { error: 'No API key' };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (isAudit) {
|
|
179
|
+
return this._handleAuditFlow(filePath, method, testType);
|
|
180
|
+
}
|
|
181
|
+
|
|
173
182
|
const fileName = path.basename(filePath);
|
|
174
183
|
const displayName = method.class_name ? `${method.class_name}#${method.name}` : `${fileName}#${method.name}`;
|
|
175
|
-
|
|
184
|
+
return this._handleTestGenerationFlow(filePath, method, testType, displayName);
|
|
185
|
+
}
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
|
|
187
|
+
_hasApiKey() {
|
|
188
|
+
const config = loadConfig();
|
|
189
|
+
if (!config.API_KEY) {
|
|
190
|
+
const msg = 'No API key configured. Run: tng init';
|
|
191
|
+
if (this.cliMode) console.log(chalk.red(msg));
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
179
196
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
197
|
+
async _handleAuditFlow(filePath, method, testType) {
|
|
198
|
+
const { Worker } = require('worker_threads');
|
|
199
|
+
const streamingUi = await this.goUiSession.showStreamingAuditResults(
|
|
200
|
+
method.name,
|
|
201
|
+
method.class_name,
|
|
202
|
+
method.source_code || ''
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
if (!streamingUi) return null;
|
|
206
|
+
|
|
207
|
+
let results = {
|
|
208
|
+
issues: [],
|
|
209
|
+
behaviours: [],
|
|
210
|
+
method_name: method.name,
|
|
211
|
+
class_name: method.class_name,
|
|
212
|
+
method_source_with_lines: method.source_code
|
|
213
|
+
};
|
|
214
|
+
let auditFinished = false;
|
|
186
215
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
'behavior_expert_status': { label: 'Logic Generator', step: 4 },
|
|
192
|
-
'context_insights_status': { label: 'Context Insights', step: 5 }
|
|
193
|
-
};
|
|
216
|
+
// Start audit in background worker
|
|
217
|
+
const worker = new Worker(path.join(__dirname, 'auditWorker.js'), {
|
|
218
|
+
workerData: { filePath, methodName: method.name, className: method.class_name || null, testType: testType || null }
|
|
219
|
+
});
|
|
194
220
|
|
|
195
|
-
|
|
196
|
-
|
|
221
|
+
const itemIds = new Set();
|
|
222
|
+
worker.on('message', (msg) => {
|
|
223
|
+
if (msg.type === 'item') {
|
|
224
|
+
if (msg.data.startsWith('{')) {
|
|
225
|
+
streamingUi.write(msg.data);
|
|
226
|
+
try {
|
|
227
|
+
const data = JSON.parse(msg.data);
|
|
228
|
+
|
|
229
|
+
// Handle metadata updates from stream
|
|
230
|
+
if (data.method_name) results.method_name = data.method_name;
|
|
231
|
+
if (data.class_name) results.class_name = data.class_name;
|
|
232
|
+
if (data.method_source_with_lines) results.method_source_with_lines = data.method_source_with_lines;
|
|
233
|
+
if (data.source_code) results.method_source_with_lines = data.source_code;
|
|
234
|
+
|
|
235
|
+
// Only push if it looks like an actual audit item
|
|
236
|
+
if (data.test_name || data.summary || data.category) {
|
|
237
|
+
const id = `${data.category}-${data.test_name}-${data.line_number}`;
|
|
238
|
+
if (!itemIds.has(id)) {
|
|
239
|
+
if (data.category === 'behavior' || data.category === 'behaviour') results.behaviours.push(data);
|
|
240
|
+
else results.issues.push(data);
|
|
241
|
+
itemIds.add(id);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} catch (e) { }
|
|
197
245
|
}
|
|
246
|
+
} else if (msg.type === 'result') {
|
|
247
|
+
try {
|
|
248
|
+
const finalResults = typeof msg.data === 'string' ? JSON.parse(msg.data) : msg.data;
|
|
249
|
+
|
|
250
|
+
// Merge final results with our local ones, preserving 'fixed' state
|
|
251
|
+
const mergeList = (localList, incomingList) => {
|
|
252
|
+
if (!incomingList || incomingList.length === 0) return localList;
|
|
253
|
+
|
|
254
|
+
return incomingList.map(incomingItem => {
|
|
255
|
+
const localItem = localList.find(it =>
|
|
256
|
+
it.test_name === incomingItem.test_name &&
|
|
257
|
+
it.line_number === incomingItem.line_number
|
|
258
|
+
);
|
|
259
|
+
if (localItem && localItem.fixed) {
|
|
260
|
+
return { ...incomingItem, fixed: true };
|
|
261
|
+
}
|
|
262
|
+
return incomingItem;
|
|
263
|
+
});
|
|
264
|
+
};
|
|
198
265
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
filePath,
|
|
202
|
-
method.name,
|
|
203
|
-
method.class_name || null,
|
|
204
|
-
testType || null,
|
|
205
|
-
isAudit, // audit_mode
|
|
206
|
-
JSON.stringify(config),
|
|
207
|
-
(msg, percent) => {
|
|
208
|
-
try {
|
|
209
|
-
if (msg.startsWith('{')) {
|
|
210
|
-
const info = JSON.parse(msg);
|
|
266
|
+
results.issues = mergeList(results.issues, finalResults.issues || finalResults.findings);
|
|
267
|
+
results.behaviours = mergeList(results.behaviours, finalResults.behaviours);
|
|
211
268
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
269
|
+
if (finalResults.method_source_with_lines) results.method_source_with_lines = finalResults.method_source_with_lines;
|
|
270
|
+
if (finalResults.method_name) results.method_name = finalResults.method_name;
|
|
271
|
+
} catch (e) { }
|
|
272
|
+
auditFinished = true;
|
|
273
|
+
} else if (msg.type === 'error') {
|
|
274
|
+
console.error(chalk.red(`Audit error: ${msg.data}`));
|
|
275
|
+
auditFinished = true;
|
|
276
|
+
}
|
|
277
|
+
});
|
|
215
278
|
|
|
216
|
-
|
|
217
|
-
|
|
279
|
+
worker.on('error', (err) => {
|
|
280
|
+
console.error(chalk.red(`Worker error: ${err.message}`));
|
|
281
|
+
auditFinished = true;
|
|
282
|
+
});
|
|
218
283
|
|
|
219
|
-
|
|
220
|
-
|
|
284
|
+
try {
|
|
285
|
+
let currentChoice = null;
|
|
221
286
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
displayMsg += ` (${vals}${values.length > 2 ? '...' : ''})`;
|
|
225
|
-
}
|
|
287
|
+
// Wait for INITIAL streaming session to complete (user picks an action or quits)
|
|
288
|
+
await streamingUi.wait;
|
|
226
289
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
progress.update(msg, { percent });
|
|
235
|
-
}
|
|
290
|
+
const getResponse = (outputFile) => {
|
|
291
|
+
if (fs.existsSync(outputFile)) {
|
|
292
|
+
const output = fs.readFileSync(outputFile, 'utf8').trim();
|
|
293
|
+
if (output) {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = JSON.parse(output);
|
|
296
|
+
return typeof parsed === 'object' ? parsed : { action: output };
|
|
236
297
|
} catch (e) {
|
|
237
|
-
|
|
298
|
+
return { action: output };
|
|
238
299
|
}
|
|
239
300
|
}
|
|
240
|
-
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
currentChoice = getResponse(streamingUi.outputFile);
|
|
306
|
+
|
|
307
|
+
// Action loop: handles fix/open and returns to UI
|
|
308
|
+
while (currentChoice && (currentChoice.action === 'fix' || currentChoice.action === 'open')) {
|
|
309
|
+
const itemIndex = typeof currentChoice.index === 'number' ? currentChoice.index : 0;
|
|
310
|
+
|
|
311
|
+
if (currentChoice.action === 'fix') {
|
|
312
|
+
console.log(chalk.cyan(`Applying fix...`));
|
|
313
|
+
const fixResult = await applyFix(filePath, currentChoice.item);
|
|
314
|
+
if (fixResult.success) {
|
|
315
|
+
console.log(chalk.green(`â Fix applied successfully.`));
|
|
316
|
+
// Mark as fixed locally
|
|
317
|
+
[results.issues, results.behaviours].forEach(list => {
|
|
318
|
+
list.forEach(it => {
|
|
319
|
+
if (it.test_name === currentChoice.item.test_name && it.line_number === currentChoice.item.line_number) {
|
|
320
|
+
it.fixed = true;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
} else {
|
|
325
|
+
console.log(chalk.red(`â Failed to apply fix: ${fixResult.error || 'Unknown error'}`));
|
|
326
|
+
}
|
|
327
|
+
} else if (currentChoice.action === 'open') {
|
|
328
|
+
this._openInEditor(filePath, currentChoice.item.line_number);
|
|
329
|
+
}
|
|
241
330
|
|
|
242
|
-
if
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
331
|
+
// Ensure source has line numbers for static UI if it doesn't already
|
|
332
|
+
if (results.method_source_with_lines && !/^\s*\d+:/.test(results.method_source_with_lines)) {
|
|
333
|
+
results.method_source_with_lines = results.method_source_with_lines
|
|
334
|
+
.split('\n')
|
|
335
|
+
.map((line, i) => `${i + 1}: ${line}`)
|
|
336
|
+
.join('\n');
|
|
248
337
|
}
|
|
249
338
|
|
|
250
|
-
|
|
339
|
+
// Re-launch UI statically with updated results
|
|
340
|
+
const choiceStr = await this.goUiSession.showAuditResults(results, itemIndex);
|
|
341
|
+
if (choiceStr === 'back' || choiceStr === 'main_menu' || choiceStr === 'exit') {
|
|
342
|
+
currentChoice = null;
|
|
343
|
+
} else {
|
|
344
|
+
try {
|
|
345
|
+
const parsed = JSON.parse(choiceStr);
|
|
346
|
+
currentChoice = typeof parsed === 'object' ? parsed : { action: choiceStr };
|
|
347
|
+
} catch (e) {
|
|
348
|
+
currentChoice = { action: choiceStr };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
251
352
|
|
|
252
|
-
|
|
253
|
-
|
|
353
|
+
return { message: 'Audit complete', results };
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error(chalk.red(`\nAudit flow failed: ${e.message}\n`));
|
|
356
|
+
return null;
|
|
357
|
+
} finally {
|
|
358
|
+
await worker.terminate();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async _handleTestGenerationFlow(filePath, method, testType, displayName) {
|
|
363
|
+
const actionName = 'Generating test for';
|
|
364
|
+
|
|
365
|
+
const progressHandler = async (progress) => {
|
|
366
|
+
progress.update('Preparing request...');
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
const config = loadConfig();
|
|
254
370
|
|
|
371
|
+
const resultJson = generateTest(
|
|
372
|
+
filePath,
|
|
373
|
+
method.name,
|
|
374
|
+
method.class_name || null,
|
|
375
|
+
testType || null,
|
|
376
|
+
JSON.stringify(config),
|
|
377
|
+
(msg, percent) => this._updateProgress(progress, msg, percent)
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
progress.update('Saving generated tests...');
|
|
381
|
+
|
|
382
|
+
const fileInfo = await saveTestFile(resultJson);
|
|
255
383
|
return {
|
|
256
384
|
message: 'Tests generated successfully!',
|
|
257
385
|
resultJson,
|
|
@@ -264,23 +392,54 @@ class GenerateTestsUI {
|
|
|
264
392
|
};
|
|
265
393
|
|
|
266
394
|
const uiResult = await this.goUiSession.showProgress(`${actionName} ${displayName}`, progressHandler);
|
|
395
|
+
return uiResult || null;
|
|
396
|
+
}
|
|
267
397
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
398
|
+
_updateProgress(progress, msg, percent) {
|
|
399
|
+
const agentMap = {
|
|
400
|
+
'context_agent_status': { label: 'Context Builder', step: 1 },
|
|
401
|
+
'style_agent_status': { label: 'Style Analyzer', step: 2 },
|
|
402
|
+
'logical_issue_status': { label: 'Logic Analyzer', step: 3 },
|
|
403
|
+
'behavior_expert_status': { label: 'Logic Generator', step: 4 },
|
|
404
|
+
'context_insights_status': { label: 'Context Insights', step: 5 }
|
|
405
|
+
};
|
|
273
406
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
407
|
+
try {
|
|
408
|
+
if (msg.trim().startsWith('{')) {
|
|
409
|
+
const info = JSON.parse(msg);
|
|
410
|
+
|
|
411
|
+
for (const [key, config] of Object.entries(agentMap)) {
|
|
412
|
+
const item = info[key];
|
|
413
|
+
if (!item) continue;
|
|
277
414
|
|
|
278
|
-
|
|
415
|
+
const agentStatus = item.status || 'pending';
|
|
416
|
+
const values = item.values || [];
|
|
417
|
+
|
|
418
|
+
let displayMsg = `${config.label}: ${agentStatus.charAt(0).toUpperCase() + agentStatus.slice(1)}...`;
|
|
419
|
+
if (agentStatus === 'completed') displayMsg = `${config.label}: Completed`;
|
|
420
|
+
|
|
421
|
+
if (values.length > 0) {
|
|
422
|
+
const vals = values.map(v => v.toString().replace(/_/g, ' ')).slice(0, 2).join(', ');
|
|
423
|
+
displayMsg += ` (${vals}${values.length > 2 ? '...' : ''})`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
progress.update(displayMsg, {
|
|
427
|
+
percent: key === 'behavior_expert_status' ? percent : undefined,
|
|
428
|
+
explicit_step: config.step,
|
|
429
|
+
step_increment: false
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
progress.update(msg, { percent });
|
|
434
|
+
}
|
|
435
|
+
} catch (e) {
|
|
436
|
+
progress.update(msg, { percent });
|
|
437
|
+
}
|
|
279
438
|
}
|
|
280
439
|
|
|
281
440
|
_showPostGenerationMenu(result) {
|
|
282
441
|
const filePath = result.file_path;
|
|
283
|
-
const runCommand = `npm test ${filePath}`;
|
|
442
|
+
const runCommand = `npm test ${filePath}`;
|
|
284
443
|
|
|
285
444
|
while (true) {
|
|
286
445
|
const choice = this.goUiSession.showPostGenerationMenu(filePath, runCommand);
|
|
@@ -308,20 +467,17 @@ class GenerateTestsUI {
|
|
|
308
467
|
};
|
|
309
468
|
} catch (error) {
|
|
310
469
|
return {
|
|
311
|
-
success: true,
|
|
312
|
-
message: "Tests completed",
|
|
313
|
-
output: error.stdout + error.stderr,
|
|
314
|
-
exit_code: error.status
|
|
470
|
+
success: true,
|
|
471
|
+
message: "Tests completed with failures",
|
|
472
|
+
output: (error.stdout || '') + (error.stderr || ''),
|
|
473
|
+
exit_code: error.status || 1
|
|
315
474
|
};
|
|
316
475
|
}
|
|
317
476
|
};
|
|
318
477
|
|
|
319
478
|
const testOutput = this.goUiSession.showSpinner("Running tests...", spinnerHandler);
|
|
320
479
|
|
|
321
|
-
// Note: For simplicity in the first pass, we provide counts.
|
|
322
|
-
// We can enhance _parseTestOutput if needed.
|
|
323
480
|
const { passed, failed, errors, total } = this._parseTestOutput(testOutput.output || "", testOutput.exit_code || 0);
|
|
324
|
-
|
|
325
481
|
this.goUiSession.showTestResults("Test Results", passed, failed, errors, total, []);
|
|
326
482
|
}
|
|
327
483
|
|
|
@@ -356,11 +512,41 @@ class GenerateTestsUI {
|
|
|
356
512
|
try {
|
|
357
513
|
if (process.platform === 'darwin') {
|
|
358
514
|
execSync('pbcopy', { input: text });
|
|
515
|
+
this.goUiSession.showClipboardSuccess(text);
|
|
516
|
+
} else if (process.platform === 'linux') {
|
|
517
|
+
// Try xclip then xsel
|
|
518
|
+
try {
|
|
519
|
+
execSync('xclip -selection clipboard', { input: text });
|
|
520
|
+
} catch (e) {
|
|
521
|
+
execSync('xsel --clipboard --input', { input: text });
|
|
522
|
+
}
|
|
523
|
+
this.goUiSession.showClipboardSuccess(text);
|
|
359
524
|
} else {
|
|
360
|
-
|
|
361
|
-
|
|
525
|
+
console.log(chalk.cyan(`\nđ Please copy this command: ${text}\n`));
|
|
526
|
+
}
|
|
527
|
+
} catch (e) {
|
|
528
|
+
console.error(chalk.yellow(`\nâ ď¸ Failed to copy to clipboard. Command: ${text}\n`));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
_openInEditor(filePath, lineNumber) {
|
|
533
|
+
const { execSync } = require('child_process');
|
|
534
|
+
try {
|
|
535
|
+
// Try to open in VS Code if available, fallback to default editor
|
|
536
|
+
const location = lineNumber ? `${filePath}:${lineNumber}` : filePath;
|
|
537
|
+
try {
|
|
538
|
+
execSync(`code --goto ${location}`, { stdio: 'ignore' });
|
|
539
|
+
console.log(chalk.blue(`\nOpening ${location} in VS Code...`));
|
|
540
|
+
} catch (e) {
|
|
541
|
+
if (process.platform === 'darwin') {
|
|
542
|
+
execSync(`open ${filePath}`);
|
|
543
|
+
} else if (process.platform === 'linux') {
|
|
544
|
+
execSync(`xdg-open ${filePath}`);
|
|
545
|
+
}
|
|
362
546
|
}
|
|
363
|
-
} catch (e) {
|
|
547
|
+
} catch (e) {
|
|
548
|
+
console.log(chalk.yellow(`\nâ ď¸ Failed to open editor: ${e.message}`));
|
|
549
|
+
}
|
|
364
550
|
}
|
|
365
551
|
|
|
366
552
|
async _getUserFiles() {
|
|
@@ -375,11 +561,16 @@ class GenerateTestsUI {
|
|
|
375
561
|
'**/*.spec.*'
|
|
376
562
|
];
|
|
377
563
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
564
|
+
try {
|
|
565
|
+
return await glob(patterns, {
|
|
566
|
+
ignore,
|
|
567
|
+
absolute: true,
|
|
568
|
+
onlyFiles: true
|
|
569
|
+
});
|
|
570
|
+
} catch (e) {
|
|
571
|
+
console.error(chalk.red(`\nError searching for files: ${e.message}\n`));
|
|
572
|
+
return [];
|
|
573
|
+
}
|
|
383
574
|
}
|
|
384
575
|
}
|
|
385
576
|
|