mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-02-19 03:07:49 -05:00
[v13.0/forgejo] fix: don't show ConEmu OSC escape sequences (#9919)
**Backport:** https://codeberg.org/forgejo/forgejo/pulls/9875 - Remove all [ConEMU OSC commands](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC) from the output of Forgejo action logs when rendering. - The regex is constructed as followed: Match the prefix `ESC ] 9 ;`. Then matches any number of digits, then match everything up to and including `ST` (this is either `ESC\` or `BELL`). - Resolves forgejo/forgejo#9244 Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9919 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org> Co-committed-by: forgejo-backport-action <forgejo-backport-action@noreply.codeberg.org>
This commit is contained in:
parent
a50968d0de
commit
cb0845cd3e
4 changed files with 94 additions and 3 deletions
|
|
@ -623,3 +623,73 @@ test('view with pre-execution error', async () => {
|
|||
expect(block.exists()).toBe(true);
|
||||
expect(block.text()).toBe('pre-execution error Oops, I dropped it.');
|
||||
});
|
||||
|
||||
test('Offset index', async () => {
|
||||
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
||||
vi.spyOn(global, 'fetch').mockImplementation((url, opts) => {
|
||||
const stepsLog_value = [
|
||||
{
|
||||
step: 0,
|
||||
cursor: 0,
|
||||
lines: [
|
||||
{index: 1, message: '\u001b]9;4;3\u0007\r\u001bM\u001b[?2026l\u001b[?2026h\u001b[J', timestamp: 0},
|
||||
{index: 2, message: 'second line', timestamp: 0},
|
||||
{index: 3, message: '\u001b]9;4;3\u0007\r\u001bM\u001b[?2026l\u001b[J\u001b]9;4;0\u0007\u001b[?2026h\u001b[J\u001b]9;4;1;0\u0007\u001b[?2026l\u001b[J\u001b]9;4;0\u0007', timestamp: 0},
|
||||
{index: 4, message: 'fourth line', timestamp: 0},
|
||||
],
|
||||
},
|
||||
];
|
||||
const jobs_value = {
|
||||
state: {
|
||||
run: {
|
||||
status: 'success',
|
||||
commit: {
|
||||
pusher: {},
|
||||
},
|
||||
},
|
||||
currentJob: {
|
||||
steps: [
|
||||
{
|
||||
summary: 'Test Job',
|
||||
duration: '1s',
|
||||
status: 'success',
|
||||
},
|
||||
],
|
||||
allAttempts: [{number: 1, time_since_started_html: '', status: 'success'}],
|
||||
},
|
||||
},
|
||||
logs: {
|
||||
stepsLog: opts.body?.includes('"cursor":null') ? stepsLog_value : [],
|
||||
},
|
||||
};
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue(
|
||||
url.endsWith('/artifacts') ? [] : jobs_value,
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
const wrapper = mount(RepoActionView, {
|
||||
props: defaultTestProps,
|
||||
});
|
||||
await flushPromises();
|
||||
await wrapper.get('.job-step-summary').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
// Check if two lines where rendered
|
||||
expect(wrapper.findAll('.job-log-line').length).toEqual(2);
|
||||
|
||||
// Check line one.
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(1)').attributes('id')).toEqual('jobstep-0-1');
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(1) .line-num').text()).toEqual('1');
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(1) .line-num').attributes('href')).toEqual('#jobstep-0-1');
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(1) .log-msg').text()).toEqual('second line');
|
||||
|
||||
// Check line two.
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(2)').attributes('id')).toEqual('jobstep-0-2');
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(2) .line-num').text()).toEqual('2');
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(2) .line-num').attributes('href')).toEqual('#jobstep-0-2');
|
||||
expect(wrapper.get('.job-log-line:nth-of-type(2) .log-msg').text()).toEqual('fourth line');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ const sfc = {
|
|||
initialLoadComplete: false,
|
||||
needLoadingWithLogCursors: null,
|
||||
intervalID: null,
|
||||
lineNumberOffset: [],
|
||||
currentJobStepsStates: [],
|
||||
artifacts: [],
|
||||
menuVisible: undefined,
|
||||
|
|
@ -203,15 +204,16 @@ const sfc = {
|
|||
},
|
||||
|
||||
createLogLine(line, startTime, stepIndex, group) {
|
||||
const lineNo = line.index - this.lineNumberOffset[stepIndex];
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('job-log-line');
|
||||
div.setAttribute('id', `jobstep-${stepIndex}-${line.index}`);
|
||||
div.setAttribute('id', `jobstep-${stepIndex}-${lineNo}`);
|
||||
div._jobLogTime = line.timestamp;
|
||||
|
||||
const lineNumber = document.createElement('a');
|
||||
lineNumber.classList.add('line-num', 'muted');
|
||||
lineNumber.textContent = line.index;
|
||||
lineNumber.setAttribute('href', `#jobstep-${stepIndex}-${line.index}`);
|
||||
lineNumber.textContent = lineNo;
|
||||
lineNumber.setAttribute('href', `#jobstep-${stepIndex}-${lineNo}`);
|
||||
div.append(lineNumber);
|
||||
|
||||
// for "Show timestamps"
|
||||
|
|
@ -230,6 +232,13 @@ const sfc = {
|
|||
|
||||
let logMessage = document.createElement('span');
|
||||
logMessage.innerHTML = renderAnsi(line.message);
|
||||
// If the input to renderAnsi is not empty and the output is empty we can
|
||||
// assume the input was only ANSI escape codes that have been removed. In
|
||||
// that case we should not display this message
|
||||
if (line.message !== '' && logMessage.innerHTML === '') {
|
||||
this.lineNumberOffset[stepIndex]++;
|
||||
return [];
|
||||
}
|
||||
if (group.isHeader) {
|
||||
const details = document.createElement('details');
|
||||
details.addEventListener('toggle', this.toggleGroupLogs);
|
||||
|
|
@ -378,6 +387,7 @@ const sfc = {
|
|||
// append logs to the UI
|
||||
for (const logs of job.logs.stepsLog) {
|
||||
// save the cursor, it will be passed to backend next time
|
||||
this.lineNumberOffset[logs.step] = 0;
|
||||
this.currentJobStepsStates[logs.step].cursor = logs.cursor;
|
||||
this.appendLogs(logs.step, logs.lines, logs.started);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import {AnsiUp} from 'ansi_up';
|
|||
|
||||
const replacements = [
|
||||
[/\x1b\[\d+[A-H]/g, ''], // Move cursor, treat them as no-op
|
||||
[/\x1bM/g, ''], // Move cursor one line up, threat them as no-op.
|
||||
[/\x1b\[\d?[JK]/g, '\r'], // Erase display/line, treat them as a Carriage Return
|
||||
[/\x1b\]9;\d+.*?(\x07|\x1b\\)/g, ''], // ConEmu, treat them as no-op.
|
||||
];
|
||||
|
||||
// render ANSI to HTML
|
||||
|
|
|
|||
|
|
@ -17,4 +17,13 @@ test('renderAnsi', () => {
|
|||
// treat "\033[0K" and "\033[0J" (Erase display/line) as "\r", then it will be covered to "\n" finally.
|
||||
expect(renderAnsi('a\x1b[Kb\x1b[2Jc')).toEqual('a\nb\nc');
|
||||
expect(renderAnsi('\x1b[48;5;88ma\x1b[38;208;48;5;159mb\x1b[m')).toEqual(`<span style="background-color:rgb(135,0,0)">a</span><span style="background-color:rgb(175,255,255)">b</span>`);
|
||||
|
||||
expect(renderAnsi('\x1b]9;4;0\x07')).toEqual('');
|
||||
expect(renderAnsi('\x1b]9;4;1;25\x07compiling main.zig')).toEqual('compiling main.zig');
|
||||
expect(renderAnsi('\x1b]9;4;1;25\x1b\\compiling main.zig')).toEqual('compiling main.zig');
|
||||
expect(renderAnsi('\x1b]9;4;3\x07waiting...')).toEqual('waiting...');
|
||||
expect(renderAnsi('\x1b]9;1;500\x07sleeping...')).toEqual('sleeping...');
|
||||
expect(renderAnsi('\x1b]9;4;3\x07waiting...\x1b]9;4;3\x07')).toEqual('waiting...');
|
||||
expect(renderAnsi('\x1b]9;12\x07')).toEqual('');
|
||||
expect(renderAnsi('\x1b]9;4;1;25\x07\x1bMcompiling main.zig')).toEqual('compiling main.zig');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue