bktide 1.0.1755547716 → 1.0.1755568192
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/ShowBuild.js +92 -0
- package/dist/commands/ShowBuild.js.map +1 -0
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/formatters/FormatterFactory.js +4 -0
- package/dist/formatters/FormatterFactory.js.map +1 -1
- package/dist/formatters/build-detail/AlfredFormatter.js +113 -0
- package/dist/formatters/build-detail/AlfredFormatter.js.map +1 -0
- package/dist/formatters/build-detail/Formatter.js +3 -0
- package/dist/formatters/build-detail/Formatter.js.map +1 -0
- package/dist/formatters/build-detail/JsonFormatter.js +132 -0
- package/dist/formatters/build-detail/JsonFormatter.js.map +1 -0
- package/dist/formatters/build-detail/PlainTextFormatter.js +851 -0
- package/dist/formatters/build-detail/PlainTextFormatter.js.map +1 -0
- package/dist/formatters/build-detail/index.js +21 -0
- package/dist/formatters/build-detail/index.js.map +1 -0
- package/dist/formatters/builds/PlainTextFormatter.js +4 -2
- package/dist/formatters/builds/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/pipelines/PlainTextFormatter.js +3 -6
- package/dist/formatters/pipelines/PlainTextFormatter.js.map +1 -1
- package/dist/graphql/queries.js +185 -0
- package/dist/graphql/queries.js.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/services/BuildkiteClient.js +87 -26
- package/dist/services/BuildkiteClient.js.map +1 -1
- package/dist/services/BuildkiteRestClient.js +8 -7
- package/dist/services/BuildkiteRestClient.js.map +1 -1
- package/dist/ui/theme.js +193 -8
- package/dist/ui/theme.js.map +1 -1
- package/dist/utils/terminal-links.js +165 -0
- package/dist/utils/terminal-links.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
import { BaseBuildDetailFormatter } from './Formatter.js';
|
|
2
|
+
import { formatDistanceToNow } from 'date-fns';
|
|
3
|
+
import { htmlToText } from 'html-to-text';
|
|
4
|
+
import { formatEmptyState, formatError, SEMANTIC_COLORS, formatBuildStatus, formatTips, TipStyle, getStateIcon, getAnnotationIcon, getProgressIcon, BUILD_STATUS_THEME } from '../../ui/theme.js';
|
|
5
|
+
// Standard emoji mappings only
|
|
6
|
+
// Only map universally recognized emoji codes, not Buildkite-specific ones
|
|
7
|
+
const STANDARD_EMOJI = {
|
|
8
|
+
// Faces & emotions
|
|
9
|
+
':smile:': '😊',
|
|
10
|
+
':grin:': '😁',
|
|
11
|
+
':joy:': '😂',
|
|
12
|
+
':laughing:': '😆',
|
|
13
|
+
':blush:': '😊',
|
|
14
|
+
':heart_eyes:': '😍',
|
|
15
|
+
':sob:': '😭',
|
|
16
|
+
':cry:': '😢',
|
|
17
|
+
':angry:': '😠',
|
|
18
|
+
':rage:': '😡',
|
|
19
|
+
':thinking:': '🤔',
|
|
20
|
+
':confused:': '😕',
|
|
21
|
+
':neutral_face:': '😐',
|
|
22
|
+
// Hands & gestures
|
|
23
|
+
':thumbsup:': '👍',
|
|
24
|
+
':thumbsdown:': '👎',
|
|
25
|
+
':clap:': '👏',
|
|
26
|
+
':wave:': '👋',
|
|
27
|
+
':raised_hand:': '✋',
|
|
28
|
+
':ok_hand:': '👌',
|
|
29
|
+
':pray:': '🙏',
|
|
30
|
+
':muscle:': '💪',
|
|
31
|
+
':point_left:': '👈',
|
|
32
|
+
':point_right:': '👉',
|
|
33
|
+
':point_up:': '👆',
|
|
34
|
+
':point_down:': '👇',
|
|
35
|
+
// Objects & symbols
|
|
36
|
+
':heart:': '❤️',
|
|
37
|
+
':broken_heart:': '💔',
|
|
38
|
+
':star:': '⭐',
|
|
39
|
+
':sparkles:': '✨',
|
|
40
|
+
':boom:': '💥',
|
|
41
|
+
':fire:': '🔥',
|
|
42
|
+
':zap:': '⚡',
|
|
43
|
+
':rocket:': '🚀',
|
|
44
|
+
':sun:': '☀️',
|
|
45
|
+
':moon:': '🌙',
|
|
46
|
+
':cloud:': '☁️',
|
|
47
|
+
':umbrella:': '☔',
|
|
48
|
+
':snowflake:': '❄️',
|
|
49
|
+
// Status symbols
|
|
50
|
+
':white_check_mark:': '✅',
|
|
51
|
+
':x:': '❌',
|
|
52
|
+
':warning:': '⚠️',
|
|
53
|
+
':exclamation:': '❗',
|
|
54
|
+
':question:': '❓',
|
|
55
|
+
':heavy_plus_sign:': '➕',
|
|
56
|
+
':heavy_minus_sign:': '➖',
|
|
57
|
+
':heavy_check_mark:': '✔️',
|
|
58
|
+
// Common tools/tech (universally recognized)
|
|
59
|
+
':computer:': '💻',
|
|
60
|
+
':iphone:': '📱',
|
|
61
|
+
':email:': '📧',
|
|
62
|
+
':package:': '📦',
|
|
63
|
+
':lock:': '🔒',
|
|
64
|
+
':key:': '🔑',
|
|
65
|
+
':mag:': '🔍',
|
|
66
|
+
':bulb:': '💡',
|
|
67
|
+
':books:': '📚',
|
|
68
|
+
':memo:': '📝',
|
|
69
|
+
':pencil:': '✏️',
|
|
70
|
+
':art:': '🎨',
|
|
71
|
+
':camera:': '📷',
|
|
72
|
+
':movie_camera:': '🎥',
|
|
73
|
+
':musical_note:': '🎵',
|
|
74
|
+
':bell:': '🔔',
|
|
75
|
+
':link:': '🔗',
|
|
76
|
+
':paperclip:': '📎',
|
|
77
|
+
':hourglass:': '⏳',
|
|
78
|
+
':alarm_clock:': '⏰',
|
|
79
|
+
':stopwatch:': '⏱️',
|
|
80
|
+
':timer_clock:': '⏲️',
|
|
81
|
+
':calendar:': '📅',
|
|
82
|
+
':date:': '📅',
|
|
83
|
+
};
|
|
84
|
+
export class PlainTextFormatter extends BaseBuildDetailFormatter {
|
|
85
|
+
name = 'plain-text';
|
|
86
|
+
parseEmoji(text) {
|
|
87
|
+
if (!text)
|
|
88
|
+
return text;
|
|
89
|
+
// Only replace standard emoji codes, leave Buildkite-specific ones as-is
|
|
90
|
+
return text.replace(/:[\w_]+:/g, (match) => {
|
|
91
|
+
return STANDARD_EMOJI[match] || match;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
formatBuildDetail(buildData, options) {
|
|
95
|
+
// Handle error cases first
|
|
96
|
+
if (options?.hasError || !buildData) {
|
|
97
|
+
return this.formatErrorState(options);
|
|
98
|
+
}
|
|
99
|
+
const build = buildData.build;
|
|
100
|
+
// Choose display mode based on options
|
|
101
|
+
if (options?.summary) {
|
|
102
|
+
return this.formatSummaryLine(build);
|
|
103
|
+
}
|
|
104
|
+
if (options?.full) {
|
|
105
|
+
return this.formatFullDetails(build, options);
|
|
106
|
+
}
|
|
107
|
+
// Default: contextual display based on state
|
|
108
|
+
switch (build.state) {
|
|
109
|
+
case 'FAILED':
|
|
110
|
+
return this.formatFailedBuild(build, options);
|
|
111
|
+
case 'RUNNING':
|
|
112
|
+
return this.formatRunningBuild(build, options);
|
|
113
|
+
case 'BLOCKED':
|
|
114
|
+
return this.formatBlockedBuild(build, options);
|
|
115
|
+
case 'PASSED':
|
|
116
|
+
return this.formatPassedBuild(build, options);
|
|
117
|
+
case 'CANCELED':
|
|
118
|
+
return this.formatCanceledBuild(build, options);
|
|
119
|
+
default:
|
|
120
|
+
return this.formatDefaultBuild(build, options);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
formatErrorState(options) {
|
|
124
|
+
if (options?.errorType === 'not_found') {
|
|
125
|
+
return formatEmptyState('Build not found', ['Check the build reference format', 'Verify the build exists']);
|
|
126
|
+
}
|
|
127
|
+
return formatError(options?.errorMessage || 'Unknown error');
|
|
128
|
+
}
|
|
129
|
+
formatSummaryLine(build) {
|
|
130
|
+
const statusIcon = this.getStatusIcon(build.state);
|
|
131
|
+
const coloredIcon = this.colorizeStatusIcon(statusIcon, build.state);
|
|
132
|
+
const duration = this.formatDuration(build);
|
|
133
|
+
const age = this.formatAge(build.createdAt);
|
|
134
|
+
const stateFormatted = formatBuildStatus(build.state, { useSymbol: false });
|
|
135
|
+
const branch = SEMANTIC_COLORS.identifier(build.branch);
|
|
136
|
+
return `${coloredIcon} ${SEMANTIC_COLORS.label(`#${build.number}`)} ${stateFormatted} • ${duration} • ${branch} • ${age}`;
|
|
137
|
+
}
|
|
138
|
+
formatPassedBuild(build, options) {
|
|
139
|
+
const lines = [];
|
|
140
|
+
// Header line
|
|
141
|
+
lines.push(this.formatHeader(build));
|
|
142
|
+
lines.push(this.formatCommitInfo(build));
|
|
143
|
+
// Show annotations summary if present
|
|
144
|
+
if (build.annotations?.edges?.length > 0) {
|
|
145
|
+
lines.push('');
|
|
146
|
+
lines.push(this.formatAnnotationSummary(build.annotations.edges));
|
|
147
|
+
if (!options?.annotations) {
|
|
148
|
+
lines.push('');
|
|
149
|
+
const tips = formatTips(['Use --annotations to view annotation details'], TipStyle.GROUPED);
|
|
150
|
+
lines.push(tips);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Show annotations detail if requested
|
|
154
|
+
if (options?.annotations) {
|
|
155
|
+
lines.push('');
|
|
156
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges));
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\n');
|
|
159
|
+
}
|
|
160
|
+
formatFailedBuild(build, options) {
|
|
161
|
+
const lines = [];
|
|
162
|
+
// Header line
|
|
163
|
+
lines.push(this.formatHeader(build));
|
|
164
|
+
lines.push(this.formatCommitInfo(build));
|
|
165
|
+
lines.push('');
|
|
166
|
+
// Failed jobs summary
|
|
167
|
+
const failedJobs = this.getFailedJobs(build.jobs?.edges);
|
|
168
|
+
const allHints = [];
|
|
169
|
+
if (failedJobs.length > 0) {
|
|
170
|
+
const { summary, hints } = this.formatFailedJobsSummaryWithHints(failedJobs, options);
|
|
171
|
+
lines.push(summary);
|
|
172
|
+
allHints.push(...hints);
|
|
173
|
+
}
|
|
174
|
+
// Annotation summary
|
|
175
|
+
if (build.annotations?.edges?.length > 0) {
|
|
176
|
+
lines.push(this.formatAnnotationSummary(build.annotations.edges));
|
|
177
|
+
}
|
|
178
|
+
// Show detailed job info if requested
|
|
179
|
+
if (options?.jobs || options?.failed) {
|
|
180
|
+
lines.push('');
|
|
181
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
182
|
+
}
|
|
183
|
+
// Show annotations detail if requested
|
|
184
|
+
if (options?.annotations) {
|
|
185
|
+
lines.push('');
|
|
186
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges));
|
|
187
|
+
}
|
|
188
|
+
// Collect all hints for more info
|
|
189
|
+
if (!options?.failed && failedJobs.length > 0) {
|
|
190
|
+
allHints.push('Use --failed to show failure details');
|
|
191
|
+
}
|
|
192
|
+
if (!options?.annotations && build.annotations?.edges?.length > 0) {
|
|
193
|
+
allHints.push('Use --annotations to view annotation details');
|
|
194
|
+
}
|
|
195
|
+
// Display all hints together
|
|
196
|
+
if (allHints.length > 0) {
|
|
197
|
+
lines.push('');
|
|
198
|
+
lines.push(formatTips(allHints, TipStyle.GROUPED));
|
|
199
|
+
}
|
|
200
|
+
return lines.join('\n');
|
|
201
|
+
}
|
|
202
|
+
formatRunningBuild(build, options) {
|
|
203
|
+
const lines = [];
|
|
204
|
+
// Header line
|
|
205
|
+
lines.push(this.formatHeader(build));
|
|
206
|
+
lines.push(this.formatCommitInfo(build));
|
|
207
|
+
lines.push('');
|
|
208
|
+
// Progress information
|
|
209
|
+
const jobStats = this.getJobStats(build.jobs?.edges);
|
|
210
|
+
lines.push(`Progress: ${SEMANTIC_COLORS.count(String(jobStats.completed))}/${jobStats.total} complete, ${SEMANTIC_COLORS.info(String(jobStats.running))} running, ${jobStats.queued} queued`);
|
|
211
|
+
// Show running jobs
|
|
212
|
+
const runningJobs = this.getRunningJobs(build.jobs?.edges);
|
|
213
|
+
if (runningJobs.length > 0) {
|
|
214
|
+
const labels = runningJobs.map(j => this.parseEmoji(j.node.label)).join(', ');
|
|
215
|
+
lines.push(`${SEMANTIC_COLORS.info('Running')}: ${labels}`);
|
|
216
|
+
}
|
|
217
|
+
// Annotation summary
|
|
218
|
+
if (build.annotations?.edges?.length > 0) {
|
|
219
|
+
lines.push('');
|
|
220
|
+
lines.push(this.formatAnnotationSummary(build.annotations.edges));
|
|
221
|
+
}
|
|
222
|
+
// Show job details if requested
|
|
223
|
+
if (options?.jobs) {
|
|
224
|
+
lines.push('');
|
|
225
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
226
|
+
}
|
|
227
|
+
// Show annotations detail if requested
|
|
228
|
+
if (options?.annotations) {
|
|
229
|
+
lines.push('');
|
|
230
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges));
|
|
231
|
+
}
|
|
232
|
+
return lines.join('\n');
|
|
233
|
+
}
|
|
234
|
+
formatBlockedBuild(build, options) {
|
|
235
|
+
const lines = [];
|
|
236
|
+
// Header line
|
|
237
|
+
lines.push(this.formatHeader(build));
|
|
238
|
+
lines.push(this.formatCommitInfo(build));
|
|
239
|
+
lines.push('');
|
|
240
|
+
// Blocked information
|
|
241
|
+
const blockedJobs = this.getBlockedJobs(build.jobs?.edges);
|
|
242
|
+
if (blockedJobs.length > 0) {
|
|
243
|
+
lines.push(`${getProgressIcon('BLOCKED_MESSAGE')} Blocked: "${blockedJobs[0].node.label}" (manual unblock required)`);
|
|
244
|
+
}
|
|
245
|
+
// Show jobs summary
|
|
246
|
+
const jobStats = this.getJobStats(build.jobs?.edges);
|
|
247
|
+
if (jobStats.completed > 0) {
|
|
248
|
+
lines.push(`${getStateIcon('PASSED')} ${jobStats.completed} jobs passed before block`);
|
|
249
|
+
}
|
|
250
|
+
// Annotation summary
|
|
251
|
+
if (build.annotations?.edges?.length > 0) {
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push(this.formatAnnotationSummary(build.annotations.edges));
|
|
254
|
+
}
|
|
255
|
+
// Show job details if requested
|
|
256
|
+
if (options?.jobs) {
|
|
257
|
+
lines.push('');
|
|
258
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
259
|
+
}
|
|
260
|
+
// Show annotations detail if requested
|
|
261
|
+
if (options?.annotations) {
|
|
262
|
+
lines.push('');
|
|
263
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges));
|
|
264
|
+
}
|
|
265
|
+
return lines.join('\n');
|
|
266
|
+
}
|
|
267
|
+
formatCanceledBuild(build, options) {
|
|
268
|
+
const lines = [];
|
|
269
|
+
// Header line
|
|
270
|
+
lines.push(this.formatHeader(build));
|
|
271
|
+
lines.push(this.formatCommitInfo(build));
|
|
272
|
+
lines.push('');
|
|
273
|
+
// Canceled information
|
|
274
|
+
if (build.createdBy) {
|
|
275
|
+
const creator = build.createdBy.name || build.createdBy.email;
|
|
276
|
+
lines.push(`Canceled by: ${creator}`);
|
|
277
|
+
}
|
|
278
|
+
// Show jobs summary
|
|
279
|
+
const jobStats = this.getJobStats(build.jobs?.edges);
|
|
280
|
+
lines.push(`Completed: ${jobStats.completed}/${jobStats.total} jobs before cancellation`);
|
|
281
|
+
// Show job details if requested
|
|
282
|
+
if (options?.jobs) {
|
|
283
|
+
lines.push('');
|
|
284
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
285
|
+
}
|
|
286
|
+
return lines.join('\n');
|
|
287
|
+
}
|
|
288
|
+
formatDefaultBuild(build, options) {
|
|
289
|
+
return this.formatPassedBuild(build, options);
|
|
290
|
+
}
|
|
291
|
+
formatFullDetails(build, options) {
|
|
292
|
+
const lines = [];
|
|
293
|
+
// Full header information
|
|
294
|
+
lines.push(this.formatHeader(build));
|
|
295
|
+
lines.push(this.formatCommitInfo(build));
|
|
296
|
+
lines.push('');
|
|
297
|
+
// Build metadata
|
|
298
|
+
lines.push('Build Details:');
|
|
299
|
+
lines.push(` URL: ${build.url}`);
|
|
300
|
+
lines.push(` Organization: ${build.organization?.name || 'Unknown'}`);
|
|
301
|
+
lines.push(` Pipeline: ${build.pipeline?.name || 'Unknown'}`);
|
|
302
|
+
if (build.pullRequest) {
|
|
303
|
+
// Try to construct PR URL from repository URL
|
|
304
|
+
const repoUrl = build.pipeline?.repository?.url;
|
|
305
|
+
if (repoUrl && repoUrl.includes('github.com')) {
|
|
306
|
+
// Extract owner/repo from various GitHub URL formats
|
|
307
|
+
const match = repoUrl.match(/github\.com[/:]([\w-]+)\/([\w-]+)/);
|
|
308
|
+
if (match && build.pullRequest.id) {
|
|
309
|
+
// Extract PR number from GraphQL ID if possible
|
|
310
|
+
// GitHub PR IDs often contain the number
|
|
311
|
+
const prUrl = `https://github.com/${match[1]}/${match[2]}/pull/${build.pullRequest.id}`;
|
|
312
|
+
lines.push(` Pull Request: ${SEMANTIC_COLORS.url(prUrl)}`);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
lines.push(` Pull Request: ${build.pullRequest.id}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
lines.push(` Pull Request: ${build.pullRequest.id}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (build.triggeredFrom) {
|
|
323
|
+
lines.push(` Triggered from: ${build.triggeredFrom.pipeline?.name} #${build.triggeredFrom.number}`);
|
|
324
|
+
}
|
|
325
|
+
lines.push('');
|
|
326
|
+
// Jobs section
|
|
327
|
+
lines.push('Jobs:');
|
|
328
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, { ...options, full: true }));
|
|
329
|
+
// Annotations section
|
|
330
|
+
if (build.annotations?.edges?.length > 0) {
|
|
331
|
+
lines.push('');
|
|
332
|
+
lines.push('Annotations:');
|
|
333
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges));
|
|
334
|
+
}
|
|
335
|
+
return lines.join('\n');
|
|
336
|
+
}
|
|
337
|
+
formatHeader(build) {
|
|
338
|
+
const statusIcon = this.getStatusIcon(build.state);
|
|
339
|
+
// Apply appropriate color to the icon based on the state
|
|
340
|
+
const coloredIcon = this.colorizeStatusIcon(statusIcon, build.state);
|
|
341
|
+
const stateFormatted = formatBuildStatus(build.state, { useSymbol: false });
|
|
342
|
+
const duration = this.formatDuration(build);
|
|
343
|
+
const age = this.formatAge(build.createdAt);
|
|
344
|
+
const branch = SEMANTIC_COLORS.identifier(build.branch);
|
|
345
|
+
return `${coloredIcon} ${SEMANTIC_COLORS.label(`#${build.number}`)} ${stateFormatted} • ${duration} • ${branch} • ${age}`;
|
|
346
|
+
}
|
|
347
|
+
formatCommitInfo(build) {
|
|
348
|
+
const shortSha = build.commit ? build.commit.substring(0, 7) : 'unknown';
|
|
349
|
+
const message = build.message || 'No commit message';
|
|
350
|
+
const truncatedMessage = message.length > 60 ? message.substring(0, 57) + '...' : message;
|
|
351
|
+
return ` "${truncatedMessage}" (${shortSha})`;
|
|
352
|
+
}
|
|
353
|
+
formatAnnotationSummary(annotations) {
|
|
354
|
+
if (!annotations || annotations.length === 0) {
|
|
355
|
+
return '';
|
|
356
|
+
}
|
|
357
|
+
const lines = [];
|
|
358
|
+
const total = annotations.length;
|
|
359
|
+
// Header with count
|
|
360
|
+
const counts = this.countAnnotationsByStyle(annotations);
|
|
361
|
+
const countParts = [];
|
|
362
|
+
if (counts.ERROR > 0)
|
|
363
|
+
countParts.push(SEMANTIC_COLORS.error(`${counts.ERROR} error${counts.ERROR > 1 ? 's' : ''}`));
|
|
364
|
+
if (counts.WARNING > 0)
|
|
365
|
+
countParts.push(SEMANTIC_COLORS.warning(`${counts.WARNING} warning${counts.WARNING > 1 ? 's' : ''}`));
|
|
366
|
+
if (counts.INFO > 0)
|
|
367
|
+
countParts.push(SEMANTIC_COLORS.info(`${counts.INFO} info`));
|
|
368
|
+
if (counts.SUCCESS > 0)
|
|
369
|
+
countParts.push(SEMANTIC_COLORS.success(`${counts.SUCCESS} success`));
|
|
370
|
+
lines.push(`${getAnnotationIcon('DEFAULT')} ${SEMANTIC_COLORS.count(String(total))} annotation${total > 1 ? 's' : ''}: ${countParts.join(', ')}`);
|
|
371
|
+
// List each annotation with style and context
|
|
372
|
+
const grouped = this.groupAnnotationsByStyle(annotations);
|
|
373
|
+
const styleOrder = ['ERROR', 'WARNING', 'INFO', 'SUCCESS'];
|
|
374
|
+
for (const style of styleOrder) {
|
|
375
|
+
if (grouped[style]) {
|
|
376
|
+
for (const annotation of grouped[style]) {
|
|
377
|
+
const icon = this.getAnnotationIcon(style);
|
|
378
|
+
const context = annotation.node.context || 'default';
|
|
379
|
+
const styleColored = this.colorizeAnnotationStyle(style);
|
|
380
|
+
lines.push(` ${icon} ${styleColored}: ${context}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return lines.join('\n');
|
|
385
|
+
}
|
|
386
|
+
formatAnnotationDetails(annotations) {
|
|
387
|
+
const lines = [];
|
|
388
|
+
// Group annotations by style
|
|
389
|
+
const grouped = this.groupAnnotationsByStyle(annotations);
|
|
390
|
+
const styleOrder = ['ERROR', 'WARNING', 'INFO', 'SUCCESS'];
|
|
391
|
+
for (const style of styleOrder) {
|
|
392
|
+
if (grouped[style]) {
|
|
393
|
+
for (const annotation of grouped[style]) {
|
|
394
|
+
const icon = this.getAnnotationIcon(style);
|
|
395
|
+
const context = annotation.node.context || 'default';
|
|
396
|
+
const styleColored = this.colorizeAnnotationStyle(style);
|
|
397
|
+
// When showing annotation details, always show the body text
|
|
398
|
+
lines.push(`${icon} ${styleColored}: ${context}`);
|
|
399
|
+
const body = htmlToText(annotation.node.body?.html || '', {
|
|
400
|
+
wordwrap: 80,
|
|
401
|
+
preserveNewlines: true
|
|
402
|
+
});
|
|
403
|
+
lines.push(body.split('\n').map(l => ` ${l}`).join('\n'));
|
|
404
|
+
lines.push('');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return lines.join('\n').trim();
|
|
409
|
+
}
|
|
410
|
+
formatJobDetails(jobs, options) {
|
|
411
|
+
if (!jobs || jobs.length === 0) {
|
|
412
|
+
return 'No jobs found';
|
|
413
|
+
}
|
|
414
|
+
const lines = [];
|
|
415
|
+
const jobStats = this.getJobStats(jobs);
|
|
416
|
+
// Summary line
|
|
417
|
+
const parts = [];
|
|
418
|
+
if (jobStats.passed > 0)
|
|
419
|
+
parts.push(`${getStateIcon('PASSED')} ${jobStats.passed} passed`);
|
|
420
|
+
if (jobStats.failed > 0)
|
|
421
|
+
parts.push(`${getStateIcon('FAILED')} ${jobStats.failed} failed`);
|
|
422
|
+
if (jobStats.running > 0)
|
|
423
|
+
parts.push(`${getStateIcon('RUNNING')} ${jobStats.running} running`);
|
|
424
|
+
if (jobStats.blocked > 0)
|
|
425
|
+
parts.push(`${getStateIcon('BLOCKED')} ${jobStats.blocked} blocked`);
|
|
426
|
+
if (jobStats.skipped > 0)
|
|
427
|
+
parts.push(`${getStateIcon('SKIPPED')} ${jobStats.skipped} skipped`);
|
|
428
|
+
lines.push(`Jobs: ${parts.join(' ')}`);
|
|
429
|
+
lines.push('');
|
|
430
|
+
// Filter jobs based on options
|
|
431
|
+
let filteredJobs = jobs;
|
|
432
|
+
if (options?.failed) {
|
|
433
|
+
filteredJobs = this.getFailedJobs(jobs);
|
|
434
|
+
}
|
|
435
|
+
// Group jobs by state
|
|
436
|
+
const grouped = this.groupJobsByState(filteredJobs);
|
|
437
|
+
for (const [state, stateJobs] of Object.entries(grouped)) {
|
|
438
|
+
if (stateJobs.length === 0)
|
|
439
|
+
continue;
|
|
440
|
+
const icon = this.getJobStateIcon(state);
|
|
441
|
+
const stateColored = this.colorizeJobState(state);
|
|
442
|
+
lines.push(`${icon} ${stateColored} (${SEMANTIC_COLORS.count(String(stateJobs.length))}):`);
|
|
443
|
+
for (const job of stateJobs) {
|
|
444
|
+
const label = this.parseEmoji(job.node.label);
|
|
445
|
+
const duration = this.formatJobDuration(job.node);
|
|
446
|
+
// Basic job line
|
|
447
|
+
lines.push(` ${label} (${duration})`);
|
|
448
|
+
// Show additional details if --jobs or --full
|
|
449
|
+
if (options?.jobs || options?.full) {
|
|
450
|
+
// Timing details
|
|
451
|
+
if (job.node.startedAt) {
|
|
452
|
+
const startTime = new Date(job.node.startedAt).toLocaleTimeString();
|
|
453
|
+
const endTime = job.node.finishedAt
|
|
454
|
+
? new Date(job.node.finishedAt).toLocaleTimeString()
|
|
455
|
+
: 'still running';
|
|
456
|
+
lines.push(` ${SEMANTIC_COLORS.dim(`${getProgressIcon('TIMING')} ${startTime} → ${endTime}`)}`);
|
|
457
|
+
}
|
|
458
|
+
// Parallel group info
|
|
459
|
+
if (job.node.parallelGroupIndex !== undefined && job.node.parallelGroupTotal) {
|
|
460
|
+
lines.push(` ${SEMANTIC_COLORS.dim(`${getProgressIcon('PARALLEL')} Parallel: ${job.node.parallelGroupIndex + 1}/${job.node.parallelGroupTotal}`)}`);
|
|
461
|
+
}
|
|
462
|
+
// Retry info
|
|
463
|
+
if (job.node.retried) {
|
|
464
|
+
lines.push(` ${SEMANTIC_COLORS.warning(`${getProgressIcon('RETRY')} Retried`)}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
lines.push('');
|
|
469
|
+
}
|
|
470
|
+
return lines.join('\n').trim();
|
|
471
|
+
}
|
|
472
|
+
formatFailedJobsSummaryWithHints(failedJobs, options) {
|
|
473
|
+
const hints = [];
|
|
474
|
+
const summary = this.formatFailedJobsSummary(failedJobs, options, hints);
|
|
475
|
+
return { summary, hints };
|
|
476
|
+
}
|
|
477
|
+
formatFailedJobsSummary(failedJobs, options, hints) {
|
|
478
|
+
const lines = [];
|
|
479
|
+
// Group identical jobs by label
|
|
480
|
+
const jobGroups = this.groupJobsByLabel(failedJobs);
|
|
481
|
+
// Show all groups if --all-jobs, otherwise limit to 10
|
|
482
|
+
const displayGroups = options?.allJobs
|
|
483
|
+
? jobGroups
|
|
484
|
+
: jobGroups.slice(0, 10);
|
|
485
|
+
for (const group of displayGroups) {
|
|
486
|
+
const label = this.parseEmoji(group.label);
|
|
487
|
+
if (group.count === 1) {
|
|
488
|
+
const duration = this.formatJobDuration(group.jobs[0].node);
|
|
489
|
+
lines.push(` ${SEMANTIC_COLORS.error('Failed')}: ${label} - ran ${duration}`);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
// Multiple jobs with same label - show detailed breakdown
|
|
493
|
+
const statusParts = [];
|
|
494
|
+
if (group.stateCounts.failed > 0) {
|
|
495
|
+
statusParts.push(`${group.stateCounts.failed} failed`);
|
|
496
|
+
}
|
|
497
|
+
if (group.stateCounts.broken > 0) {
|
|
498
|
+
statusParts.push(`${group.stateCounts.broken} broken`);
|
|
499
|
+
}
|
|
500
|
+
if (group.stateCounts.notStarted > 0) {
|
|
501
|
+
statusParts.push(`${group.stateCounts.notStarted} not started`);
|
|
502
|
+
}
|
|
503
|
+
if (group.stateCounts.passed > 0) {
|
|
504
|
+
statusParts.push(`${group.stateCounts.passed} passed`);
|
|
505
|
+
}
|
|
506
|
+
if (group.stateCounts.other > 0) {
|
|
507
|
+
statusParts.push(`${group.stateCounts.other} other`);
|
|
508
|
+
}
|
|
509
|
+
const statusInfo = statusParts.join(', ') || 'various states';
|
|
510
|
+
// Show parallel info if it's a parallel job group
|
|
511
|
+
const parallelInfo = group.parallelTotal > 0 ? ` (${group.count}/${group.parallelTotal} parallel)` : ` (${SEMANTIC_COLORS.count(String(group.count))} jobs)`;
|
|
512
|
+
lines.push(` ${SEMANTIC_COLORS.error('Failed')}: ${label}${parallelInfo}: ${statusInfo}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Add summary if there are more job types and not showing all
|
|
516
|
+
if (!options?.allJobs) {
|
|
517
|
+
const remaining = jobGroups.length - displayGroups.length;
|
|
518
|
+
if (remaining > 0) {
|
|
519
|
+
lines.push(` ${SEMANTIC_COLORS.muted(`...and ${remaining} more job types`)}`);
|
|
520
|
+
// If hints array is provided, add hint there; otherwise format inline
|
|
521
|
+
if (hints) {
|
|
522
|
+
hints.push('Use --all-jobs to show all jobs');
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
lines.push('');
|
|
526
|
+
const tips = formatTips(['Use --all-jobs to show all jobs'], TipStyle.GROUPED);
|
|
527
|
+
lines.push(tips);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return lines.join('\n');
|
|
532
|
+
}
|
|
533
|
+
groupJobsByLabel(jobs) {
|
|
534
|
+
const groups = new Map();
|
|
535
|
+
for (const job of jobs) {
|
|
536
|
+
const fullLabel = job.node.label || 'Unnamed job';
|
|
537
|
+
// Strip parallel job index from label for grouping
|
|
538
|
+
// e.g., "deposit_and_filing_schedule_calculator rspec (1/22)" -> "deposit_and_filing_schedule_calculator rspec"
|
|
539
|
+
const baseLabel = fullLabel.replace(/\s*\(\d+\/\d+\)\s*$/, '').trim();
|
|
540
|
+
if (!groups.has(baseLabel)) {
|
|
541
|
+
groups.set(baseLabel, {
|
|
542
|
+
label: baseLabel,
|
|
543
|
+
count: 0,
|
|
544
|
+
jobs: [],
|
|
545
|
+
parallelTotal: 0,
|
|
546
|
+
stateCounts: {
|
|
547
|
+
failed: 0,
|
|
548
|
+
broken: 0,
|
|
549
|
+
notStarted: 0,
|
|
550
|
+
passed: 0,
|
|
551
|
+
other: 0
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
const group = groups.get(baseLabel);
|
|
556
|
+
group.count++;
|
|
557
|
+
group.jobs.push(job);
|
|
558
|
+
// Track the maximum parallel total for this job group
|
|
559
|
+
if (job.node.parallelGroupTotal && job.node.parallelGroupTotal > group.parallelTotal) {
|
|
560
|
+
group.parallelTotal = job.node.parallelGroupTotal;
|
|
561
|
+
}
|
|
562
|
+
// Count by state
|
|
563
|
+
const state = job.node.state?.toUpperCase();
|
|
564
|
+
// Use exit status as source of truth when available
|
|
565
|
+
// Note: exitStatus comes as a string from Buildkite API
|
|
566
|
+
if (job.node.exitStatus !== null && job.node.exitStatus !== undefined) {
|
|
567
|
+
const exitCode = parseInt(job.node.exitStatus, 10);
|
|
568
|
+
if (exitCode === 0) {
|
|
569
|
+
group.stateCounts.passed++;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
group.stateCounts.failed++;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
else if (!job.node.startedAt) {
|
|
576
|
+
group.stateCounts.notStarted++;
|
|
577
|
+
}
|
|
578
|
+
else if (state === 'FINISHED' || state === 'COMPLETED') {
|
|
579
|
+
// For finished jobs without exit status, check passed field
|
|
580
|
+
if (job.node.passed === true) {
|
|
581
|
+
group.stateCounts.passed++;
|
|
582
|
+
}
|
|
583
|
+
else if (job.node.passed === false) {
|
|
584
|
+
group.stateCounts.failed++;
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
group.stateCounts.other++;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else if (state === 'FAILED') {
|
|
591
|
+
group.stateCounts.failed++;
|
|
592
|
+
}
|
|
593
|
+
else if (state === 'BROKEN') {
|
|
594
|
+
group.stateCounts.broken++;
|
|
595
|
+
}
|
|
596
|
+
else if (state === 'PASSED' || job.node.passed === true) {
|
|
597
|
+
group.stateCounts.passed++;
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
group.stateCounts.other++;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// Convert to array and sort by count (most failures first)
|
|
604
|
+
return Array.from(groups.values())
|
|
605
|
+
.sort((a, b) => b.count - a.count);
|
|
606
|
+
}
|
|
607
|
+
formatDuration(build) {
|
|
608
|
+
if (!build.startedAt) {
|
|
609
|
+
return 'not started';
|
|
610
|
+
}
|
|
611
|
+
const start = new Date(build.startedAt);
|
|
612
|
+
const end = build.finishedAt ? new Date(build.finishedAt) : new Date();
|
|
613
|
+
const durationMs = end.getTime() - start.getTime();
|
|
614
|
+
const minutes = Math.floor(durationMs / 60000);
|
|
615
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
616
|
+
if (build.state === 'RUNNING') {
|
|
617
|
+
return `${minutes}m ${seconds}s elapsed`;
|
|
618
|
+
}
|
|
619
|
+
return `${minutes}m ${seconds}s`;
|
|
620
|
+
}
|
|
621
|
+
formatJobDuration(job) {
|
|
622
|
+
if (!job.startedAt) {
|
|
623
|
+
return 'not started';
|
|
624
|
+
}
|
|
625
|
+
const start = new Date(job.startedAt);
|
|
626
|
+
const end = job.finishedAt ? new Date(job.finishedAt) : new Date();
|
|
627
|
+
const durationMs = end.getTime() - start.getTime();
|
|
628
|
+
const minutes = Math.floor(durationMs / 60000);
|
|
629
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
630
|
+
return `${minutes}m ${seconds}s`;
|
|
631
|
+
}
|
|
632
|
+
formatAge(createdAt) {
|
|
633
|
+
return formatDistanceToNow(new Date(createdAt), { addSuffix: true });
|
|
634
|
+
}
|
|
635
|
+
colorizeJobState(state) {
|
|
636
|
+
switch (state.toLowerCase()) {
|
|
637
|
+
case 'failed':
|
|
638
|
+
return SEMANTIC_COLORS.error(state);
|
|
639
|
+
case 'passed':
|
|
640
|
+
return SEMANTIC_COLORS.success(state);
|
|
641
|
+
case 'running':
|
|
642
|
+
return SEMANTIC_COLORS.info(state);
|
|
643
|
+
case 'blocked':
|
|
644
|
+
return SEMANTIC_COLORS.warning(state);
|
|
645
|
+
case 'skipped':
|
|
646
|
+
case 'canceled':
|
|
647
|
+
return SEMANTIC_COLORS.muted(state);
|
|
648
|
+
default:
|
|
649
|
+
return state;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
getStatusIcon(state) {
|
|
653
|
+
return getStateIcon(state);
|
|
654
|
+
}
|
|
655
|
+
getJobStateIcon(state) {
|
|
656
|
+
return getStateIcon(state);
|
|
657
|
+
}
|
|
658
|
+
getAnnotationIcon(style) {
|
|
659
|
+
return getAnnotationIcon(style);
|
|
660
|
+
}
|
|
661
|
+
colorizeAnnotationStyle(style) {
|
|
662
|
+
switch (style.toUpperCase()) {
|
|
663
|
+
case 'ERROR':
|
|
664
|
+
return SEMANTIC_COLORS.error(style.toLowerCase());
|
|
665
|
+
case 'WARNING':
|
|
666
|
+
return SEMANTIC_COLORS.warning(style.toLowerCase());
|
|
667
|
+
case 'INFO':
|
|
668
|
+
return SEMANTIC_COLORS.info(style.toLowerCase());
|
|
669
|
+
case 'SUCCESS':
|
|
670
|
+
return SEMANTIC_COLORS.success(style.toLowerCase());
|
|
671
|
+
default:
|
|
672
|
+
return style.toLowerCase();
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
colorizeStatusIcon(icon, state) {
|
|
676
|
+
const upperState = state.toUpperCase();
|
|
677
|
+
const theme = BUILD_STATUS_THEME[upperState];
|
|
678
|
+
if (!theme) {
|
|
679
|
+
return SEMANTIC_COLORS.muted(icon);
|
|
680
|
+
}
|
|
681
|
+
return theme.color(icon);
|
|
682
|
+
}
|
|
683
|
+
getJobStats(jobs) {
|
|
684
|
+
const stats = {
|
|
685
|
+
total: jobs?.length || 0,
|
|
686
|
+
passed: 0,
|
|
687
|
+
failed: 0,
|
|
688
|
+
running: 0,
|
|
689
|
+
blocked: 0,
|
|
690
|
+
skipped: 0,
|
|
691
|
+
queued: 0,
|
|
692
|
+
completed: 0
|
|
693
|
+
};
|
|
694
|
+
if (!jobs)
|
|
695
|
+
return stats;
|
|
696
|
+
for (const job of jobs) {
|
|
697
|
+
const state = job.node.state?.toUpperCase() || '';
|
|
698
|
+
// If we have an exit status, use that as the source of truth
|
|
699
|
+
// Note: exitStatus comes as a string from Buildkite API
|
|
700
|
+
if (job.node.exitStatus !== null && job.node.exitStatus !== undefined) {
|
|
701
|
+
const exitCode = parseInt(job.node.exitStatus, 10);
|
|
702
|
+
if (exitCode === 0) {
|
|
703
|
+
stats.passed++;
|
|
704
|
+
stats.completed++;
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
stats.failed++;
|
|
708
|
+
stats.completed++;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
else if (state === 'RUNNING') {
|
|
712
|
+
stats.running++;
|
|
713
|
+
}
|
|
714
|
+
else if (state === 'BLOCKED') {
|
|
715
|
+
stats.blocked++;
|
|
716
|
+
}
|
|
717
|
+
else if (state === 'SKIPPED' || state === 'CANCELED') {
|
|
718
|
+
stats.skipped++;
|
|
719
|
+
stats.completed++;
|
|
720
|
+
}
|
|
721
|
+
else if (state === 'SCHEDULED' || state === 'ASSIGNED') {
|
|
722
|
+
stats.queued++;
|
|
723
|
+
}
|
|
724
|
+
else if (state === 'FINISHED' || state === 'COMPLETED') {
|
|
725
|
+
// For finished jobs without exit status, check passed field
|
|
726
|
+
if (job.node.passed === true) {
|
|
727
|
+
stats.passed++;
|
|
728
|
+
stats.completed++;
|
|
729
|
+
}
|
|
730
|
+
else if (job.node.passed === false) {
|
|
731
|
+
stats.failed++;
|
|
732
|
+
stats.completed++;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else if (state === 'PASSED' || job.node.passed === true) {
|
|
736
|
+
stats.passed++;
|
|
737
|
+
stats.completed++;
|
|
738
|
+
}
|
|
739
|
+
else if (state === 'FAILED' || state === 'BROKEN' || job.node.passed === false) {
|
|
740
|
+
stats.failed++;
|
|
741
|
+
stats.completed++;
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
return stats;
|
|
745
|
+
}
|
|
746
|
+
getFailedJobs(jobs) {
|
|
747
|
+
if (!jobs)
|
|
748
|
+
return [];
|
|
749
|
+
return jobs.filter(job => {
|
|
750
|
+
const state = job.node.state?.toUpperCase();
|
|
751
|
+
// If we have an exit status, use that as the source of truth
|
|
752
|
+
// Note: exitStatus comes as a string from Buildkite API
|
|
753
|
+
if (job.node.exitStatus !== null && job.node.exitStatus !== undefined) {
|
|
754
|
+
const exitCode = parseInt(job.node.exitStatus, 10);
|
|
755
|
+
return exitCode !== 0;
|
|
756
|
+
}
|
|
757
|
+
// Otherwise fall back to state
|
|
758
|
+
return state === 'FAILED' || state === 'BROKEN' || job.node.passed === false;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
getRunningJobs(jobs) {
|
|
762
|
+
if (!jobs)
|
|
763
|
+
return [];
|
|
764
|
+
return jobs.filter(job => job.node.state?.toLowerCase() === 'running');
|
|
765
|
+
}
|
|
766
|
+
getBlockedJobs(jobs) {
|
|
767
|
+
if (!jobs)
|
|
768
|
+
return [];
|
|
769
|
+
return jobs.filter(job => job.node.state?.toLowerCase() === 'blocked');
|
|
770
|
+
}
|
|
771
|
+
groupJobsByState(jobs) {
|
|
772
|
+
const grouped = {
|
|
773
|
+
'Failed': [],
|
|
774
|
+
'Passed': [],
|
|
775
|
+
'Running': [],
|
|
776
|
+
'Blocked': [],
|
|
777
|
+
'Skipped': []
|
|
778
|
+
};
|
|
779
|
+
if (!jobs)
|
|
780
|
+
return grouped;
|
|
781
|
+
for (const job of jobs) {
|
|
782
|
+
const state = job.node.state?.toUpperCase() || '';
|
|
783
|
+
// If we have an exit status, use that as the source of truth
|
|
784
|
+
// Note: exitStatus comes as a string from Buildkite API
|
|
785
|
+
if (job.node.exitStatus !== null && job.node.exitStatus !== undefined) {
|
|
786
|
+
const exitCode = parseInt(job.node.exitStatus, 10);
|
|
787
|
+
if (exitCode === 0) {
|
|
788
|
+
grouped['Passed'].push(job);
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
grouped['Failed'].push(job);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
else if (state === 'RUNNING') {
|
|
795
|
+
grouped['Running'].push(job);
|
|
796
|
+
}
|
|
797
|
+
else if (state === 'BLOCKED') {
|
|
798
|
+
grouped['Blocked'].push(job);
|
|
799
|
+
}
|
|
800
|
+
else if (state === 'SKIPPED' || state === 'CANCELED') {
|
|
801
|
+
grouped['Skipped'].push(job);
|
|
802
|
+
}
|
|
803
|
+
else if (state === 'FINISHED' || state === 'COMPLETED') {
|
|
804
|
+
// For finished jobs without exit status, check passed field
|
|
805
|
+
if (job.node.passed === true) {
|
|
806
|
+
grouped['Passed'].push(job);
|
|
807
|
+
}
|
|
808
|
+
else if (job.node.passed === false) {
|
|
809
|
+
grouped['Failed'].push(job);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
else if (state === 'PASSED' || job.node.passed === true) {
|
|
813
|
+
grouped['Passed'].push(job);
|
|
814
|
+
}
|
|
815
|
+
else if (state === 'FAILED' || state === 'BROKEN' || job.node.passed === false) {
|
|
816
|
+
grouped['Failed'].push(job);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return grouped;
|
|
820
|
+
}
|
|
821
|
+
countAnnotationsByStyle(annotations) {
|
|
822
|
+
const counts = {
|
|
823
|
+
ERROR: 0,
|
|
824
|
+
WARNING: 0,
|
|
825
|
+
INFO: 0,
|
|
826
|
+
SUCCESS: 0
|
|
827
|
+
};
|
|
828
|
+
for (const annotation of annotations) {
|
|
829
|
+
const style = annotation.node.style?.toUpperCase() || 'INFO';
|
|
830
|
+
if (style in counts) {
|
|
831
|
+
counts[style]++;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
return counts;
|
|
835
|
+
}
|
|
836
|
+
groupAnnotationsByStyle(annotations) {
|
|
837
|
+
const grouped = {};
|
|
838
|
+
for (const annotation of annotations) {
|
|
839
|
+
const style = annotation.node.style?.toUpperCase() || 'INFO';
|
|
840
|
+
if (!grouped[style]) {
|
|
841
|
+
grouped[style] = [];
|
|
842
|
+
}
|
|
843
|
+
grouped[style].push(annotation);
|
|
844
|
+
}
|
|
845
|
+
return grouped;
|
|
846
|
+
}
|
|
847
|
+
formatError(action, error) {
|
|
848
|
+
return formatError(action, error);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
//# sourceMappingURL=PlainTextFormatter.js.map
|