runspec-node 0.19.0 → 0.22.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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/inference.d.ts +6 -0
- package/dist/inference.d.ts.map +1 -1
- package/dist/inference.js +25 -0
- package/dist/inference.js.map +1 -1
- package/dist/loader.js +5 -0
- package/dist/loader.js.map +1 -1
- package/dist/logging_setup.d.ts +29 -1
- package/dist/logging_setup.d.ts.map +1 -1
- package/dist/logging_setup.js +170 -34
- package/dist/logging_setup.js.map +1 -1
- package/dist/models.d.ts +3 -0
- package/dist/models.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/index.ts +1 -0
- package/src/inference.ts +25 -0
- package/src/loader.ts +5 -0
- package/src/logging_setup.ts +188 -33
- package/src/models.ts +3 -0
- package/tests/test_emit_schema.test.ts +16 -0
- package/tests/test_inference.test.ts +29 -1
- package/tests/test_loader.test.ts +30 -2
- package/tests/test_logging_setup.test.ts +64 -6
- package/tests/test_run_summary.test.ts +99 -0
|
@@ -6,7 +6,11 @@ import {
|
|
|
6
6
|
getLogger,
|
|
7
7
|
emitRunSummary,
|
|
8
8
|
_resetForTest,
|
|
9
|
+
_handleUncaught,
|
|
10
|
+
buildExcStructured,
|
|
11
|
+
formatCompactTrace,
|
|
9
12
|
RUN_SUMMARY_LOGGER,
|
|
13
|
+
EXCEPTION_LOGGER,
|
|
10
14
|
} from '../src/logging_setup';
|
|
11
15
|
|
|
12
16
|
function tmpDir(): string {
|
|
@@ -260,3 +264,98 @@ test('sudo user_target written to audit record', () => {
|
|
|
260
264
|
expect(summary.extra.user).toBe('alice');
|
|
261
265
|
expect(summary.extra.user_target).toBe('root');
|
|
262
266
|
});
|
|
267
|
+
|
|
268
|
+
// ── uncaught exceptions ────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
function readExcRecord(dir: string): Record<string, any> | undefined {
|
|
271
|
+
const content = fs.readFileSync(path.join(dir, 'logs', 'myscript.log'), 'utf-8');
|
|
272
|
+
return content.trim().split('\n').map(l => JSON.parse(l)).find(o => o.logger === EXCEPTION_LOGGER);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
test('uncaught exception writes a structured record even with summary off', () => {
|
|
276
|
+
const dir = tmpDir();
|
|
277
|
+
configureLogging(makeCfg(dir, { summary: false }));
|
|
278
|
+
const cap = captureStderr();
|
|
279
|
+
_handleUncaught(new TypeError('invalid quality 200'));
|
|
280
|
+
cap.restore();
|
|
281
|
+
const rec = readExcRecord(dir);
|
|
282
|
+
expect(rec).toBeDefined();
|
|
283
|
+
expect(rec!.level).toBe('CRITICAL');
|
|
284
|
+
expect(rec!.exc_structured.type).toBe('TypeError');
|
|
285
|
+
expect(rec!.exc_structured.message).toBe('invalid quality 200');
|
|
286
|
+
expect(Array.isArray(rec!.exc_structured.frames)).toBe(true);
|
|
287
|
+
expect(rec!.exc).toBeDefined(); // full stack string also present
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('without --debug the console shows a one-liner, not a traceback', () => {
|
|
291
|
+
const dir = tmpDir();
|
|
292
|
+
configureLogging(makeCfg(dir, { debug: false }));
|
|
293
|
+
const cap = captureStderr();
|
|
294
|
+
_handleUncaught(new Error('boom'));
|
|
295
|
+
cap.restore();
|
|
296
|
+
const joined = cap.lines.join('');
|
|
297
|
+
expect(joined).toContain('ERROR: Error: boom');
|
|
298
|
+
expect(joined).toContain('run with --debug');
|
|
299
|
+
expect(joined).not.toContain('\n at '); // no raw V8 stack dump
|
|
300
|
+
expect(readExcRecord(dir)).toBeDefined();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('with --debug the console shows a compact aligned trace', () => {
|
|
304
|
+
const dir = tmpDir();
|
|
305
|
+
configureLogging(makeCfg(dir, { debug: true }));
|
|
306
|
+
const cap = captureStderr();
|
|
307
|
+
_handleUncaught(new Error('boom'));
|
|
308
|
+
cap.restore();
|
|
309
|
+
const joined = cap.lines.join('');
|
|
310
|
+
expect(joined).toContain('Error: boom');
|
|
311
|
+
expect(joined).not.toContain('run with --debug'); // hint only in quiet mode
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('the exception record is not echoed to the console handlers', () => {
|
|
315
|
+
const dir = tmpDir();
|
|
316
|
+
const stdoutLines: string[] = [];
|
|
317
|
+
const stderrLines: string[] = [];
|
|
318
|
+
const o = jest.spyOn(process.stdout, 'write').mockImplementation((c) => { stdoutLines.push(String(c)); return true; });
|
|
319
|
+
const e = jest.spyOn(process.stderr, 'write').mockImplementation((c) => { stderrLines.push(String(c)); return true; });
|
|
320
|
+
configureLogging(makeCfg(dir, { debug: false }));
|
|
321
|
+
_handleUncaught(new Error('boom'));
|
|
322
|
+
o.mockRestore();
|
|
323
|
+
e.mockRestore();
|
|
324
|
+
// The structured JSON record is file-only; only our one-liner hits stderr.
|
|
325
|
+
expect(stdoutLines.join('')).not.toContain('uncaught exception');
|
|
326
|
+
expect(stderrLines.join('')).not.toContain('"logger":"runspec.exception"');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('buildExcStructured parses frames innermost-last with module', () => {
|
|
330
|
+
const err = new Error('x');
|
|
331
|
+
err.stack = [
|
|
332
|
+
'Error: x',
|
|
333
|
+
' at inner (/app/deep.js:5:10)',
|
|
334
|
+
' at outer (/app/main.js:20:3)',
|
|
335
|
+
].join('\n');
|
|
336
|
+
const es = buildExcStructured(err);
|
|
337
|
+
expect(es.type).toBe('Error');
|
|
338
|
+
expect(es.frames.map(f => f.func)).toEqual(['outer', 'inner']); // innermost last
|
|
339
|
+
expect(es.module).toBe('deep');
|
|
340
|
+
expect(es.frames[0]).toEqual({ func: 'outer', file: '/app/main.js', line: 20, code: null });
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test('formatCompactTrace drops internal runspec frames but keeps user frames', () => {
|
|
344
|
+
const err = new Error('x');
|
|
345
|
+
// RUNSPEC_PKG_DIR is the dir of the logging_setup module (the package source).
|
|
346
|
+
const pkgDir = path.resolve(__dirname, '..', 'src');
|
|
347
|
+
const frames = [
|
|
348
|
+
{ func: 'parse', file: path.join(pkgDir, 'parser.ts'), line: 10, code: null },
|
|
349
|
+
{ func: 'main', file: '/app/deploy.js', line: 42, code: null },
|
|
350
|
+
];
|
|
351
|
+
const out = formatCompactTrace(err, frames);
|
|
352
|
+
expect(out).toContain('deploy.js:42');
|
|
353
|
+
expect(out).not.toContain('parser.ts:10');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
test('formatCompactTrace falls back to full list when every frame is internal', () => {
|
|
357
|
+
const err = new Error('x');
|
|
358
|
+
const pkgDir = path.resolve(__dirname, '..', 'src');
|
|
359
|
+
const frames = [{ func: 'parse', file: path.join(pkgDir, 'parser.ts'), line: 10, code: null }];
|
|
360
|
+
expect(formatCompactTrace(err, frames)).toContain('parser.ts:10');
|
|
361
|
+
});
|