Skip to content

Commit adbedc3

Browse files
committed
fix: linear output for errors from function commands
1 parent 2289122 commit adbedc3

File tree

5 files changed

+152
-100
lines changed

5 files changed

+152
-100
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export async function runp<const TCommands extends Commands = Commands>(
108108
}
109109

110110
const results = await Promise.all(tasks.map((task) => task.result));
111+
await new Promise<void>((resolve) => setTimeout(resolve));
111112
stop?.();
112113
return results as { [K in keyof TCommands]: RunpResult };
113114
}

src/task.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function task(command: RunpCommand, allTasks: () => Task[], q = createQue
101101
} catch (error) {
102102
state.set('status', 'error');
103103
state.set('output', String(error));
104+
writeLine(String(error), thisTask);
104105
} finally {
105106
state.set('time', performance.now() - start);
106107
}
@@ -159,7 +160,10 @@ export function task(command: RunpCommand, allTasks: () => Task[], q = createQue
159160
state.set('time', performance.now() - start);
160161
});
161162

162-
subProcess.on('error', (error) => state.set('output', String(error)));
163+
subProcess.on('error', (error) => {
164+
state.set('output', String(error));
165+
writeLine(String(error), thisTask);
166+
});
163167
});
164168

165169
return result;

test/_helperts.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { RenderOptions } from '@schummar/react-terminal';
2+
import { setTimeout } from 'node:timers/promises';
3+
import { Terminal } from 'xterm-headless';
4+
5+
export type Target = RenderOptions['target'] extends infer T | undefined ? T : never;
6+
7+
export class TestTerminal implements Target {
8+
term: Terminal;
9+
write: Target['write'];
10+
columns: number;
11+
rows: number;
12+
13+
constructor(private options: { cols: number; rows: number }) {
14+
this.term = new Terminal({
15+
cols: this.options.cols,
16+
rows: this.options.rows,
17+
allowProposedApi: true,
18+
});
19+
20+
this.write = this.term.write.bind(this.term) as Target['write'];
21+
this.columns = this.options.cols;
22+
this.rows = this.options.rows;
23+
}
24+
25+
getBuffer = (all = false) => {
26+
const buffer = this.term.buffer.active;
27+
const offset = all ? 0 : buffer.baseY;
28+
29+
return Array(buffer.length - offset)
30+
.fill(0)
31+
.map((x, i) =>
32+
buffer
33+
.getLine(offset + i)
34+
?.translateToString()
35+
.replace(/[]/g, '⠋')
36+
.replace(/\[(.*)s\]/g, (x, y) => `[${''.padEnd(y.length - 4, '#')}.###s]`)
37+
.replace(/\xA0/g, ' '),
38+
);
39+
};
40+
}
41+
42+
export const poll = async (assertion: () => void, max = 9_000) => {
43+
const start = performance.now();
44+
45+
for (;;) {
46+
try {
47+
assertion();
48+
return;
49+
} catch (e) {
50+
if (performance.now() - start > max) {
51+
throw e;
52+
}
53+
await setTimeout(10);
54+
}
55+
}
56+
};

test/linearOutput.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { setTimeout } from 'node:timers/promises';
2+
import { describe, expect, test } from 'vitest';
3+
import { runp } from '../src';
4+
import { poll, TestTerminal } from './_helperts';
5+
6+
describe('linear outout', () => {
7+
test('updating output', async () => {
8+
const term = new TestTerminal({ cols: 25, rows: 15 });
9+
10+
await runp({
11+
commands: [
12+
{
13+
name: 'command',
14+
async cmd({ updateOutput }) {
15+
updateOutput('line 1');
16+
updateOutput('line 2');
17+
await setTimeout(1000);
18+
updateOutput('line 3');
19+
},
20+
},
21+
],
22+
target: term,
23+
linearOutput: true,
24+
});
25+
26+
await poll(
27+
() =>
28+
expect(term.getBuffer()).toEqual([
29+
' ',
30+
'-- [command] -> ',
31+
' ',
32+
'line 1 ',
33+
'line 2 ',
34+
'line 3 ',
35+
' ',
36+
'<- [command] -- ',
37+
' ',
38+
'✓ command [#.###s] ',
39+
' ',
40+
' ',
41+
' ',
42+
' ',
43+
' ',
44+
]),
45+
1000,
46+
);
47+
});
48+
49+
test('error output', async () => {
50+
const term = new TestTerminal({ cols: 25, rows: 15 });
51+
52+
await runp({
53+
commands: [
54+
{
55+
name: 'command',
56+
async cmd({ updateOutput }) {
57+
updateOutput('line 1');
58+
// updateStatus('error')
59+
throw Error('error line');
60+
},
61+
},
62+
],
63+
target: term,
64+
linearOutput: true,
65+
});
66+
67+
await poll(
68+
() =>
69+
expect(term.getBuffer()).toEqual([
70+
' ',
71+
'-- [command] -> ',
72+
' ',
73+
'line 1 ',
74+
'Error: error line ',
75+
' ',
76+
'<- [command] -- ',
77+
' ',
78+
'✕ command [#.###s] ',
79+
' ',
80+
' ',
81+
' ',
82+
' ',
83+
' ',
84+
' ',
85+
]),
86+
1000,
87+
);
88+
});
89+
});

test/runp.test.ts

Lines changed: 1 addition & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,8 @@
1-
import { RenderOptions } from '@schummar/react-terminal';
21
import pty from 'node-pty';
32
import { setTimeout } from 'node:timers/promises';
43
import { describe, expect, test } from 'vitest';
5-
import { Terminal } from 'xterm-headless';
64
import { resolveCommands, runp } from '../src';
7-
8-
type Target = RenderOptions['target'] extends infer T | undefined ? T : never;
9-
10-
class TestTerminal implements Target {
11-
term: Terminal;
12-
write: Target['write'];
13-
columns: number;
14-
rows: number;
15-
16-
constructor(private options: { cols: number; rows: number }) {
17-
this.term = new Terminal({
18-
cols: this.options.cols,
19-
rows: this.options.rows,
20-
allowProposedApi: true,
21-
});
22-
23-
this.write = this.term.write.bind(this.term) as Target['write'];
24-
this.columns = this.options.cols;
25-
this.rows = this.options.rows;
26-
}
27-
28-
getBuffer = (all = false) => {
29-
const buffer = this.term.buffer.active;
30-
const offset = all ? 0 : buffer.baseY;
31-
32-
return Array(buffer.length - offset)
33-
.fill(0)
34-
.map((x, i) =>
35-
buffer
36-
.getLine(offset + i)
37-
?.translateToString()
38-
.replace(/[]/g, '⠋')
39-
.replace(/\[(.*)s\]/g, (x, y) => `[${''.padEnd(y.length - 4, '#')}.###s]`)
40-
.replace(/\xA0/g, ' '),
41-
);
42-
};
43-
}
44-
45-
const poll = async (assertion: () => void, max = 9_000) => {
46-
const start = performance.now();
47-
48-
for (;;) {
49-
try {
50-
assertion();
51-
return;
52-
} catch (e) {
53-
if (performance.now() - start > max) {
54-
throw e;
55-
}
56-
await setTimeout(10);
57-
}
58-
}
59-
};
5+
import { poll, TestTerminal } from './_helperts';
606

617
describe.concurrent('runp', () => {
628
test('node api', async () => {
@@ -350,48 +296,4 @@ describe.concurrent('runp', () => {
350296
expect(exact.map((x) => [x.cmd, ...x.args].join(' '))).toStrictEqual(['pnpm run --silent @complex/name:with:colons']);
351297
});
352298
});
353-
354-
describe('linear outout', () => {
355-
test('updating output', async () => {
356-
const term = new TestTerminal({ cols: 25, rows: 15 });
357-
358-
const [result] = await runp({
359-
commands: [
360-
{
361-
name: 'command',
362-
async cmd({ updateOutput }) {
363-
updateOutput('line 1');
364-
updateOutput('line 2');
365-
await setTimeout(1000);
366-
updateOutput('line 3');
367-
},
368-
},
369-
],
370-
target: term,
371-
linearOutput: true,
372-
});
373-
374-
await poll(
375-
() =>
376-
expect(term.getBuffer()).toEqual([
377-
' ',
378-
'-- [command] -> ',
379-
' ',
380-
'line 1 ',
381-
'line 2 ',
382-
'line 3 ',
383-
' ',
384-
'<- [command] -- ',
385-
' ',
386-
'✓ command [#.###s] ',
387-
' ',
388-
' ',
389-
' ',
390-
' ',
391-
' ',
392-
]),
393-
1000,
394-
);
395-
});
396-
});
397299
});

0 commit comments

Comments
 (0)