@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/goUiSession.js
CHANGED
|
@@ -7,6 +7,7 @@ class GoUISession {
|
|
|
7
7
|
constructor() {
|
|
8
8
|
this._binaryPath = this._findGoUiBinary();
|
|
9
9
|
this._running = false;
|
|
10
|
+
this._tempFiles = new Set();
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
start() {
|
|
@@ -15,20 +16,30 @@ class GoUISession {
|
|
|
15
16
|
|
|
16
17
|
stop() {
|
|
17
18
|
this._running = false;
|
|
19
|
+
this.cleanup();
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
running() {
|
|
21
23
|
return this._running;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
cleanup() {
|
|
27
|
+
for (const file of this._tempFiles) {
|
|
28
|
+
this._cleanupTempFile(file);
|
|
29
|
+
}
|
|
30
|
+
this._tempFiles.clear();
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
showMenu() {
|
|
25
|
-
const outputFile = this._createTempFile('menu-output', '.txt');
|
|
34
|
+
const outputFile = this._trackTempFile(this._createTempFile('menu-output', '.txt'));
|
|
26
35
|
|
|
27
36
|
try {
|
|
28
|
-
spawnSync(this._binaryPath, ['menu', '--output', outputFile], {
|
|
37
|
+
const result = spawnSync(this._binaryPath, ['menu', '--output', outputFile], {
|
|
29
38
|
stdio: 'inherit'
|
|
30
39
|
});
|
|
31
40
|
|
|
41
|
+
if (result.error) throw result.error;
|
|
42
|
+
|
|
32
43
|
if (!fs.existsSync(outputFile) || fs.statSync(outputFile).size === 0) {
|
|
33
44
|
return 'exit';
|
|
34
45
|
}
|
|
@@ -36,7 +47,7 @@ class GoUISession {
|
|
|
36
47
|
const choice = fs.readFileSync(outputFile, 'utf8').trim();
|
|
37
48
|
return choice || 'exit';
|
|
38
49
|
} catch (error) {
|
|
39
|
-
console.error('Menu error:', error);
|
|
50
|
+
console.error('Menu error:', error.message);
|
|
40
51
|
return 'exit';
|
|
41
52
|
} finally {
|
|
42
53
|
this._cleanupTempFile(outputFile);
|
|
@@ -44,13 +55,15 @@ class GoUISession {
|
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
showJsTestMenu() {
|
|
47
|
-
const outputFile = this._createTempFile('js-menu-output', '.txt');
|
|
58
|
+
const outputFile = this._trackTempFile(this._createTempFile('js-menu-output', '.txt'));
|
|
48
59
|
|
|
49
60
|
try {
|
|
50
|
-
spawnSync(this._binaryPath, ['js-test-menu', '--output', outputFile], {
|
|
61
|
+
const result = spawnSync(this._binaryPath, ['js-test-menu', '--output', outputFile], {
|
|
51
62
|
stdio: 'inherit'
|
|
52
63
|
});
|
|
53
64
|
|
|
65
|
+
if (result.error) throw result.error;
|
|
66
|
+
|
|
54
67
|
if (!fs.existsSync(outputFile) || fs.statSync(outputFile).size === 0) {
|
|
55
68
|
return 'back';
|
|
56
69
|
}
|
|
@@ -58,7 +71,7 @@ class GoUISession {
|
|
|
58
71
|
const choice = fs.readFileSync(outputFile, 'utf8').trim();
|
|
59
72
|
return choice || 'back';
|
|
60
73
|
} catch (error) {
|
|
61
|
-
console.error('JS API Menu error:', error);
|
|
74
|
+
console.error('JS API Menu error:', error.message);
|
|
62
75
|
return 'back';
|
|
63
76
|
} finally {
|
|
64
77
|
this._cleanupTempFile(outputFile);
|
|
@@ -67,13 +80,15 @@ class GoUISession {
|
|
|
67
80
|
|
|
68
81
|
showListView(title, items) {
|
|
69
82
|
const dataJson = JSON.stringify({ title, items });
|
|
70
|
-
const outputFile = this._createTempFile('list-view-output', '.txt');
|
|
83
|
+
const outputFile = this._trackTempFile(this._createTempFile('list-view-output', '.txt'));
|
|
71
84
|
|
|
72
85
|
try {
|
|
73
|
-
spawnSync(this._binaryPath, ['list-view', '--data', dataJson, '--output', outputFile], {
|
|
86
|
+
const result = spawnSync(this._binaryPath, ['list-view', '--data', dataJson, '--output', outputFile], {
|
|
74
87
|
stdio: 'inherit'
|
|
75
88
|
});
|
|
76
89
|
|
|
90
|
+
if (result.error) throw result.error;
|
|
91
|
+
|
|
77
92
|
if (!fs.existsSync(outputFile) || fs.statSync(outputFile).size === 0) {
|
|
78
93
|
return 'back';
|
|
79
94
|
}
|
|
@@ -81,7 +96,7 @@ class GoUISession {
|
|
|
81
96
|
const selected = fs.readFileSync(outputFile, 'utf8').trim();
|
|
82
97
|
return selected || 'back';
|
|
83
98
|
} catch (error) {
|
|
84
|
-
console.error('List view error:', error);
|
|
99
|
+
console.error('List view error:', error.message);
|
|
85
100
|
return 'back';
|
|
86
101
|
} finally {
|
|
87
102
|
this._cleanupTempFile(outputFile);
|
|
@@ -89,10 +104,9 @@ class GoUISession {
|
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
showSpinner(message, func) {
|
|
92
|
-
const controlFile = this._createTempFile('spinner-control', '.json');
|
|
107
|
+
const controlFile = this._trackTempFile(this._createTempFile('spinner-control', '.json'));
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
const process = spawn(this._binaryPath, ['spinner', '--message', message, '--control', controlFile], {
|
|
109
|
+
const child = spawn(this._binaryPath, ['spinner', '--message', message, '--control', controlFile], {
|
|
96
110
|
stdio: ['ignore', 'inherit', 'inherit']
|
|
97
111
|
});
|
|
98
112
|
|
|
@@ -106,19 +120,20 @@ class GoUISession {
|
|
|
106
120
|
|
|
107
121
|
fs.writeFileSync(controlFile, JSON.stringify(status));
|
|
108
122
|
|
|
109
|
-
// Wait for
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// But for this simple implementation, we'll wait for the process to exit
|
|
123
|
+
// Wait for spinner to exit (it should exit after reading success/error from control file)
|
|
124
|
+
spawnSync('sleep', ['0.2']);
|
|
125
|
+
|
|
113
126
|
return result;
|
|
114
127
|
} catch (error) {
|
|
115
128
|
const status = { status: 'error', message: error.message };
|
|
116
|
-
fs.writeFileSync(controlFile, JSON.stringify(status));
|
|
129
|
+
try { fs.writeFileSync(controlFile, JSON.stringify(status)); } catch (e) { }
|
|
117
130
|
throw error;
|
|
118
131
|
} finally {
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
132
|
+
// Give the child process a moment to exit before we delete the control file
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
this._cleanupTempFile(controlFile);
|
|
135
|
+
if (!child.killed) child.kill();
|
|
136
|
+
}, 500);
|
|
122
137
|
}
|
|
123
138
|
}
|
|
124
139
|
|
|
@@ -129,7 +144,7 @@ class GoUISession {
|
|
|
129
144
|
stdio: 'inherit'
|
|
130
145
|
});
|
|
131
146
|
} catch (error) {
|
|
132
|
-
console.error('Stats error:', error);
|
|
147
|
+
console.error('Stats error:', error.message);
|
|
133
148
|
}
|
|
134
149
|
}
|
|
135
150
|
|
|
@@ -139,26 +154,28 @@ class GoUISession {
|
|
|
139
154
|
stdio: 'inherit'
|
|
140
155
|
});
|
|
141
156
|
} catch (error) {
|
|
142
|
-
console.error('About error:', error);
|
|
157
|
+
console.error('About error:', error.message);
|
|
143
158
|
}
|
|
144
159
|
}
|
|
145
160
|
|
|
146
161
|
showPostGenerationMenu(filePath, runCommand) {
|
|
147
162
|
const dataJson = JSON.stringify({ file_path: filePath, run_command: runCommand });
|
|
148
|
-
const outputFile = this._createTempFile('post-gen-output', '.txt');
|
|
163
|
+
const outputFile = this._trackTempFile(this._createTempFile('post-gen-output', '.txt'));
|
|
149
164
|
|
|
150
165
|
try {
|
|
151
|
-
spawnSync(this._binaryPath, ['post-generation-menu', '--data', dataJson, '--output', outputFile], {
|
|
166
|
+
const result = spawnSync(this._binaryPath, ['post-generation-menu', '--data', dataJson, '--output', outputFile], {
|
|
152
167
|
stdio: 'inherit'
|
|
153
168
|
});
|
|
154
169
|
|
|
170
|
+
if (result.error) throw result.error;
|
|
171
|
+
|
|
155
172
|
if (!fs.existsSync(outputFile) || fs.statSync(outputFile).size === 0) {
|
|
156
173
|
return 'back';
|
|
157
174
|
}
|
|
158
175
|
|
|
159
176
|
return fs.readFileSync(outputFile, 'utf8').trim() || 'back';
|
|
160
177
|
} catch (error) {
|
|
161
|
-
console.error('Post-gen menu error:', error);
|
|
178
|
+
console.error('Post-gen menu error:', error.message);
|
|
162
179
|
return 'back';
|
|
163
180
|
} finally {
|
|
164
181
|
this._cleanupTempFile(outputFile);
|
|
@@ -166,9 +183,8 @@ class GoUISession {
|
|
|
166
183
|
}
|
|
167
184
|
|
|
168
185
|
async showProgress(title, handler) {
|
|
169
|
-
const controlFile = this._createTempFile('progress-control', '.json');
|
|
186
|
+
const controlFile = this._trackTempFile(this._createTempFile('progress-control', '.json'));
|
|
170
187
|
|
|
171
|
-
// Spawn progress in background
|
|
172
188
|
const child = spawn(this._binaryPath, ['progress', '--title', title, '--control', controlFile], {
|
|
173
189
|
stdio: ['ignore', 'inherit', 'inherit'],
|
|
174
190
|
env: process.env
|
|
@@ -196,20 +212,19 @@ class GoUISession {
|
|
|
196
212
|
data.percent = options.percent;
|
|
197
213
|
}
|
|
198
214
|
|
|
199
|
-
|
|
215
|
+
try {
|
|
216
|
+
fs.writeFileSync(controlFile, JSON.stringify(data));
|
|
217
|
+
} catch (e) {
|
|
218
|
+
// Silently fail if control file is locked or missing
|
|
219
|
+
}
|
|
200
220
|
|
|
201
221
|
if (options.explicit_step === undefined && options.step_increment !== false) {
|
|
202
222
|
stepCounter++;
|
|
203
223
|
}
|
|
204
|
-
|
|
205
|
-
// Small delay to ensure Go UI (polling at 100ms) doesn't miss rapid updates
|
|
206
|
-
if (options.explicit_step !== undefined) {
|
|
207
|
-
spawnSync('sleep', ['0.05']);
|
|
208
|
-
}
|
|
209
224
|
},
|
|
210
225
|
error: (message) => {
|
|
211
226
|
const data = { type: 'error', message };
|
|
212
|
-
fs.writeFileSync(controlFile, JSON.stringify(data));
|
|
227
|
+
try { fs.writeFileSync(controlFile, JSON.stringify(data)); } catch (e) { }
|
|
213
228
|
},
|
|
214
229
|
complete: (message, options = {}) => {
|
|
215
230
|
const data = {
|
|
@@ -217,68 +232,68 @@ class GoUISession {
|
|
|
217
232
|
message: message || 'Done!',
|
|
218
233
|
auto_exit: options.auto_exit === true
|
|
219
234
|
};
|
|
220
|
-
fs.writeFileSync(controlFile, JSON.stringify(data));
|
|
235
|
+
try { fs.writeFileSync(controlFile, JSON.stringify(data)); } catch (e) { }
|
|
221
236
|
}
|
|
222
237
|
};
|
|
223
238
|
|
|
224
239
|
try {
|
|
225
240
|
const result = await handler(progress);
|
|
226
|
-
// Signal success if not already completed/errored
|
|
227
241
|
progress.complete();
|
|
228
|
-
|
|
229
|
-
// Wait for progress bar to exit CLEANLY
|
|
230
242
|
await processPromise;
|
|
231
|
-
|
|
232
243
|
return result;
|
|
233
244
|
} catch (error) {
|
|
234
245
|
progress.error(error.message);
|
|
235
|
-
await processPromise;
|
|
246
|
+
await processPromise;
|
|
236
247
|
throw error;
|
|
237
|
-
|
|
248
|
+
} finally {
|
|
249
|
+
setTimeout(() => this._cleanupTempFile(controlFile), 500);
|
|
238
250
|
}
|
|
239
251
|
}
|
|
240
252
|
|
|
241
|
-
|
|
242
253
|
showClipboardSuccess(command) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
254
|
+
try {
|
|
255
|
+
spawnSync(this._binaryPath, ['clipboard-success', '--command', command], {
|
|
256
|
+
stdio: 'inherit'
|
|
257
|
+
});
|
|
258
|
+
} catch (e) { }
|
|
246
259
|
}
|
|
247
260
|
|
|
248
261
|
showTestResults(title, passed, failed, errors, total, results = []) {
|
|
249
262
|
const dataJson = JSON.stringify({ title, passed, failed, errors, total, results });
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
263
|
+
try {
|
|
264
|
+
spawnSync(this._binaryPath, ['test-results', '--data', dataJson], {
|
|
265
|
+
stdio: 'inherit'
|
|
266
|
+
});
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error('Test results display error:', error.message);
|
|
269
|
+
}
|
|
253
270
|
}
|
|
254
|
-
|
|
255
|
-
async showAuditResults(auditResult) {
|
|
271
|
+
async showAuditResults(auditResult, index = 0) {
|
|
256
272
|
const dataJson = JSON.stringify(auditResult);
|
|
257
|
-
const inputFile = this._createTempFile('audit-data', '.json');
|
|
258
|
-
const outputFile = this._createTempFile('audit-choice', '.txt');
|
|
273
|
+
const inputFile = this._trackTempFile(this._createTempFile('audit-data', '.json'));
|
|
274
|
+
const outputFile = this._trackTempFile(this._createTempFile('audit-choice', '.txt'));
|
|
259
275
|
|
|
260
276
|
try {
|
|
261
277
|
fs.writeFileSync(inputFile, dataJson);
|
|
262
278
|
|
|
263
|
-
// Pause stdin to allow child process to take control of TTY
|
|
264
279
|
if (process.stdin.setRawMode) {
|
|
265
280
|
process.stdin.setRawMode(false);
|
|
266
281
|
}
|
|
267
282
|
process.stdin.pause();
|
|
268
283
|
|
|
269
284
|
await new Promise((resolve, reject) => {
|
|
270
|
-
const
|
|
285
|
+
const args = ['audit-results', '--file', inputFile, '--output', outputFile];
|
|
286
|
+
if (index > 0) {
|
|
287
|
+
args.push('--index', index.toString());
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const child = spawn(this._binaryPath, args, {
|
|
271
291
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
272
292
|
env: process.env
|
|
273
293
|
});
|
|
274
294
|
|
|
275
|
-
child.on('error', (err) =>
|
|
276
|
-
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
child.on('exit', (code) => {
|
|
280
|
-
resolve();
|
|
281
|
-
});
|
|
295
|
+
child.on('error', (err) => reject(err));
|
|
296
|
+
child.on('exit', () => resolve());
|
|
282
297
|
});
|
|
283
298
|
|
|
284
299
|
if (!fs.existsSync(outputFile) || fs.statSync(outputFile).size === 0) {
|
|
@@ -287,24 +302,75 @@ class GoUISession {
|
|
|
287
302
|
|
|
288
303
|
return fs.readFileSync(outputFile, 'utf8').trim() || 'back';
|
|
289
304
|
} catch (error) {
|
|
290
|
-
console.error('Audit results error:', error);
|
|
305
|
+
console.error('Audit results error:', error.message);
|
|
291
306
|
return 'back';
|
|
292
307
|
} finally {
|
|
293
|
-
// Resume stdin
|
|
294
308
|
process.stdin.resume();
|
|
295
|
-
|
|
296
309
|
this._cleanupTempFile(inputFile);
|
|
297
310
|
this._cleanupTempFile(outputFile);
|
|
298
311
|
}
|
|
299
312
|
}
|
|
300
313
|
|
|
314
|
+
async showStreamingAuditResults(methodName, className, sourceCode) {
|
|
315
|
+
const outputFile = this._trackTempFile(this._createTempFile('audit-choice', '.txt'));
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
if (process.stdin.setRawMode) {
|
|
319
|
+
process.stdin.setRawMode(false);
|
|
320
|
+
}
|
|
321
|
+
process.stdin.pause();
|
|
322
|
+
|
|
323
|
+
const child = spawn(this._binaryPath, [
|
|
324
|
+
'streaming-audit-results',
|
|
325
|
+
'--method', methodName || '',
|
|
326
|
+
'--class', className || '',
|
|
327
|
+
'--source', sourceCode || '',
|
|
328
|
+
'--output', outputFile
|
|
329
|
+
], {
|
|
330
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
331
|
+
env: process.env
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Handle potential broken pipes defensively
|
|
335
|
+
child.stdin.on('error', (err) => {
|
|
336
|
+
if (err.code !== 'EPIPE' && err.code !== 'EOF') {
|
|
337
|
+
console.error('Streaming stdin error:', err.message);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const processPromise = new Promise((resolve, reject) => {
|
|
342
|
+
child.on('error', (err) => reject(err));
|
|
343
|
+
child.on('exit', (code) => resolve(code));
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
process: child,
|
|
348
|
+
wait: processPromise,
|
|
349
|
+
outputFile: outputFile,
|
|
350
|
+
write: (msg) => {
|
|
351
|
+
if (child.stdin.writable) {
|
|
352
|
+
try {
|
|
353
|
+
child.stdin.write(msg + '\n');
|
|
354
|
+
} catch (e) {
|
|
355
|
+
// Suppress EPIPE errors during write
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
} catch (error) {
|
|
361
|
+
console.error('Streaming audit results error:', error.message);
|
|
362
|
+
process.stdin.resume();
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
301
367
|
showNoItems(itemType) {
|
|
302
368
|
try {
|
|
303
369
|
spawnSync(this._binaryPath, ['no-items', '--type', itemType], {
|
|
304
370
|
stdio: 'inherit'
|
|
305
371
|
});
|
|
306
372
|
} catch (error) {
|
|
307
|
-
console.error('No
|
|
373
|
+
console.error('UI No Items error:', error.message);
|
|
308
374
|
}
|
|
309
375
|
}
|
|
310
376
|
|
|
@@ -321,29 +387,32 @@ class GoUISession {
|
|
|
321
387
|
throw new Error(`Unsupported platform: ${platform}`);
|
|
322
388
|
}
|
|
323
389
|
|
|
324
|
-
// Look in binaries folder relative to this file
|
|
325
390
|
const localBinary = path.join(__dirname, '..', 'binaries', binaryName);
|
|
326
391
|
if (fs.existsSync(localBinary)) {
|
|
327
392
|
return localBinary;
|
|
328
393
|
}
|
|
329
394
|
|
|
330
|
-
throw new Error(`
|
|
395
|
+
throw new Error(`Critical: TNG UI binary not found at ${localBinary}. Please reinstall the extension.`);
|
|
331
396
|
}
|
|
332
397
|
|
|
333
398
|
_createTempFile(prefix, suffix) {
|
|
334
399
|
const tmpDir = os.tmpdir();
|
|
335
|
-
const filePath = path.join(tmpDir,
|
|
400
|
+
const filePath = path.join(tmpDir, `tng-${prefix}-${Date.now()}-${Math.floor(Math.random() * 1000)}${suffix}`);
|
|
336
401
|
fs.writeFileSync(filePath, '');
|
|
337
402
|
return filePath;
|
|
338
403
|
}
|
|
339
404
|
|
|
405
|
+
_trackTempFile(filePath) {
|
|
406
|
+
this._tempFiles.add(filePath);
|
|
407
|
+
return filePath;
|
|
408
|
+
}
|
|
409
|
+
|
|
340
410
|
_cleanupTempFile(filePath) {
|
|
341
411
|
if (fs.existsSync(filePath)) {
|
|
342
412
|
try {
|
|
343
413
|
fs.unlinkSync(filePath);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
414
|
+
this._tempFiles.delete(filePath);
|
|
415
|
+
} catch (e) { }
|
|
347
416
|
}
|
|
348
417
|
}
|
|
349
418
|
}
|
package/lib/jsonSession.js
CHANGED
|
@@ -14,21 +14,21 @@ class JsonSession {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
displayError(message) {
|
|
17
|
-
this.emitEvent('error', { message: this.stripColors(message) });
|
|
17
|
+
this.emitEvent('error', { message: this.stripColors(message || 'Unknown error') });
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
displayWarning(message) {
|
|
21
|
-
this.emitEvent('warning', { message: this.stripColors(message) });
|
|
21
|
+
this.emitEvent('warning', { message: this.stripColors(message || 'Warning') });
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
displayInfo(message) {
|
|
25
|
-
this.emitEvent('info', { message: this.stripColors(message) });
|
|
25
|
+
this.emitEvent('info', { message: this.stripColors(message || '') });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
displayList(title, items) {
|
|
29
29
|
this.emitEvent('list', {
|
|
30
|
-
title: this.stripColors(title),
|
|
31
|
-
items: items
|
|
30
|
+
title: this.stripColors(title || 'Items'),
|
|
31
|
+
items: items || []
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -37,7 +37,7 @@ class JsonSession {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
showProgress(title, callback) {
|
|
40
|
-
this.emitEvent('progress_start', { title });
|
|
40
|
+
this.emitEvent('progress_start', { title: title || 'Processing' });
|
|
41
41
|
|
|
42
42
|
const reporter = new JsonProgressReporter();
|
|
43
43
|
|
|
@@ -45,33 +45,35 @@ class JsonSession {
|
|
|
45
45
|
const result = callback(reporter);
|
|
46
46
|
|
|
47
47
|
if (result && result.error) {
|
|
48
|
-
//
|
|
48
|
+
// Keep progress state but report error
|
|
49
|
+
this.emitEvent('error', { message: result.error });
|
|
49
50
|
} else {
|
|
50
51
|
this.emitEvent('progress_complete', {
|
|
51
52
|
message: result?.message || 'Done',
|
|
52
|
-
result: result
|
|
53
|
+
result: result || {}
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
return result;
|
|
57
58
|
} catch (error) {
|
|
58
|
-
|
|
59
|
+
const errMsg = error?.message || 'Progress interrupted by unknown error';
|
|
60
|
+
this.emitEvent('error', { message: errMsg });
|
|
59
61
|
throw error;
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
showAuditResults(auditResult) {
|
|
64
|
-
this.emitEvent('result', auditResult);
|
|
66
|
+
this.emitEvent('result', auditResult || {});
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
showTestResults(title, passed, failed, errors, total, results = []) {
|
|
68
70
|
this.emitEvent('test_results', {
|
|
69
|
-
title,
|
|
70
|
-
passed,
|
|
71
|
-
failed,
|
|
72
|
-
errors,
|
|
73
|
-
total,
|
|
74
|
-
results
|
|
71
|
+
title: title || 'Test Results',
|
|
72
|
+
passed: Math.max(0, passed || 0),
|
|
73
|
+
failed: Math.max(0, failed || 0),
|
|
74
|
+
errors: Math.max(0, errors || 0),
|
|
75
|
+
total: Math.max(0, total || 0),
|
|
76
|
+
results: results || []
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -80,29 +82,35 @@ class JsonSession {
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
showConfigError(missing) {
|
|
83
|
-
this.emitEvent('config_error', { missing });
|
|
85
|
+
this.emitEvent('config_error', { missing: missing || [] });
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
showConfigMissing(missingItems) {
|
|
87
|
-
this.emitEvent('config_missing', { missing: missingItems });
|
|
89
|
+
this.emitEvent('config_missing', { missing: missingItems || [] });
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
showSystemStatus(status) {
|
|
91
|
-
this.emitEvent('system_status', status);
|
|
93
|
+
this.emitEvent('system_status', status || {});
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
showNoItems(type) {
|
|
95
|
-
this.emitEvent('no_items', { type });
|
|
97
|
+
this.emitEvent('no_items', { type: type || 'items' });
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
stripColors(str) {
|
|
101
|
+
if (typeof str !== 'string') return '';
|
|
99
102
|
// Remove ANSI color codes
|
|
100
103
|
return str.replace(/\x1b\[\d+(;\d+)*m/g, '');
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
emitEvent(type, data = {}) {
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
try {
|
|
108
|
+
const event = { type, ...data, timestamp: Date.now() };
|
|
109
|
+
console.log(JSON.stringify(event));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
// If serialization fails (e.g. circular ref), emit minimal error
|
|
112
|
+
console.log(JSON.stringify({ type: 'error', message: 'Internal session serialization error' }));
|
|
113
|
+
}
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
|
|
@@ -115,17 +123,29 @@ class JsonProgressReporter {
|
|
|
115
123
|
const { stepIncrement = true, explicitStep = null } = options;
|
|
116
124
|
const stepIdx = explicitStep !== null ? explicitStep : this.step;
|
|
117
125
|
|
|
118
|
-
|
|
126
|
+
let payload = {
|
|
119
127
|
type: 'progress_update',
|
|
120
|
-
message,
|
|
121
128
|
step: stepIdx
|
|
122
129
|
};
|
|
123
130
|
|
|
131
|
+
if (typeof message === 'string' && message.trim().startsWith('{')) {
|
|
132
|
+
try {
|
|
133
|
+
const parsed = JSON.parse(message);
|
|
134
|
+
payload = { ...payload, ...parsed };
|
|
135
|
+
} catch (e) {
|
|
136
|
+
payload.message = message;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
payload.message = message || '';
|
|
140
|
+
}
|
|
141
|
+
|
|
124
142
|
if (percent !== null) {
|
|
125
|
-
payload.percent = percent;
|
|
143
|
+
payload.percent = Math.min(100, Math.max(0, percent));
|
|
126
144
|
}
|
|
127
145
|
|
|
128
|
-
|
|
146
|
+
try {
|
|
147
|
+
console.log(JSON.stringify(payload));
|
|
148
|
+
} catch (e) { }
|
|
129
149
|
|
|
130
150
|
if (stepIncrement && explicitStep === null) {
|
|
131
151
|
this.step += 1;
|
|
@@ -133,7 +153,9 @@ class JsonProgressReporter {
|
|
|
133
153
|
}
|
|
134
154
|
|
|
135
155
|
error(message) {
|
|
136
|
-
|
|
156
|
+
try {
|
|
157
|
+
console.log(JSON.stringify({ type: 'error', message: message || 'Unknown error' }));
|
|
158
|
+
} catch (e) { }
|
|
137
159
|
}
|
|
138
160
|
}
|
|
139
161
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|