@quenty/cli-output-helpers 1.10.1 → 1.12.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.
Files changed (152) hide show
  1. package/CHANGELOG.md +30 -103
  2. package/dist/outputHelper.d.ts +5 -0
  3. package/dist/outputHelper.d.ts.map +1 -1
  4. package/dist/outputHelper.js +10 -1
  5. package/dist/outputHelper.js.map +1 -1
  6. package/dist/reporting/build-result-reporter.d.ts +33 -0
  7. package/dist/reporting/build-result-reporter.d.ts.map +1 -0
  8. package/dist/reporting/build-result-reporter.js +33 -0
  9. package/dist/reporting/build-result-reporter.js.map +1 -0
  10. package/dist/reporting/build-result-reporter.test.d.ts +2 -0
  11. package/dist/reporting/build-result-reporter.test.d.ts.map +1 -0
  12. package/dist/reporting/build-result-reporter.test.js +42 -0
  13. package/dist/reporting/build-result-reporter.test.js.map +1 -0
  14. package/dist/reporting/composite-reporter.d.ts.map +1 -1
  15. package/dist/reporting/composite-reporter.js.map +1 -1
  16. package/dist/reporting/composite-result-reporter.d.ts +15 -0
  17. package/dist/reporting/composite-result-reporter.d.ts.map +1 -0
  18. package/dist/reporting/composite-result-reporter.js +43 -0
  19. package/dist/reporting/composite-result-reporter.js.map +1 -0
  20. package/dist/reporting/composite-result-reporter.test.d.ts +2 -0
  21. package/dist/reporting/composite-result-reporter.test.d.ts.map +1 -0
  22. package/dist/reporting/composite-result-reporter.test.js +61 -0
  23. package/dist/reporting/composite-result-reporter.test.js.map +1 -0
  24. package/dist/reporting/file-result-reporter.d.ts +29 -0
  25. package/dist/reporting/file-result-reporter.d.ts.map +1 -0
  26. package/dist/reporting/file-result-reporter.js +61 -0
  27. package/dist/reporting/file-result-reporter.js.map +1 -0
  28. package/dist/reporting/file-result-reporter.test.d.ts +2 -0
  29. package/dist/reporting/file-result-reporter.test.d.ts.map +1 -0
  30. package/dist/reporting/file-result-reporter.test.js +78 -0
  31. package/dist/reporting/file-result-reporter.test.js.map +1 -0
  32. package/dist/reporting/format-json.d.ts +9 -0
  33. package/dist/reporting/format-json.d.ts.map +1 -0
  34. package/dist/reporting/format-json.js +12 -0
  35. package/dist/reporting/format-json.js.map +1 -0
  36. package/dist/reporting/format-json.test.d.ts +2 -0
  37. package/dist/reporting/format-json.test.d.ts.map +1 -0
  38. package/dist/reporting/format-json.test.js +29 -0
  39. package/dist/reporting/format-json.test.js.map +1 -0
  40. package/dist/reporting/format-table.d.ts +18 -0
  41. package/dist/reporting/format-table.d.ts.map +1 -0
  42. package/dist/reporting/format-table.js +61 -0
  43. package/dist/reporting/format-table.js.map +1 -0
  44. package/dist/reporting/format-table.test.d.ts +2 -0
  45. package/dist/reporting/format-table.test.d.ts.map +1 -0
  46. package/dist/reporting/format-table.test.js +90 -0
  47. package/dist/reporting/format-table.test.js.map +1 -0
  48. package/dist/reporting/github/annotations.d.ts.map +1 -1
  49. package/dist/reporting/github/annotations.js +1 -4
  50. package/dist/reporting/github/annotations.js.map +1 -1
  51. package/dist/reporting/github/annotations.test.js.map +1 -1
  52. package/dist/reporting/github/comment-table-reporter.d.ts.map +1 -1
  53. package/dist/reporting/github/comment-table-reporter.js.map +1 -1
  54. package/dist/reporting/github/job-summary-reporter.d.ts.map +1 -1
  55. package/dist/reporting/github/job-summary-reporter.js.map +1 -1
  56. package/dist/reporting/grouped-reporter.d.ts.map +1 -1
  57. package/dist/reporting/grouped-reporter.js +6 -2
  58. package/dist/reporting/grouped-reporter.js.map +1 -1
  59. package/dist/reporting/index.d.ts +9 -0
  60. package/dist/reporting/index.d.ts.map +1 -1
  61. package/dist/reporting/index.js +12 -1
  62. package/dist/reporting/index.js.map +1 -1
  63. package/dist/reporting/json-file-reporter.d.ts.map +1 -1
  64. package/dist/reporting/json-file-reporter.js +2 -1
  65. package/dist/reporting/json-file-reporter.js.map +1 -1
  66. package/dist/reporting/progress-format.d.ts.map +1 -1
  67. package/dist/reporting/progress-format.js.map +1 -1
  68. package/dist/reporting/reporter.d.ts.map +1 -1
  69. package/dist/reporting/reporter.js.map +1 -1
  70. package/dist/reporting/result-reporter.d.ts +37 -0
  71. package/dist/reporting/result-reporter.d.ts.map +1 -0
  72. package/dist/reporting/result-reporter.js +22 -0
  73. package/dist/reporting/result-reporter.js.map +1 -0
  74. package/dist/reporting/simple-reporter.d.ts.map +1 -1
  75. package/dist/reporting/simple-reporter.js +3 -1
  76. package/dist/reporting/simple-reporter.js.map +1 -1
  77. package/dist/reporting/spinner-reporter.d.ts +1 -0
  78. package/dist/reporting/spinner-reporter.d.ts.map +1 -1
  79. package/dist/reporting/spinner-reporter.js +25 -10
  80. package/dist/reporting/spinner-reporter.js.map +1 -1
  81. package/dist/reporting/spinner-reporter.test.js.map +1 -1
  82. package/dist/reporting/state/live-state-tracker.d.ts.map +1 -1
  83. package/dist/reporting/state/live-state-tracker.js +1 -1
  84. package/dist/reporting/state/live-state-tracker.js.map +1 -1
  85. package/dist/reporting/state/loaded-state-tracker.d.ts.map +1 -1
  86. package/dist/reporting/state/loaded-state-tracker.js.map +1 -1
  87. package/dist/reporting/state/state-tracker.d.ts.map +1 -1
  88. package/dist/reporting/stdout-result-reporter.d.ts +14 -0
  89. package/dist/reporting/stdout-result-reporter.d.ts.map +1 -0
  90. package/dist/reporting/stdout-result-reporter.js +16 -0
  91. package/dist/reporting/stdout-result-reporter.js.map +1 -0
  92. package/dist/reporting/stdout-result-reporter.test.d.ts +2 -0
  93. package/dist/reporting/stdout-result-reporter.test.d.ts.map +1 -0
  94. package/dist/reporting/stdout-result-reporter.test.js +28 -0
  95. package/dist/reporting/stdout-result-reporter.test.js.map +1 -0
  96. package/dist/reporting/summary-table-reporter.d.ts +2 -0
  97. package/dist/reporting/summary-table-reporter.d.ts.map +1 -1
  98. package/dist/reporting/summary-table-reporter.js +44 -33
  99. package/dist/reporting/summary-table-reporter.js.map +1 -1
  100. package/dist/reporting/watch-renderer.d.ts +16 -0
  101. package/dist/reporting/watch-renderer.d.ts.map +1 -0
  102. package/dist/reporting/watch-renderer.js +110 -0
  103. package/dist/reporting/watch-renderer.js.map +1 -0
  104. package/dist/reporting/watch-renderer.test.d.ts +2 -0
  105. package/dist/reporting/watch-renderer.test.d.ts.map +1 -0
  106. package/dist/reporting/watch-renderer.test.js +103 -0
  107. package/dist/reporting/watch-renderer.test.js.map +1 -0
  108. package/dist/reporting/watch-result-reporter.d.ts +21 -0
  109. package/dist/reporting/watch-result-reporter.d.ts.map +1 -0
  110. package/dist/reporting/watch-result-reporter.js +34 -0
  111. package/dist/reporting/watch-result-reporter.js.map +1 -0
  112. package/dist/reporting/watch-result-reporter.test.d.ts +2 -0
  113. package/dist/reporting/watch-result-reporter.test.d.ts.map +1 -0
  114. package/dist/reporting/watch-result-reporter.test.js +37 -0
  115. package/dist/reporting/watch-result-reporter.test.js.map +1 -0
  116. package/package.json +2 -2
  117. package/src/outputHelper.ts +12 -3
  118. package/src/reporting/build-result-reporter.test.ts +48 -0
  119. package/src/reporting/build-result-reporter.ts +58 -0
  120. package/src/reporting/composite-reporter.ts +10 -2
  121. package/src/reporting/composite-result-reporter.test.ts +71 -0
  122. package/src/reporting/composite-result-reporter.ts +58 -0
  123. package/src/reporting/file-result-reporter.test.ts +112 -0
  124. package/src/reporting/file-result-reporter.ts +75 -0
  125. package/src/reporting/format-json.test.ts +34 -0
  126. package/src/reporting/format-json.ts +16 -0
  127. package/src/reporting/format-table.test.ts +113 -0
  128. package/src/reporting/format-table.ts +97 -0
  129. package/src/reporting/github/annotations.test.ts +1 -3
  130. package/src/reporting/github/annotations.ts +13 -12
  131. package/src/reporting/github/comment-table-reporter.ts +5 -5
  132. package/src/reporting/github/job-summary-reporter.ts +3 -1
  133. package/src/reporting/grouped-reporter.ts +6 -2
  134. package/src/reporting/index.ts +34 -1
  135. package/src/reporting/json-file-reporter.ts +2 -1
  136. package/src/reporting/progress-format.ts +5 -2
  137. package/src/reporting/reporter.ts +15 -2
  138. package/src/reporting/result-reporter.ts +40 -0
  139. package/src/reporting/simple-reporter.ts +3 -1
  140. package/src/reporting/spinner-reporter.test.ts +7 -6
  141. package/src/reporting/spinner-reporter.ts +40 -14
  142. package/src/reporting/state/live-state-tracker.ts +12 -6
  143. package/src/reporting/state/loaded-state-tracker.ts +2 -7
  144. package/src/reporting/state/state-tracker.ts +5 -1
  145. package/src/reporting/stdout-result-reporter.test.ts +34 -0
  146. package/src/reporting/stdout-result-reporter.ts +23 -0
  147. package/src/reporting/summary-table-reporter.ts +51 -36
  148. package/src/reporting/watch-renderer.test.ts +127 -0
  149. package/src/reporting/watch-renderer.ts +132 -0
  150. package/src/reporting/watch-result-reporter.test.ts +44 -0
  151. package/src/reporting/watch-result-reporter.ts +45 -0
  152. package/tsconfig.tsbuildinfo +1 -1
@@ -1,6 +1,7 @@
1
1
  import { OutputHelper } from '../outputHelper.js';
2
2
  import { formatDurationMs } from '../cli-utils.js';
3
3
  import { BaseReporter } from './reporter.js';
4
+ import { formatTable } from './format-table.js';
4
5
  import { formatProgressResult, isEmptyTestRun } from './progress-format.js';
5
6
  /**
6
7
  * Prints a final summary table of all results when jobs complete.
@@ -25,40 +26,27 @@ export class SummaryTableReporter extends BaseReporter {
25
26
  const failures = this._state.getFailures();
26
27
  const passed = results.length - failures.length;
27
28
  const durationMs = Date.now() - this._state.startTimeMs;
28
- const STATUS_WIDTH = 26;
29
- console.log('');
30
- console.log('Package'.padEnd(40) + 'Status'.padEnd(STATUS_WIDTH) + 'Duration');
31
- console.log('─'.repeat(40 + STATUS_WIDTH + 8));
32
29
  let emptyRunCount = 0;
33
- for (const result of results) {
34
- const progressText = formatProgressResult(result.progressSummary);
35
- const empty = isEmptyTestRun(result.progressSummary);
36
- if (empty)
37
- emptyRunCount++;
38
- let label;
39
- if (result.success) {
40
- label = progressText ? `${this._successLabel} ${progressText}` : this._successLabel;
41
- }
42
- else {
43
- const failedPhase = result.failedPhase;
44
- label = failedPhase
45
- ? `${this._failureLabel} at ${failedPhase}`
46
- : this._failureLabel;
47
- }
48
- // Pad the plain text BEFORE wrapping in ANSI so padEnd counts visible chars
49
- const paddedLabel = label.padEnd(STATUS_WIDTH);
50
- let status;
51
- if (result.success) {
52
- status = empty
53
- ? OutputHelper.formatWarning(paddedLabel)
54
- : OutputHelper.formatSuccess(paddedLabel);
55
- }
56
- else {
57
- status = OutputHelper.formatError(paddedLabel);
58
- }
59
- const duration = OutputHelper.formatDim(formatDurationMs(result.durationMs));
60
- console.log(result.packageName.padEnd(40) + status + duration);
61
- }
30
+ const columns = [
31
+ {
32
+ header: 'Package',
33
+ value: (r) => r.packageName,
34
+ minWidth: 40,
35
+ },
36
+ {
37
+ header: 'Status',
38
+ value: (r) => this._statusLabel(r),
39
+ format: (label, r) => this._colorStatus(label, r, () => emptyRunCount++),
40
+ minWidth: 26,
41
+ },
42
+ {
43
+ header: 'Duration',
44
+ value: (r) => formatDurationMs(r.durationMs),
45
+ format: (v) => OutputHelper.formatDim(v),
46
+ },
47
+ ];
48
+ console.log('');
49
+ console.log(formatTable(results, columns));
62
50
  console.log('');
63
51
  const passedText = OutputHelper.formatSuccess(`${passed} passed`);
64
52
  const failedText = failures.length > 0
@@ -70,5 +58,28 @@ export class SummaryTableReporter extends BaseReporter {
70
58
  console.log(OutputHelper.formatWarning(`⚠ ${emptyRunCount} package(s) ran 0 tests — check test discovery`));
71
59
  }
72
60
  }
61
+ _statusLabel(result) {
62
+ if (result.success) {
63
+ const progressText = formatProgressResult(result.progressSummary);
64
+ return progressText
65
+ ? `${this._successLabel} ${progressText}`
66
+ : this._successLabel;
67
+ }
68
+ const failedPhase = result.failedPhase;
69
+ return failedPhase
70
+ ? `${this._failureLabel} at ${failedPhase}`
71
+ : this._failureLabel;
72
+ }
73
+ _colorStatus(label, result, countEmpty) {
74
+ if (result.success) {
75
+ const empty = isEmptyTestRun(result.progressSummary);
76
+ if (empty)
77
+ countEmpty();
78
+ return empty
79
+ ? OutputHelper.formatWarning(label)
80
+ : OutputHelper.formatSuccess(label);
81
+ }
82
+ return OutputHelper.formatError(label);
83
+ }
73
84
  }
74
85
  //# sourceMappingURL=summary-table-reporter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"summary-table-reporter.js","sourceRoot":"","sources":["../../src/reporting/summary-table-reporter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"summary-table-reporter.js","sourceRoot":"","sources":["../../src/reporting/summary-table-reporter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Live-updating renderer for watch/monitoring commands. In TTY mode,
3
+ * rewrites output in-place using cursor control. In non-TTY mode,
4
+ * appends new output only when it changes.
5
+ */
6
+ export interface WatchRendererOptions {
7
+ intervalMs?: number;
8
+ rewrite?: boolean;
9
+ }
10
+ export interface WatchRenderer {
11
+ start(): void;
12
+ update(): void;
13
+ stop(): void;
14
+ }
15
+ export declare function createWatchRenderer(render: () => string, options?: WatchRendererOptions): WatchRenderer;
16
+ //# sourceMappingURL=watch-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-renderer.d.ts","sourceRoot":"","sources":["../../src/reporting/watch-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,MAAM,EACpB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,aAAa,CA+Gf"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Live-updating renderer for watch/monitoring commands. In TTY mode,
3
+ * rewrites output in-place using cursor control. In non-TTY mode,
4
+ * appends new output only when it changes.
5
+ */
6
+ export function createWatchRenderer(render, options) {
7
+ const intervalMs = options?.intervalMs ?? 1000;
8
+ const rewrite = options?.rewrite ?? (process.stdout.isTTY ? true : false);
9
+ let _intervalHandle = null;
10
+ let _previousOutput = '';
11
+ let _previousLineCount = 0;
12
+ function _render() {
13
+ const output = render();
14
+ if (rewrite) {
15
+ // TTY rewrite mode: move cursor up and clear previous output
16
+ if (_previousLineCount > 0) {
17
+ process.stdout.write(`\x1b[${_previousLineCount}A\x1b[J`);
18
+ }
19
+ process.stdout.write(output + '\n');
20
+ _previousLineCount = output.split('\n').length;
21
+ }
22
+ else {
23
+ // Non-TTY append mode: only write when content changes
24
+ if (output !== _previousOutput) {
25
+ process.stdout.write(output + '\n');
26
+ }
27
+ }
28
+ _previousOutput = output;
29
+ }
30
+ function _startInterval() {
31
+ _intervalHandle = setInterval(_render, intervalMs);
32
+ }
33
+ function _clearInterval() {
34
+ if (_intervalHandle !== null) {
35
+ clearInterval(_intervalHandle);
36
+ _intervalHandle = null;
37
+ }
38
+ }
39
+ let _cursorHidden = false;
40
+ let _signalsBound = false;
41
+ function _showCursor() {
42
+ if (_cursorHidden) {
43
+ _cursorHidden = false;
44
+ process.stdout.write('\x1b[?25h');
45
+ }
46
+ }
47
+ function _onSignal(signal) {
48
+ _clearInterval();
49
+ _showCursor();
50
+ // Restore default behavior so the process actually exits
51
+ process.removeListener('SIGINT', _onSignal);
52
+ process.removeListener('SIGTERM', _onSignal);
53
+ process.kill(process.pid, signal);
54
+ }
55
+ function _bindSignals() {
56
+ if (_signalsBound)
57
+ return;
58
+ _signalsBound = true;
59
+ process.once('SIGINT', _onSignal);
60
+ process.once('SIGTERM', _onSignal);
61
+ }
62
+ function _unbindSignals() {
63
+ if (!_signalsBound)
64
+ return;
65
+ _signalsBound = false;
66
+ process.removeListener('SIGINT', _onSignal);
67
+ process.removeListener('SIGTERM', _onSignal);
68
+ }
69
+ return {
70
+ start() {
71
+ if (rewrite) {
72
+ process.stdout.write('\x1b[?25l'); // hide cursor
73
+ _cursorHidden = true;
74
+ _bindSignals();
75
+ }
76
+ try {
77
+ _render();
78
+ }
79
+ catch (err) {
80
+ _showCursor();
81
+ _unbindSignals();
82
+ throw err;
83
+ }
84
+ _startInterval();
85
+ },
86
+ update() {
87
+ _clearInterval();
88
+ try {
89
+ _render();
90
+ }
91
+ catch (err) {
92
+ _showCursor();
93
+ _unbindSignals();
94
+ throw err;
95
+ }
96
+ _startInterval();
97
+ },
98
+ stop() {
99
+ _clearInterval();
100
+ try {
101
+ _render();
102
+ }
103
+ finally {
104
+ _showCursor();
105
+ _unbindSignals();
106
+ }
107
+ },
108
+ };
109
+ }
110
+ //# sourceMappingURL=watch-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-renderer.js","sourceRoot":"","sources":["../../src/reporting/watch-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=watch-renderer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-renderer.test.d.ts","sourceRoot":"","sources":["../../src/reporting/watch-renderer.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { createWatchRenderer } from './watch-renderer.js';
3
+ describe('createWatchRenderer', () => {
4
+ let writes;
5
+ beforeEach(() => {
6
+ vi.useFakeTimers();
7
+ writes = [];
8
+ vi.spyOn(process.stdout, 'write').mockImplementation(((chunk) => {
9
+ writes.push(typeof chunk === 'string' ? chunk : chunk.toString());
10
+ return true;
11
+ }));
12
+ });
13
+ afterEach(() => {
14
+ vi.useRealTimers();
15
+ vi.restoreAllMocks();
16
+ });
17
+ it('start renders immediately and stop clears interval with final render', () => {
18
+ let count = 0;
19
+ const renderer = createWatchRenderer(() => `frame-${count++}`, {
20
+ rewrite: false,
21
+ intervalMs: 1000,
22
+ });
23
+ renderer.start();
24
+ expect(writes).toEqual(['frame-0\n']);
25
+ renderer.stop();
26
+ // stop does a final render
27
+ expect(writes).toEqual(['frame-0\n', 'frame-1\n']);
28
+ // After stop, no more renders should happen
29
+ writes.length = 0;
30
+ vi.advanceTimersByTime(3000);
31
+ expect(writes).toEqual([]);
32
+ });
33
+ it('update forces immediate render and resets interval', () => {
34
+ let count = 0;
35
+ const renderer = createWatchRenderer(() => `frame-${count++}`, {
36
+ rewrite: false,
37
+ intervalMs: 1000,
38
+ });
39
+ renderer.start(); // frame-0
40
+ expect(writes).toEqual(['frame-0\n']);
41
+ // Advance 500ms, then force update
42
+ vi.advanceTimersByTime(500);
43
+ renderer.update(); // frame-1 (forced)
44
+ expect(writes).toEqual(['frame-0\n', 'frame-1\n']);
45
+ // Advance 500ms more — the old interval would have fired at 1000ms total,
46
+ // but update() reset it, so nothing fires yet
47
+ vi.advanceTimersByTime(500);
48
+ expect(writes).toEqual(['frame-0\n', 'frame-1\n']);
49
+ // Advance another 500ms (1000ms since update), new interval fires
50
+ vi.advanceTimersByTime(500);
51
+ expect(writes).toEqual(['frame-0\n', 'frame-1\n', 'frame-2\n']);
52
+ renderer.stop();
53
+ });
54
+ it('non-TTY mode only writes when content changes', () => {
55
+ let value = 'same';
56
+ const renderer = createWatchRenderer(() => value, {
57
+ rewrite: false,
58
+ intervalMs: 100,
59
+ });
60
+ renderer.start(); // writes "same"
61
+ expect(writes).toEqual(['same\n']);
62
+ // Same content on next interval — should NOT write
63
+ vi.advanceTimersByTime(100);
64
+ expect(writes).toEqual(['same\n']);
65
+ // Change content — should write
66
+ value = 'different';
67
+ vi.advanceTimersByTime(100);
68
+ expect(writes).toEqual(['same\n', 'different\n']);
69
+ renderer.stop();
70
+ });
71
+ it('stop clears the interval so no more renders happen', () => {
72
+ let count = 0;
73
+ const renderer = createWatchRenderer(() => `f-${count++}`, {
74
+ rewrite: false,
75
+ intervalMs: 100,
76
+ });
77
+ renderer.start(); // f-0
78
+ renderer.stop(); // f-1 (final)
79
+ writes.length = 0;
80
+ vi.advanceTimersByTime(1000);
81
+ expect(writes).toEqual([]);
82
+ });
83
+ it('TTY rewrite mode hides/shows cursor and uses escape codes', () => {
84
+ let count = 0;
85
+ const renderer = createWatchRenderer(() => `line-${count++}`, {
86
+ rewrite: true,
87
+ intervalMs: 100,
88
+ });
89
+ renderer.start();
90
+ // Should have written hide-cursor + first frame
91
+ expect(writes[0]).toBe('\x1b[?25l');
92
+ expect(writes[1]).toBe('line-0\n');
93
+ // Advance to trigger second render — should include cursor-up + clear
94
+ vi.advanceTimersByTime(100);
95
+ const cursorUpWrite = writes.find((w) => w.includes('\x1b[1A\x1b[J'));
96
+ expect(cursorUpWrite).toBeDefined();
97
+ renderer.stop();
98
+ // Should have written show-cursor
99
+ const lastWrite = writes[writes.length - 1];
100
+ expect(lastWrite).toBe('\x1b[?25h');
101
+ });
102
+ });
103
+ //# sourceMappingURL=watch-renderer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-renderer.test.js","sourceRoot":"","sources":["../../src/reporting/watch-renderer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Live-redraw reporter for watch-mode commands. Wraps the WatchRenderer —
3
+ * stores the latest result and redraws via cursor manipulation.
4
+ *
5
+ * The first onResult starts the underlying renderer; subsequent calls update
6
+ * it. stopAsync stops the renderer and restores the cursor.
7
+ */
8
+ import { BaseResultReporter } from './result-reporter.js';
9
+ export interface WatchResultReporterOptions<T> {
10
+ render: (result: T) => string;
11
+ intervalMs?: number;
12
+ }
13
+ export declare class WatchResultReporter<T = unknown> extends BaseResultReporter<T> {
14
+ private _renderer;
15
+ private _latest;
16
+ private _started;
17
+ constructor(options: WatchResultReporterOptions<T>);
18
+ onResult(result: T): void;
19
+ stopAsync(): Promise<void>;
20
+ }
21
+ //# sourceMappingURL=watch-result-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-result-reporter.d.ts","sourceRoot":"","sources":["../../src/reporting/watch-result-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,MAAM,WAAW,0BAA0B,CAAC,CAAC;IAC3C,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,mBAAmB,CAAC,CAAC,GAAG,OAAO,CAAE,SAAQ,kBAAkB,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,SAAS,CAAgB;IACjC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,EAAE,0BAA0B,CAAC,CAAC,CAAC;IAQzC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI;IAUnB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAK1C"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Live-redraw reporter for watch-mode commands. Wraps the WatchRenderer —
3
+ * stores the latest result and redraws via cursor manipulation.
4
+ *
5
+ * The first onResult starts the underlying renderer; subsequent calls update
6
+ * it. stopAsync stops the renderer and restores the cursor.
7
+ */
8
+ import { BaseResultReporter } from './result-reporter.js';
9
+ import { createWatchRenderer } from './watch-renderer.js';
10
+ export class WatchResultReporter extends BaseResultReporter {
11
+ _renderer;
12
+ _latest;
13
+ _started = false;
14
+ constructor(options) {
15
+ super();
16
+ this._renderer = createWatchRenderer(() => (this._latest === undefined ? '' : options.render(this._latest)), { intervalMs: options.intervalMs });
17
+ }
18
+ onResult(result) {
19
+ this._latest = result;
20
+ if (!this._started) {
21
+ this._started = true;
22
+ this._renderer.start();
23
+ }
24
+ else {
25
+ this._renderer.update();
26
+ }
27
+ }
28
+ async stopAsync() {
29
+ if (this._started) {
30
+ this._renderer.stop();
31
+ }
32
+ }
33
+ }
34
+ //# sourceMappingURL=watch-result-reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-result-reporter.js","sourceRoot":"","sources":["../../src/reporting/watch-result-reporter.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAsB,MAAM,qBAAqB,CAAC;AAO9E,MAAM,OAAO,mBAAiC,SAAQ,kBAAqB;IACjE,SAAS,CAAgB;IACzB,OAAO,CAAgB;IACvB,QAAQ,GAAG,KAAK,CAAC;IAEzB,YAAY,OAAsC;QAChD,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,SAAS,GAAG,mBAAmB,CAClC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EACtE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,CACnC,CAAC;IACJ,CAAC;IAEQ,QAAQ,CAAC,MAAS;QACzB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QAC1B,CAAC;IACH,CAAC;IAEQ,KAAK,CAAC,SAAS;QACtB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=watch-result-reporter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-result-reporter.test.d.ts","sourceRoot":"","sources":["../../src/reporting/watch-result-reporter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { WatchResultReporter } from './watch-result-reporter.js';
3
+ describe('WatchResultReporter', () => {
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ let writeSpy;
6
+ const originalIsTTY = process.stdout.isTTY;
7
+ beforeEach(() => {
8
+ writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
9
+ vi.useFakeTimers();
10
+ });
11
+ afterEach(() => {
12
+ vi.useRealTimers();
13
+ writeSpy.mockRestore();
14
+ process.stdout.isTTY = originalIsTTY;
15
+ });
16
+ it('starts the renderer on the first onResult and updates on subsequent calls', () => {
17
+ process.stdout.isTTY = false;
18
+ const renderFn = vi.fn((r) => `v=${r.v}`);
19
+ const reporter = new WatchResultReporter({
20
+ render: renderFn,
21
+ intervalMs: 1000,
22
+ });
23
+ reporter.onResult({ v: 1 });
24
+ expect(renderFn).toHaveBeenCalledWith({ v: 1 });
25
+ expect(writeSpy).toHaveBeenCalled();
26
+ reporter.onResult({ v: 2 });
27
+ expect(renderFn).toHaveBeenCalledWith({ v: 2 });
28
+ });
29
+ it('stopAsync without any onResult does not start the renderer', async () => {
30
+ process.stdout.isTTY = false;
31
+ const renderFn = vi.fn(() => '');
32
+ const reporter = new WatchResultReporter({ render: renderFn });
33
+ await reporter.stopAsync();
34
+ expect(renderFn).not.toHaveBeenCalled();
35
+ });
36
+ });
37
+ //# sourceMappingURL=watch-result-reporter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch-result-reporter.test.js","sourceRoot":"","sources":["../../src/reporting/watch-result-reporter.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,8DAA8D;IAC9D,IAAI,QAAa,CAAC;IAClB,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;IAE3C,UAAU,CAAC,GAAG,EAAE;QACd,QAAQ,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5E,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,aAAa,EAAE,CAAC;QACnB,QAAQ,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,aAAa,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2EAA2E,EAAE,GAAG,EAAE;QACnF,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAC7B,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAgB,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,IAAI,mBAAmB,CAAgB;YACtD,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,IAAI;SACjB,CAAC,CAAC;QAEH,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5B,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,QAAQ,CAAC,CAAC,gBAAgB,EAAE,CAAC;QAEpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC5B,MAAM,CAAC,QAAQ,CAAC,CAAC,oBAAoB,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;QAC1E,OAAO,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC;QAC7B,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,mBAAmB,CAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAExE,MAAM,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/cli-output-helpers",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "Helpers to generate Nevermore package and game templates",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -59,5 +59,5 @@
59
59
  "engines": {
60
60
  "node": ">=16"
61
61
  },
62
- "gitHead": "ab12ae11f52faf00116949383563934b2853b449"
62
+ "gitHead": "5cba15660c2856169fd0362e1eeeca4c260029c2"
63
63
  }
@@ -69,8 +69,7 @@ export class OutputHelper {
69
69
  return chalk.greenBright(message);
70
70
  }
71
71
 
72
- private static _hasAnsi = (text: string): boolean =>
73
- text.includes('\x1b[');
72
+ private static _hasAnsi = (text: string): boolean => text.includes('\x1b[');
74
73
 
75
74
  /** Strip ANSI escape codes from terminal output. */
76
75
  public static stripAnsi = (text: string): string =>
@@ -135,6 +134,14 @@ export class OutputHelper {
135
134
  this._verbose = verbose;
136
135
  }
137
136
 
137
+ /**
138
+ * Returns the current verbose flag. Useful for handlers that need to
139
+ * forward the global `--verbose` setting to downstream APIs.
140
+ */
141
+ public static isVerbose(): boolean {
142
+ return this._verbose;
143
+ }
144
+
138
145
  /**
139
146
  * Logs a verbose/intermediate message. Suppressed when verbose is false.
140
147
  * When running inside a buffered context (see runBuffered), messages are
@@ -145,7 +152,9 @@ export class OutputHelper {
145
152
  return;
146
153
  }
147
154
 
148
- const formatted = this._hasAnsi(message) ? message : this.formatDim(message);
155
+ const formatted = this._hasAnsi(message)
156
+ ? message
157
+ : this.formatDim(message);
149
158
  const buffer = _outputStorage.getStore();
150
159
  if (buffer) {
151
160
  buffer.lines.push(formatted);
@@ -0,0 +1,48 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildResultReporter } from './build-result-reporter.js';
3
+ import { StdoutResultReporter } from './stdout-result-reporter.js';
4
+ import { FileResultReporter } from './file-result-reporter.js';
5
+ import { WatchResultReporter } from './watch-result-reporter.js';
6
+
7
+ describe('buildResultReporter', () => {
8
+ const render = (r: unknown) => String(r);
9
+
10
+ it('returns a StdoutResultReporter by default', () => {
11
+ const reporter = buildResultReporter({ render });
12
+ expect(reporter).toBeInstanceOf(StdoutResultReporter);
13
+ });
14
+
15
+ it('returns a FileResultReporter when outputPath is set', () => {
16
+ const reporter = buildResultReporter({
17
+ outputPath: '/tmp/out.txt',
18
+ render,
19
+ });
20
+ expect(reporter).toBeInstanceOf(FileResultReporter);
21
+ });
22
+
23
+ it('returns a WatchResultReporter when watch is true and no outputPath', () => {
24
+ const reporter = buildResultReporter({ watch: true, render });
25
+ expect(reporter).toBeInstanceOf(WatchResultReporter);
26
+ });
27
+
28
+ it('outputPath wins over watch', () => {
29
+ const reporter = buildResultReporter({
30
+ outputPath: '/tmp/out.txt',
31
+ watch: true,
32
+ render,
33
+ });
34
+ expect(reporter).toBeInstanceOf(FileResultReporter);
35
+ });
36
+
37
+ it('treats empty-string outputPath as a file destination', () => {
38
+ // Empty string is a valid (if degenerate) path — selection is by
39
+ // presence of the field, not truthiness.
40
+ const reporter = buildResultReporter({ outputPath: '', render });
41
+ expect(reporter).toBeInstanceOf(FileResultReporter);
42
+ });
43
+
44
+ it('returns Stdout when watch is false and no outputPath', () => {
45
+ const reporter = buildResultReporter({ watch: false, render });
46
+ expect(reporter).toBeInstanceOf(StdoutResultReporter);
47
+ });
48
+ });