feat(terminal): enhance terminal task rendering with progress, lifecycle helpers, and configurable output modes

This commit is contained in:
2026-05-13 18:33:06 +00:00
parent c07b2969b8
commit 502cca375f
6 changed files with 507 additions and 124 deletions
-79
View File
@@ -3,25 +3,6 @@ import * as smartrx from '@push.rocks/smartrx';
import * as smartcli from '../ts/index.js';
class TestWritable implements smartcli.ISmartcliWritable {
public chunks: string[] = [];
public isTTY: boolean;
public columns = 80;
constructor(isTTYArg: boolean) {
this.isTTY = isTTYArg;
}
public write(chunkArg: string): boolean {
this.chunks.push(chunkArg);
return true;
}
public toString(): string {
return this.chunks.join('');
}
}
tap.test('should create a new Smartcli', async () => {
const smartCliTestObject = new smartcli.Smartcli();
expect(smartCliTestObject).toBeInstanceOf(smartcli.Smartcli);
@@ -55,64 +36,4 @@ tap.test('should accept a command', async () => {
expect(hasExecuted).toBeTrue();
});
tap.test('should render terminal tasks in non-interactive mode', async () => {
const stream = new TestWritable(false);
const terminal = new smartcli.SmartcliTerminal({ stream, interactive: false, colors: false });
const task = terminal.createTask({ job: 'build package', rows: 2 });
task.update('running tsbuild');
task.complete('done');
const output = stream.toString();
expect(output).toInclude('[start] build package');
expect(output).toInclude('[build package] running tsbuild');
expect(output).toInclude('[ok] build package - done');
expect(terminal.getTasks()).toHaveLength(0);
});
tap.test('should render fixed rows in interactive mode', async () => {
const stream = new TestWritable(true);
const terminal = new smartcli.SmartcliTerminal({ stream, interactive: true, colors: false });
const task = terminal.createTask({ job: 'install dependencies', rows: 2 });
task.update('fetching packages');
const renderedRows = task.renderPlainRows(80);
expect(renderedRows).toHaveLength(2);
expect(renderedRows[0]).toInclude('[run] install dependencies');
expect(stream.toString()).toInclude('\u001B[2K');
task.complete('installed');
expect(stream.toString()).toInclude('[ok] install dependencies - installed');
expect(terminal.getTasks()).toHaveLength(0);
});
tap.test('should attach persistent terminal task errors', async () => {
const stream = new TestWritable(true);
const terminal = new smartcli.SmartcliTerminal({ stream, interactive: true, colors: false });
const task = terminal.createTask({ job: 'deploy release', rows: 3 });
task.attachError('deployment failed', { keepOpen: true });
expect(task.status).toEqual('failed');
expect(task.getErrorLines()).toContain('deployment failed');
expect(task.renderPlainRows(80)[0]).toInclude('[err] deploy release');
expect(terminal.getTasks()).toHaveLength(1);
terminal.clear();
});
tap.test('should collapse failed terminal tasks into permanent output', async () => {
const stream = new TestWritable(false);
const terminal = new smartcli.SmartcliTerminal({ stream, interactive: false, colors: false });
const task = terminal.createTask({ job: 'publish package' });
task.attachError('registry rejected package');
const output = stream.toString();
expect(output).toInclude('[err] publish package - registry rejected package');
expect(terminal.getTasks()).toHaveLength(0);
});
export default tap.start();