diff --git a/src/build-logs.ts b/src/build-logs.ts index 58c5227..bfbed6a 100644 --- a/src/build-logs.ts +++ b/src/build-logs.ts @@ -100,7 +100,7 @@ export async function readBuildLogs(api: Api, projectId: string, source: BuildSo return { ...first, nextCursor: undefined, state: pending ? 'pending' : first.state, steps, output } } -const safeText = (text: string) => stripVTControlCharacters(text).replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '') +const safeText = (text: string) => stripVTControlCharacters(text).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, '') export class BuildLogPrinter { private seen = new Map() diff --git a/test/build-logs.test.ts b/test/build-logs.test.ts index 8cd15d7..008924c 100644 --- a/test/build-logs.test.ts +++ b/test/build-logs.test.ts @@ -47,6 +47,13 @@ describe('build logs', () => { expect(write.mock.calls.map(([text]) => text)).toEqual(['RUN test\n', 'same\n', 'same\n', 'late\n', 'same\n']) }) + it('strips bidi overrides, invisible marks and C1 controls while keeping tabs and newlines', () => { + const printer = new BuildLogPrinter() + const write = vi.fn() + printer.print({ ...base, state: 'ready', output: [{ step: 'step1', name: 'RUN test', entries: [entry('a\u202eb\u0080\u0085c\u2066d\u2069e\u200ef\u200fg\u202ah\u009fi\n'), entry('col1\tcol2\n'), entry('plain text ok\n')] }] }, write) + expect(write.mock.calls.map(([text]) => text)).toEqual(['RUN test\n', 'abcdefghi\n', 'col1\tcol2\n', 'plain text ok\n']) + }) + it('resumes after transient errors and reads delayed terminal output', async () => { let reads = 0 const api = apiWith((path) => {