Files
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

238 lines
5.9 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Writable} from 'node:stream';
import blessed from 'blessed';
import stripAnsi from 'strip-ansi';
class Runner {
commands;
filter = '';
ui;
scrollLocked = true;
buffer = [];
partialBuffer = '';
outputStream;
closeListeners = new Set();
constructor(commands) {
this.commands = commands;
this.outputStream = new Writable({
write: (chunk, encoding, callback) => this.writeToStream(chunk, encoding, callback),
});
this.makeUi(commands.map((command) => command.name), this.onFilter);
this.registerHotkeys();
}
// Initialization
makeUi(commandNames) {
// Set up screen and output panes
const screen = blessed.screen({
smartCSR: true,
dockBorders: true,
});
const output = blessed.box({
top: 0,
left: 0,
width: '100%',
height: '100%-3',
content: 'THE END IS NEVER '.repeat(1000),
tags: true,
alwaysScroll: true,
scrollable: true,
scrollbar: {
ch: '#',
style: {},
track: {
ch: '|',
},
},
style: {},
});
screen.append(output);
// Set up the menu bar
const menu = blessed.listbar({
top: '100%-3',
left: 0,
width: '100%',
height: 3,
border: {
type: 'line',
},
style: {
item: {
bg: 'red',
hover: {
bg: 'green',
},
},
selected: {
bg: 'blue',
},
},
tags: true,
autoCommandKeys: true,
mouse: true,
});
menu.add('All', () => this.onFilter(''));
for (const name of commandNames) {
menu.add(name, () => this.onFilter(name));
}
screen.append(menu);
this.ui = {
menu,
output,
screen,
};
}
registerHotkeys() {
this.ui.screen.key(['escape', 'q', 'C-c'], () => {
for (const listener of this.closeListeners) {
listener();
}
});
this.ui.screen.key(['up', 'down'], (char, key) => {
this.scrollDelta(key.name === 'up' ? -1 : 1);
});
this.ui.screen.on('wheelup', () => {
this.scrollDelta(-3);
});
this.ui.screen.on('wheeldown', () => {
this.scrollDelta(3);
});
this.ui.screen.key('end', () => {
this.scrollToBottom();
});
}
// Rendering and internal logic
renderUi() {
const filtered = this.buffer.filter((line) => this.filter === '' || line.tag === this.filter);
this.ui.output.setContent(filtered.map((line) => this.formatLine(line)).join('\n'));
if (this.scrollLocked) {
this.ui.output.scrollbar.style.inverse = true;
this.ui.output.setScrollPerc(100);
} else {
this.ui.output.scrollbar.style.inverse = false;
}
this.ui.screen.render();
}
formatLine(line) {
const color = this.commands.find((command) => command.name === line.tag)?.prefixColor;
return color ? `{bold}{${color}-fg}[${line.tag}]{/} ${line.text}` : `[${line.tag}] ${line.text}`;
}
onFilter(newFilter) {
this.filter = newFilter;
this.scrollLocked = true;
this.renderUi();
}
scrollDelta(delta) {
this.ui.output.scroll(delta);
if (this.ui.output.getScrollPerc() >= 100 || this.ui.output.getScrollHeight() <= this.ui.output.height) {
this.scrollLocked = true;
} else {
this.scrollLocked = false;
}
this.renderUi();
}
scrollToBottom() {
this.scrollLocked = true;
this.renderUi();
}
// Terminal output handling
getOutputStream() {
return this.outputStream;
}
writeToStream(chunk, encoding, callback) {
const str = String(chunk);
if (str.includes('\n')) {
const parts = str.split('\n');
// Add completed lines to buffer
this.appendToBuffer(this.partialBuffer + parts[0]);
for (let i = 1; i < parts.length - 1; i++) {
this.appendToBuffer(parts[i]);
}
// Track partial line
this.partialBuffer = parts[parts.length - 1];
} else {
// Track partial line
this.partialBuffer += str;
}
this.renderUi();
callback();
}
appendToBuffer(line) {
// This regex is more complicated than expected because it
const match = (/^\[([^\]]*)\]\s*(.*)$/).exec(stripAnsi(line));
if (match) {
this.buffer.push({tag: match[1], text: match[2]});
} else {
this.buffer.push({tag: '', text: 'Line not recognized correctly: ' + line});
}
// Keep the buffer from using too much memory by removing the oldest chunk of it every time it goes over 5000 lines
const bufferCapacity = 5000;
const capacityReduction = 1000;
if (this.buffer.length > bufferCapacity) {
this.buffer = this.buffer.slice(this.buffer.length - capacityReduction);
}
}
// Event handlers
addCloseListener(listener) {
this.closeListeners.add(listener);
}
removeCloseListener(listener) {
this.closeListeners.remove(listener);
}
}
export function makeRunner(commands) {
const runner = new Runner(commands);
runner.renderUi();
return runner;
}