bktide
Version:
Command-line interface for Buildkite CI/CD workflows with rich shell completions (Fish, Bash, Zsh) and Alfred workflow integration for macOS power users
113 lines • 4.19 kB
JavaScript
import { BaseBuildDetailFormatter } from './Formatter.js';
export class AlfredFormatter extends BaseBuildDetailFormatter {
name = 'alfred';
formatBuildDetail(buildData, options) {
if (options?.hasError || !buildData) {
return JSON.stringify({
items: [{
uid: 'error',
title: 'Error fetching build',
subtitle: options?.errorMessage || 'An error occurred',
valid: false,
icon: {
path: './icons/error.png'
}
}]
});
}
const build = buildData.build;
const items = [];
// Main build item
const duration = this.formatDuration(build);
const mainItem = {
uid: build.id,
title: `#${build.number}: ${build.message || 'No message'}`,
subtitle: `${build.state} • ${build.branch} • ${duration}`,
arg: build.url,
icon: {
path: this.getIconPath(build.state)
},
mods: {
cmd: {
subtitle: 'Open in browser',
arg: build.url
},
alt: {
subtitle: 'Copy build number',
arg: build.number.toString()
}
}
};
items.push(mainItem);
// Add failed jobs if present
if (options?.failed || build.state === 'FAILED') {
const failedJobs = (build.jobs?.edges || [])
.filter((j) => j.node.state === 'FAILED' || j.node.exitStatus !== 0);
for (const job of failedJobs) {
items.push({
uid: job.node.id,
title: `❌ ${job.node.label}`,
subtitle: `Failed with exit code ${job.node.exitStatus || 1}`,
valid: false,
icon: {
path: './icons/failed.png'
}
});
}
}
// Add annotation summary if present
if (build.annotations?.edges?.length > 0) {
const annotationCount = build.annotations.edges.length;
items.push({
uid: 'annotations',
title: `📝 ${annotationCount} annotation${annotationCount > 1 ? 's' : ''}`,
subtitle: 'View build annotations',
valid: false,
icon: {
path: './icons/annotation.png'
}
});
}
return JSON.stringify({ items });
}
formatDuration(build) {
if (!build.startedAt) {
return 'not started';
}
const start = new Date(build.startedAt);
const end = build.finishedAt ? new Date(build.finishedAt) : new Date();
const durationMs = end.getTime() - start.getTime();
const minutes = Math.floor(durationMs / 60000);
const seconds = Math.floor((durationMs % 60000) / 1000);
if (build.state === 'RUNNING') {
return `${minutes}m ${seconds}s elapsed`;
}
return `${minutes}m ${seconds}s`;
}
getIconPath(state) {
const iconMap = {
'PASSED': './icons/passed.png',
'FAILED': './icons/failed.png',
'RUNNING': './icons/running.png',
'BLOCKED': './icons/blocked.png',
'CANCELED': './icons/canceled.png',
'SCHEDULED': './icons/scheduled.png',
'SKIPPED': './icons/skipped.png'
};
return iconMap[state] || './icons/unknown.png';
}
formatError(action, error) {
return JSON.stringify({
items: [{
uid: 'error',
title: `Error ${action}`,
subtitle: error instanceof Error ? error.message : String(error),
valid: false,
icon: {
path: './icons/error.png'
}
}]
});
}
}
//# sourceMappingURL=AlfredFormatter.js.map