bktide 1.0.1755267617 → 1.0.1755559112
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/README.md +107 -1
- package/WORKFLOW_README.md +1 -1
- package/completions/bktide-dynamic.fish +171 -0
- package/completions/bktide.bash +124 -0
- package/completions/bktide.fish +107 -0
- package/completions/bktide.zsh +139 -0
- package/dist/commands/BaseCommand.js +7 -7
- package/dist/commands/BaseCommand.js.map +1 -1
- package/dist/commands/GenerateCompletions.js +238 -0
- package/dist/commands/GenerateCompletions.js.map +1 -0
- package/dist/commands/ListAnnotations.js +7 -0
- package/dist/commands/ListAnnotations.js.map +1 -1
- package/dist/commands/ListBuilds.js +67 -3
- package/dist/commands/ListBuilds.js.map +1 -1
- package/dist/commands/ListOrganizations.js +6 -0
- package/dist/commands/ListOrganizations.js.map +1 -1
- package/dist/commands/ListPipelines.js +87 -12
- package/dist/commands/ListPipelines.js.map +1 -1
- package/dist/commands/ManageToken.js +32 -9
- package/dist/commands/ManageToken.js.map +1 -1
- package/dist/commands/ShowBuild.js +88 -0
- package/dist/commands/ShowBuild.js.map +1 -0
- package/dist/commands/ShowViewer.js +7 -1
- package/dist/commands/ShowViewer.js.map +1 -1
- package/dist/commands/index.js +2 -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/annotations/PlainTextFormatter.js +37 -9
- package/dist/formatters/annotations/PlainTextFormatter.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 +680 -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 +82 -60
- package/dist/formatters/builds/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/errors/AlfredFormatter.js +20 -0
- package/dist/formatters/errors/AlfredFormatter.js.map +1 -1
- package/dist/formatters/errors/PlainTextFormatter.js +121 -23
- package/dist/formatters/errors/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/organizations/PlainTextFormatter.js +37 -6
- package/dist/formatters/organizations/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/pipelines/AlfredFormatter.js.map +1 -1
- package/dist/formatters/pipelines/Formatter.js.map +1 -1
- package/dist/formatters/pipelines/JsonFormatter.js.map +1 -1
- package/dist/formatters/pipelines/PlainTextFormatter.js +165 -19
- package/dist/formatters/pipelines/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/token/AlfredFormatter.js +15 -2
- package/dist/formatters/token/AlfredFormatter.js.map +1 -1
- package/dist/formatters/token/PlainTextFormatter.js +56 -18
- package/dist/formatters/token/PlainTextFormatter.js.map +1 -1
- package/dist/formatters/viewer/PlainTextFormatter.js +8 -7
- package/dist/formatters/viewer/PlainTextFormatter.js.map +1 -1
- package/dist/graphql/queries.js +181 -0
- package/dist/graphql/queries.js.map +1 -1
- package/dist/index.js +67 -6
- package/dist/index.js.map +1 -1
- package/dist/services/BuildkiteClient.js +61 -1
- package/dist/services/BuildkiteClient.js.map +1 -1
- package/dist/services/CredentialManager.js +80 -10
- package/dist/services/CredentialManager.js.map +1 -1
- package/dist/ui/help.js +69 -0
- package/dist/ui/help.js.map +1 -0
- package/dist/ui/progress.js +356 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/reporter.js +111 -0
- package/dist/ui/reporter.js.map +1 -0
- package/dist/ui/responsive-table.js +183 -0
- package/dist/ui/responsive-table.js.map +1 -0
- package/dist/ui/spinner.js +20 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/symbols.js +46 -0
- package/dist/ui/symbols.js.map +1 -0
- package/dist/ui/table.js +32 -0
- package/dist/ui/table.js.map +1 -0
- package/dist/ui/theme.js +280 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/width.js +111 -0
- package/dist/ui/width.js.map +1 -0
- package/dist/utils/alfred.js +6 -0
- package/dist/utils/alfred.js.map +1 -0
- package/dist/utils/cli-error-handler.js +35 -20
- package/dist/utils/cli-error-handler.js.map +1 -1
- package/dist/utils/pagination.js +92 -0
- package/dist/utils/pagination.js.map +1 -0
- package/info.plist +51 -218
- package/package.json +24 -5
|
@@ -0,0 +1,680 @@
|
|
|
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 } 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 status = this.getStatusIcon(build.state);
|
|
131
|
+
const duration = this.formatDuration(build);
|
|
132
|
+
const age = this.formatAge(build.createdAt);
|
|
133
|
+
return `${status} #${build.number} ${build.state.toLowerCase()} • ${duration} • ${build.branch} • ${age}`;
|
|
134
|
+
}
|
|
135
|
+
formatPassedBuild(build, options) {
|
|
136
|
+
const lines = [];
|
|
137
|
+
// Header line
|
|
138
|
+
lines.push(this.formatHeader(build));
|
|
139
|
+
lines.push(this.formatCommitInfo(build));
|
|
140
|
+
// Show annotations summary if present
|
|
141
|
+
if (build.annotations?.edges?.length > 0) {
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push(this.formatAnnotationSummary(build.annotations.edges));
|
|
144
|
+
if (!options?.annotations) {
|
|
145
|
+
lines.push(SEMANTIC_COLORS.dim(`→ bin/bktide build ${build.number} --annotations # view annotations`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Show annotations detail if requested
|
|
149
|
+
if (options?.annotations) {
|
|
150
|
+
lines.push('');
|
|
151
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges, options));
|
|
152
|
+
}
|
|
153
|
+
return lines.join('\n');
|
|
154
|
+
}
|
|
155
|
+
formatFailedBuild(build, options) {
|
|
156
|
+
const lines = [];
|
|
157
|
+
// Header line
|
|
158
|
+
lines.push(this.formatHeader(build));
|
|
159
|
+
lines.push(this.formatCommitInfo(build));
|
|
160
|
+
lines.push('');
|
|
161
|
+
// Failed jobs summary
|
|
162
|
+
const failedJobs = this.getFailedJobs(build.jobs?.edges);
|
|
163
|
+
if (failedJobs.length > 0) {
|
|
164
|
+
lines.push(this.formatFailedJobsSummary(failedJobs));
|
|
165
|
+
}
|
|
166
|
+
// Annotation summary
|
|
167
|
+
if (build.annotations?.edges?.length > 0) {
|
|
168
|
+
lines.push(this.formatAnnotationSummary(build.annotations.edges));
|
|
169
|
+
}
|
|
170
|
+
// Show detailed job info if requested
|
|
171
|
+
if (options?.jobs || options?.failed) {
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
174
|
+
}
|
|
175
|
+
// Show annotations detail if requested
|
|
176
|
+
if (options?.annotations) {
|
|
177
|
+
lines.push('');
|
|
178
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges, options));
|
|
179
|
+
}
|
|
180
|
+
// Hints for more info (no Tips label)
|
|
181
|
+
if (!options?.failed && failedJobs.length > 0) {
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push(SEMANTIC_COLORS.dim(`→ bin/bktide build ${build.number} --failed # show failure details`));
|
|
184
|
+
}
|
|
185
|
+
if (!options?.annotations && build.annotations?.edges?.length > 0) {
|
|
186
|
+
lines.push(SEMANTIC_COLORS.dim(`→ bin/bktide build ${build.number} --annotations # view annotations`));
|
|
187
|
+
}
|
|
188
|
+
return lines.join('\n');
|
|
189
|
+
}
|
|
190
|
+
formatRunningBuild(build, options) {
|
|
191
|
+
const lines = [];
|
|
192
|
+
// Header line
|
|
193
|
+
lines.push(this.formatHeader(build));
|
|
194
|
+
lines.push(this.formatCommitInfo(build));
|
|
195
|
+
lines.push('');
|
|
196
|
+
// Progress information
|
|
197
|
+
const jobStats = this.getJobStats(build.jobs?.edges);
|
|
198
|
+
lines.push(`Progress: ${SEMANTIC_COLORS.count(String(jobStats.completed))}/${jobStats.total} complete, ${SEMANTIC_COLORS.info(String(jobStats.running))} running, ${jobStats.queued} queued`);
|
|
199
|
+
// Show running jobs
|
|
200
|
+
const runningJobs = this.getRunningJobs(build.jobs?.edges);
|
|
201
|
+
if (runningJobs.length > 0) {
|
|
202
|
+
const labels = runningJobs.map(j => this.parseEmoji(j.node.label)).join(', ');
|
|
203
|
+
lines.push(`${SEMANTIC_COLORS.info('Running')}: ${labels}`);
|
|
204
|
+
}
|
|
205
|
+
// Show job details if requested
|
|
206
|
+
if (options?.jobs) {
|
|
207
|
+
lines.push('');
|
|
208
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
209
|
+
}
|
|
210
|
+
return lines.join('\n');
|
|
211
|
+
}
|
|
212
|
+
formatBlockedBuild(build, options) {
|
|
213
|
+
const lines = [];
|
|
214
|
+
// Header line
|
|
215
|
+
lines.push(this.formatHeader(build));
|
|
216
|
+
lines.push(this.formatCommitInfo(build));
|
|
217
|
+
lines.push('');
|
|
218
|
+
// Blocked information
|
|
219
|
+
const blockedJobs = this.getBlockedJobs(build.jobs?.edges);
|
|
220
|
+
if (blockedJobs.length > 0) {
|
|
221
|
+
lines.push(`🚫 Blocked: "${blockedJobs[0].node.label}" (manual unblock required)`);
|
|
222
|
+
}
|
|
223
|
+
// Show jobs summary
|
|
224
|
+
const jobStats = this.getJobStats(build.jobs?.edges);
|
|
225
|
+
if (jobStats.completed > 0) {
|
|
226
|
+
lines.push(`✅ ${jobStats.completed} jobs passed before block`);
|
|
227
|
+
}
|
|
228
|
+
// Show job details if requested
|
|
229
|
+
if (options?.jobs) {
|
|
230
|
+
lines.push('');
|
|
231
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
232
|
+
}
|
|
233
|
+
return lines.join('\n');
|
|
234
|
+
}
|
|
235
|
+
formatCanceledBuild(build, options) {
|
|
236
|
+
const lines = [];
|
|
237
|
+
// Header line
|
|
238
|
+
lines.push(this.formatHeader(build));
|
|
239
|
+
lines.push(this.formatCommitInfo(build));
|
|
240
|
+
lines.push('');
|
|
241
|
+
// Canceled information
|
|
242
|
+
if (build.createdBy) {
|
|
243
|
+
const creator = build.createdBy.name || build.createdBy.email;
|
|
244
|
+
lines.push(`Canceled by: ${creator}`);
|
|
245
|
+
}
|
|
246
|
+
// Show jobs summary
|
|
247
|
+
const jobStats = this.getJobStats(build.jobs?.edges);
|
|
248
|
+
lines.push(`Completed: ${jobStats.completed}/${jobStats.total} jobs before cancellation`);
|
|
249
|
+
// Show job details if requested
|
|
250
|
+
if (options?.jobs) {
|
|
251
|
+
lines.push('');
|
|
252
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, options));
|
|
253
|
+
}
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
formatDefaultBuild(build, options) {
|
|
257
|
+
return this.formatPassedBuild(build, options);
|
|
258
|
+
}
|
|
259
|
+
formatFullDetails(build, options) {
|
|
260
|
+
const lines = [];
|
|
261
|
+
// Full header information
|
|
262
|
+
lines.push(this.formatHeader(build));
|
|
263
|
+
lines.push(this.formatCommitInfo(build));
|
|
264
|
+
lines.push('');
|
|
265
|
+
// Build metadata
|
|
266
|
+
lines.push('Build Details:');
|
|
267
|
+
lines.push(` URL: ${build.url}`);
|
|
268
|
+
lines.push(` Organization: ${build.organization?.name || 'Unknown'}`);
|
|
269
|
+
lines.push(` Pipeline: ${build.pipeline?.name || 'Unknown'}`);
|
|
270
|
+
if (build.pullRequest) {
|
|
271
|
+
lines.push(` Pull Request: #${build.pullRequest.number}`);
|
|
272
|
+
}
|
|
273
|
+
if (build.triggeredFrom) {
|
|
274
|
+
lines.push(` Triggered from: ${build.triggeredFrom.pipeline?.name} #${build.triggeredFrom.number}`);
|
|
275
|
+
}
|
|
276
|
+
lines.push('');
|
|
277
|
+
// Jobs section
|
|
278
|
+
lines.push('Jobs:');
|
|
279
|
+
lines.push(this.formatJobDetails(build.jobs?.edges, { ...options, full: true }));
|
|
280
|
+
// Annotations section
|
|
281
|
+
if (build.annotations?.edges?.length > 0) {
|
|
282
|
+
lines.push('');
|
|
283
|
+
lines.push('Annotations:');
|
|
284
|
+
lines.push(this.formatAnnotationDetails(build.annotations.edges, { ...options, annotationsFull: true }));
|
|
285
|
+
}
|
|
286
|
+
return lines.join('\n');
|
|
287
|
+
}
|
|
288
|
+
formatHeader(build) {
|
|
289
|
+
const status = this.getStatusIcon(build.state);
|
|
290
|
+
const stateFormatted = formatBuildStatus(build.state, { useSymbol: false });
|
|
291
|
+
const duration = this.formatDuration(build);
|
|
292
|
+
const age = this.formatAge(build.createdAt);
|
|
293
|
+
const branch = SEMANTIC_COLORS.identifier(build.branch);
|
|
294
|
+
return `${status} ${SEMANTIC_COLORS.label(`#${build.number}`)} ${stateFormatted} • ${duration} • ${branch} • ${age}`;
|
|
295
|
+
}
|
|
296
|
+
formatCommitInfo(build) {
|
|
297
|
+
const shortSha = build.commit ? build.commit.substring(0, 7) : 'unknown';
|
|
298
|
+
const message = build.message || 'No commit message';
|
|
299
|
+
const truncatedMessage = message.length > 60 ? message.substring(0, 57) + '...' : message;
|
|
300
|
+
return ` "${truncatedMessage}" (${shortSha})`;
|
|
301
|
+
}
|
|
302
|
+
formatAnnotationSummary(annotations) {
|
|
303
|
+
const counts = this.countAnnotationsByStyle(annotations);
|
|
304
|
+
const parts = [];
|
|
305
|
+
if (counts.ERROR > 0)
|
|
306
|
+
parts.push(SEMANTIC_COLORS.error(`${counts.ERROR} error${counts.ERROR > 1 ? 's' : ''}`));
|
|
307
|
+
if (counts.WARNING > 0)
|
|
308
|
+
parts.push(SEMANTIC_COLORS.warning(`${counts.WARNING} warning${counts.WARNING > 1 ? 's' : ''}`));
|
|
309
|
+
if (counts.INFO > 0)
|
|
310
|
+
parts.push(SEMANTIC_COLORS.info(`${counts.INFO} info`));
|
|
311
|
+
if (counts.SUCCESS > 0)
|
|
312
|
+
parts.push(SEMANTIC_COLORS.success(`${counts.SUCCESS} success`));
|
|
313
|
+
const total = annotations.length;
|
|
314
|
+
return `📝 ${SEMANTIC_COLORS.count(String(total))} annotation${total > 1 ? 's' : ''}: ${parts.join(', ')}`;
|
|
315
|
+
}
|
|
316
|
+
formatAnnotationDetails(annotations, options) {
|
|
317
|
+
const lines = [];
|
|
318
|
+
// Group annotations by style
|
|
319
|
+
const grouped = this.groupAnnotationsByStyle(annotations);
|
|
320
|
+
for (const [style, items] of Object.entries(grouped)) {
|
|
321
|
+
for (const annotation of items) {
|
|
322
|
+
const icon = this.getAnnotationIcon(style);
|
|
323
|
+
const context = annotation.node.context || 'default';
|
|
324
|
+
if (options?.annotationsFull) {
|
|
325
|
+
// Full content
|
|
326
|
+
lines.push(`${icon} ${style} [${context}]:`);
|
|
327
|
+
const body = htmlToText(annotation.node.body?.html || '', {
|
|
328
|
+
wordwrap: 80,
|
|
329
|
+
preserveNewlines: true
|
|
330
|
+
});
|
|
331
|
+
lines.push(body.split('\n').map(l => ` ${l}`).join('\n'));
|
|
332
|
+
lines.push('');
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// Summary only
|
|
336
|
+
lines.push(`${icon} ${style} [${context}]`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return lines.join('\n');
|
|
341
|
+
}
|
|
342
|
+
formatJobDetails(jobs, options) {
|
|
343
|
+
if (!jobs || jobs.length === 0) {
|
|
344
|
+
return 'No jobs found';
|
|
345
|
+
}
|
|
346
|
+
const lines = [];
|
|
347
|
+
const jobStats = this.getJobStats(jobs);
|
|
348
|
+
// Summary line
|
|
349
|
+
const parts = [];
|
|
350
|
+
if (jobStats.passed > 0)
|
|
351
|
+
parts.push(`✅ ${jobStats.passed} passed`);
|
|
352
|
+
if (jobStats.failed > 0)
|
|
353
|
+
parts.push(`❌ ${jobStats.failed} failed`);
|
|
354
|
+
if (jobStats.running > 0)
|
|
355
|
+
parts.push(`🔄 ${jobStats.running} running`);
|
|
356
|
+
if (jobStats.blocked > 0)
|
|
357
|
+
parts.push(`⏸️ ${jobStats.blocked} blocked`);
|
|
358
|
+
if (jobStats.skipped > 0)
|
|
359
|
+
parts.push(`⏭️ ${jobStats.skipped} skipped`);
|
|
360
|
+
lines.push(`Jobs: ${parts.join(' ')}`);
|
|
361
|
+
lines.push('');
|
|
362
|
+
// Filter jobs based on options
|
|
363
|
+
let filteredJobs = jobs;
|
|
364
|
+
if (options?.failed) {
|
|
365
|
+
filteredJobs = this.getFailedJobs(jobs);
|
|
366
|
+
}
|
|
367
|
+
// Group jobs by state
|
|
368
|
+
const grouped = this.groupJobsByState(filteredJobs);
|
|
369
|
+
for (const [state, stateJobs] of Object.entries(grouped)) {
|
|
370
|
+
if (stateJobs.length === 0)
|
|
371
|
+
continue;
|
|
372
|
+
const icon = this.getJobStateIcon(state);
|
|
373
|
+
const stateColored = this.colorizeJobState(state);
|
|
374
|
+
lines.push(`${icon} ${stateColored} (${SEMANTIC_COLORS.count(String(stateJobs.length))}):`);
|
|
375
|
+
for (const job of stateJobs) {
|
|
376
|
+
const label = this.parseEmoji(job.node.label);
|
|
377
|
+
const duration = this.formatJobDuration(job.node);
|
|
378
|
+
const exitCode = job.node.exitStatus ? `, exit ${job.node.exitStatus}` : '';
|
|
379
|
+
lines.push(` ${label} (${duration}${exitCode})`);
|
|
380
|
+
if (options?.full && job.node.agent) {
|
|
381
|
+
lines.push(` ${SEMANTIC_COLORS.dim(`Agent: ${job.node.agent.name || job.node.agent.hostname}`)}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
lines.push('');
|
|
385
|
+
}
|
|
386
|
+
return lines.join('\n').trim();
|
|
387
|
+
}
|
|
388
|
+
formatFailedJobsSummary(failedJobs) {
|
|
389
|
+
const lines = [];
|
|
390
|
+
// Group identical jobs by label
|
|
391
|
+
const jobGroups = this.groupJobsByLabel(failedJobs);
|
|
392
|
+
// Show first 10 unique job types
|
|
393
|
+
const displayGroups = jobGroups.slice(0, 10);
|
|
394
|
+
for (const group of displayGroups) {
|
|
395
|
+
const label = this.parseEmoji(group.label);
|
|
396
|
+
if (group.count === 1) {
|
|
397
|
+
const duration = this.formatJobDuration(group.jobs[0].node);
|
|
398
|
+
lines.push(` ${SEMANTIC_COLORS.error('Failed')}: ${label} - ran ${duration}`);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// Multiple jobs with same label - show detailed breakdown
|
|
402
|
+
const statusParts = [];
|
|
403
|
+
if (group.stateCounts.failed > 0) {
|
|
404
|
+
statusParts.push(`${group.stateCounts.failed} failed`);
|
|
405
|
+
}
|
|
406
|
+
if (group.stateCounts.broken > 0) {
|
|
407
|
+
statusParts.push(`${group.stateCounts.broken} broken`);
|
|
408
|
+
}
|
|
409
|
+
if (group.stateCounts.notStarted > 0) {
|
|
410
|
+
statusParts.push(`${group.stateCounts.notStarted} not started`);
|
|
411
|
+
}
|
|
412
|
+
if (group.stateCounts.passed > 0) {
|
|
413
|
+
statusParts.push(`${group.stateCounts.passed} passed`);
|
|
414
|
+
}
|
|
415
|
+
if (group.stateCounts.other > 0) {
|
|
416
|
+
statusParts.push(`${group.stateCounts.other} other`);
|
|
417
|
+
}
|
|
418
|
+
// Add exit codes if available
|
|
419
|
+
if (group.exitCodes.length > 0) {
|
|
420
|
+
const exitCodeStr = group.exitCodes.length === 1
|
|
421
|
+
? `exit ${group.exitCodes[0]}`
|
|
422
|
+
: `exits: ${group.exitCodes.join(', ')}`;
|
|
423
|
+
statusParts.push(exitCodeStr);
|
|
424
|
+
}
|
|
425
|
+
const statusInfo = statusParts.join(', ') || 'various states';
|
|
426
|
+
lines.push(` ${SEMANTIC_COLORS.error('Failed')}: ${label} (${SEMANTIC_COLORS.count(String(group.count))} jobs: ${statusInfo})`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Add summary if there are more job types
|
|
430
|
+
const remaining = jobGroups.length - displayGroups.length;
|
|
431
|
+
if (remaining > 0) {
|
|
432
|
+
lines.push(` ${SEMANTIC_COLORS.muted(`...and ${remaining} more job types`)}`);
|
|
433
|
+
}
|
|
434
|
+
return lines.join('\n');
|
|
435
|
+
}
|
|
436
|
+
groupJobsByLabel(jobs) {
|
|
437
|
+
const groups = new Map();
|
|
438
|
+
for (const job of jobs) {
|
|
439
|
+
const label = job.node.label || 'Unnamed job';
|
|
440
|
+
if (!groups.has(label)) {
|
|
441
|
+
groups.set(label, {
|
|
442
|
+
label,
|
|
443
|
+
count: 0,
|
|
444
|
+
jobs: [],
|
|
445
|
+
exitCodes: new Set(),
|
|
446
|
+
stateCounts: {
|
|
447
|
+
failed: 0,
|
|
448
|
+
broken: 0,
|
|
449
|
+
notStarted: 0,
|
|
450
|
+
passed: 0,
|
|
451
|
+
other: 0
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
const group = groups.get(label);
|
|
456
|
+
group.count++;
|
|
457
|
+
group.jobs.push(job);
|
|
458
|
+
// Track exit codes
|
|
459
|
+
if (job.node.exitStatus !== null && job.node.exitStatus !== undefined) {
|
|
460
|
+
group.exitCodes.add(job.node.exitStatus);
|
|
461
|
+
}
|
|
462
|
+
// Count by state
|
|
463
|
+
const state = job.node.state?.toUpperCase();
|
|
464
|
+
if (!job.node.startedAt) {
|
|
465
|
+
group.stateCounts.notStarted++;
|
|
466
|
+
}
|
|
467
|
+
else if (state === 'FAILED') {
|
|
468
|
+
group.stateCounts.failed++;
|
|
469
|
+
}
|
|
470
|
+
else if (state === 'BROKEN') {
|
|
471
|
+
group.stateCounts.broken++;
|
|
472
|
+
}
|
|
473
|
+
else if (state === 'PASSED' || job.node.passed === true) {
|
|
474
|
+
group.stateCounts.passed++;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
group.stateCounts.other++;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// Convert to array and sort by count (most failures first)
|
|
481
|
+
return Array.from(groups.values())
|
|
482
|
+
.map(g => ({ ...g, exitCodes: Array.from(g.exitCodes) }))
|
|
483
|
+
.sort((a, b) => b.count - a.count);
|
|
484
|
+
}
|
|
485
|
+
formatDuration(build) {
|
|
486
|
+
if (!build.startedAt) {
|
|
487
|
+
return 'not started';
|
|
488
|
+
}
|
|
489
|
+
const start = new Date(build.startedAt);
|
|
490
|
+
const end = build.finishedAt ? new Date(build.finishedAt) : new Date();
|
|
491
|
+
const durationMs = end.getTime() - start.getTime();
|
|
492
|
+
const minutes = Math.floor(durationMs / 60000);
|
|
493
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
494
|
+
if (build.state === 'RUNNING') {
|
|
495
|
+
return `${minutes}m ${seconds}s elapsed`;
|
|
496
|
+
}
|
|
497
|
+
return `${minutes}m ${seconds}s`;
|
|
498
|
+
}
|
|
499
|
+
formatJobDuration(job) {
|
|
500
|
+
if (!job.startedAt) {
|
|
501
|
+
return 'not started';
|
|
502
|
+
}
|
|
503
|
+
const start = new Date(job.startedAt);
|
|
504
|
+
const end = job.finishedAt ? new Date(job.finishedAt) : new Date();
|
|
505
|
+
const durationMs = end.getTime() - start.getTime();
|
|
506
|
+
const minutes = Math.floor(durationMs / 60000);
|
|
507
|
+
const seconds = Math.floor((durationMs % 60000) / 1000);
|
|
508
|
+
return `${minutes}m ${seconds}s`;
|
|
509
|
+
}
|
|
510
|
+
formatAge(createdAt) {
|
|
511
|
+
return formatDistanceToNow(new Date(createdAt), { addSuffix: true });
|
|
512
|
+
}
|
|
513
|
+
colorizeJobState(state) {
|
|
514
|
+
switch (state.toLowerCase()) {
|
|
515
|
+
case 'failed':
|
|
516
|
+
return SEMANTIC_COLORS.error(state);
|
|
517
|
+
case 'passed':
|
|
518
|
+
return SEMANTIC_COLORS.success(state);
|
|
519
|
+
case 'running':
|
|
520
|
+
return SEMANTIC_COLORS.info(state);
|
|
521
|
+
case 'blocked':
|
|
522
|
+
return SEMANTIC_COLORS.warning(state);
|
|
523
|
+
case 'skipped':
|
|
524
|
+
case 'canceled':
|
|
525
|
+
return SEMANTIC_COLORS.muted(state);
|
|
526
|
+
default:
|
|
527
|
+
return state;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
getStatusIcon(state) {
|
|
531
|
+
const icons = {
|
|
532
|
+
'PASSED': '✅',
|
|
533
|
+
'FAILED': '❌',
|
|
534
|
+
'RUNNING': '🔄',
|
|
535
|
+
'BLOCKED': '⏸️',
|
|
536
|
+
'CANCELED': '🚫',
|
|
537
|
+
'SCHEDULED': '📅',
|
|
538
|
+
'SKIPPED': '⏭️'
|
|
539
|
+
};
|
|
540
|
+
return icons[state] || '❓';
|
|
541
|
+
}
|
|
542
|
+
getJobStateIcon(state) {
|
|
543
|
+
const icons = {
|
|
544
|
+
'passed': '✅',
|
|
545
|
+
'failed': '❌',
|
|
546
|
+
'running': '🔄',
|
|
547
|
+
'blocked': '⏸️',
|
|
548
|
+
'canceled': '🚫',
|
|
549
|
+
'scheduled': '📅',
|
|
550
|
+
'skipped': '⏭️'
|
|
551
|
+
};
|
|
552
|
+
return icons[state.toLowerCase()] || '❓';
|
|
553
|
+
}
|
|
554
|
+
getAnnotationIcon(style) {
|
|
555
|
+
const icons = {
|
|
556
|
+
'ERROR': '❌',
|
|
557
|
+
'WARNING': '⚠️',
|
|
558
|
+
'INFO': 'ℹ️',
|
|
559
|
+
'SUCCESS': '✅'
|
|
560
|
+
};
|
|
561
|
+
return icons[style.toUpperCase()] || '📝';
|
|
562
|
+
}
|
|
563
|
+
getJobStats(jobs) {
|
|
564
|
+
const stats = {
|
|
565
|
+
total: jobs?.length || 0,
|
|
566
|
+
passed: 0,
|
|
567
|
+
failed: 0,
|
|
568
|
+
running: 0,
|
|
569
|
+
blocked: 0,
|
|
570
|
+
skipped: 0,
|
|
571
|
+
queued: 0,
|
|
572
|
+
completed: 0
|
|
573
|
+
};
|
|
574
|
+
if (!jobs)
|
|
575
|
+
return stats;
|
|
576
|
+
for (const job of jobs) {
|
|
577
|
+
const state = job.node.state?.toUpperCase() || '';
|
|
578
|
+
if (state === 'PASSED' || (job.node.passed === true && state !== 'BROKEN')) {
|
|
579
|
+
stats.passed++;
|
|
580
|
+
stats.completed++;
|
|
581
|
+
}
|
|
582
|
+
else if (state === 'FAILED' || state === 'BROKEN' || (job.node.exitStatus && job.node.exitStatus !== 0) || job.node.passed === false) {
|
|
583
|
+
stats.failed++;
|
|
584
|
+
stats.completed++;
|
|
585
|
+
}
|
|
586
|
+
else if (state === 'RUNNING') {
|
|
587
|
+
stats.running++;
|
|
588
|
+
}
|
|
589
|
+
else if (state === 'BLOCKED') {
|
|
590
|
+
stats.blocked++;
|
|
591
|
+
}
|
|
592
|
+
else if (state === 'SKIPPED' || state === 'CANCELED') {
|
|
593
|
+
stats.skipped++;
|
|
594
|
+
stats.completed++;
|
|
595
|
+
}
|
|
596
|
+
else if (state === 'SCHEDULED' || state === 'ASSIGNED') {
|
|
597
|
+
stats.queued++;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return stats;
|
|
601
|
+
}
|
|
602
|
+
getFailedJobs(jobs) {
|
|
603
|
+
if (!jobs)
|
|
604
|
+
return [];
|
|
605
|
+
return jobs.filter(job => {
|
|
606
|
+
const state = job.node.state?.toUpperCase();
|
|
607
|
+
return state === 'FAILED' || state === 'BROKEN' || (job.node.exitStatus && job.node.exitStatus !== 0) || job.node.passed === false;
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
getRunningJobs(jobs) {
|
|
611
|
+
if (!jobs)
|
|
612
|
+
return [];
|
|
613
|
+
return jobs.filter(job => job.node.state?.toLowerCase() === 'running');
|
|
614
|
+
}
|
|
615
|
+
getBlockedJobs(jobs) {
|
|
616
|
+
if (!jobs)
|
|
617
|
+
return [];
|
|
618
|
+
return jobs.filter(job => job.node.state?.toLowerCase() === 'blocked');
|
|
619
|
+
}
|
|
620
|
+
groupJobsByState(jobs) {
|
|
621
|
+
const grouped = {
|
|
622
|
+
'Failed': [],
|
|
623
|
+
'Passed': [],
|
|
624
|
+
'Running': [],
|
|
625
|
+
'Blocked': [],
|
|
626
|
+
'Skipped': []
|
|
627
|
+
};
|
|
628
|
+
if (!jobs)
|
|
629
|
+
return grouped;
|
|
630
|
+
for (const job of jobs) {
|
|
631
|
+
const state = job.node.state?.toUpperCase() || '';
|
|
632
|
+
if (state === 'FAILED' || state === 'BROKEN' || (job.node.exitStatus && job.node.exitStatus !== 0) || job.node.passed === false) {
|
|
633
|
+
grouped['Failed'].push(job);
|
|
634
|
+
}
|
|
635
|
+
else if (state === 'PASSED' || (job.node.passed === true && state !== 'BROKEN')) {
|
|
636
|
+
grouped['Passed'].push(job);
|
|
637
|
+
}
|
|
638
|
+
else if (state === 'RUNNING') {
|
|
639
|
+
grouped['Running'].push(job);
|
|
640
|
+
}
|
|
641
|
+
else if (state === 'BLOCKED') {
|
|
642
|
+
grouped['Blocked'].push(job);
|
|
643
|
+
}
|
|
644
|
+
else if (state === 'SKIPPED' || state === 'CANCELED') {
|
|
645
|
+
grouped['Skipped'].push(job);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return grouped;
|
|
649
|
+
}
|
|
650
|
+
countAnnotationsByStyle(annotations) {
|
|
651
|
+
const counts = {
|
|
652
|
+
ERROR: 0,
|
|
653
|
+
WARNING: 0,
|
|
654
|
+
INFO: 0,
|
|
655
|
+
SUCCESS: 0
|
|
656
|
+
};
|
|
657
|
+
for (const annotation of annotations) {
|
|
658
|
+
const style = annotation.node.style?.toUpperCase() || 'INFO';
|
|
659
|
+
if (style in counts) {
|
|
660
|
+
counts[style]++;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return counts;
|
|
664
|
+
}
|
|
665
|
+
groupAnnotationsByStyle(annotations) {
|
|
666
|
+
const grouped = {};
|
|
667
|
+
for (const annotation of annotations) {
|
|
668
|
+
const style = annotation.node.style?.toUpperCase() || 'INFO';
|
|
669
|
+
if (!grouped[style]) {
|
|
670
|
+
grouped[style] = [];
|
|
671
|
+
}
|
|
672
|
+
grouped[style].push(annotation);
|
|
673
|
+
}
|
|
674
|
+
return grouped;
|
|
675
|
+
}
|
|
676
|
+
formatError(action, error) {
|
|
677
|
+
return formatError(action, error);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
//# sourceMappingURL=PlainTextFormatter.js.map
|