Skip to content

Commit 4ba3d8b

Browse files
fix(cli): O(n) truncateMessage and skip truncation when non-TTY
Closes #3826
1 parent a7c734c commit 4ba3d8b

2 files changed

Lines changed: 44 additions & 6 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trigger.dev": patch
3+
---
4+
5+
Skip spinner message truncation in non-TTY environments and make truncation O(n), so large deploys no longer hang at 100% CPU in CI.

packages/cli-v3/src/utilities/windows.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,55 @@ function getVisibleLength(str: string): number {
1919
}
2020

2121
function truncateMessage(msg: string, maxLength?: number): string {
22+
// Non-TTY (CI): no spinner width to honor — skip truncation entirely.
23+
// Character-by-character truncation is O(n²) and hangs deploys on large graphs.
24+
if (maxLength === undefined && (!process.stdout.isTTY || !process.stdout.columns)) {
25+
return msg;
26+
}
27+
2228
const terminalWidth = maxLength ?? process.stdout.columns ?? 80;
2329
const availableWidth = terminalWidth - 5; // Reserve some space for the spinner and padding
30+
const targetWidth = availableWidth - 3; // room for "..."
2431
const visibleLength = getVisibleLength(msg);
2532

2633
if (visibleLength <= availableWidth) {
2734
return msg;
2835
}
2936

30-
// We need to truncate based on visible characters, but preserve ANSI sequences
31-
// Simple approach: truncate character by character until we fit
32-
let truncated = msg;
33-
while (getVisibleLength(truncated) > availableWidth - 3) {
34-
truncated = truncated.slice(0, -1);
37+
// Walk once, counting visible characters, and cut at the first overflow.
38+
// Preserve ANSI sequences so colors/links remain valid.
39+
let visible = 0;
40+
let i = 0;
41+
while (i < msg.length) {
42+
// OSC 8 hyperlinks: \u001b]8;;URL\u0007TEXT\u001b]8;;\u0007
43+
if (msg.startsWith("\u001b]8;;", i)) {
44+
const end = msg.indexOf("\u0007", i);
45+
if (end === -1) {
46+
break;
47+
}
48+
i = end + 1;
49+
continue;
50+
}
51+
// CSI sequences: \x1b[...X
52+
if (msg[i] === "\x1b" && msg[i + 1] === "[") {
53+
i += 2;
54+
while (i < msg.length && !/[a-zA-Z]/.test(msg[i]!)) {
55+
i++;
56+
}
57+
if (i < msg.length) {
58+
i++;
59+
}
60+
continue;
61+
}
62+
63+
if (visible >= targetWidth) {
64+
return msg.slice(0, i) + "...";
65+
}
66+
visible++;
67+
i++;
3568
}
3669

37-
return truncated + "...";
70+
return msg.slice(0, i) + "...";
3871
}
3972

4073
const wrappedClackSpinner = () => {

0 commit comments

Comments
 (0)