@docker/actions-toolkit
Version:
Toolkit for Docker (GitHub) Actions
166 lines • 9.13 kB
JavaScript
/**
* Copyright 2026 actions-toolkit authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import he from 'he';
import { dump as yamldump } from 'js-yaml';
import os from 'os';
import * as core from '@actions/core';
import { GitHub } from './github.js';
import { Util } from '../util.js';
export class GitHubSummary {
static async writeBuildSummary(opts) {
// can't use original core.summary.addLink due to the need to make
// EOL optional
const addLink = function (text, url, addEOL = false) {
return `<a href="${url}">${text}</a>` + (addEOL ? os.EOL : '');
};
const refsSize = opts.exportRes.refs.length;
const firstRef = refsSize > 0 ? opts.exportRes.refs?.[0] : undefined;
const firstSummary = firstRef ? opts.exportRes.summaries?.[firstRef] : undefined;
const dbcAccount = opts.driver === 'cloud' && opts.endpoint ? opts.endpoint?.replace(/^cloud:\/\//, '').split('/')[0] : undefined;
const sum = core.summary.addHeading('Docker Build summary', 2);
if (dbcAccount && refsSize === 1 && firstRef && firstSummary) {
const buildURL = GitHubSummary.formatDBCBuildURL(dbcAccount, firstRef, firstSummary.defaultPlatform);
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`For a detailed look at the build, you can check the results at:`)
.addRaw('</p>')
.addRaw(`<p>`)
.addRaw(`:whale: ${addLink(`<strong>${buildURL}</strong>`, buildURL)}`)
.addRaw(`</p>`);
}
if (opts.uploadRes) {
// we just need the last two parts of the URL as they are always relative
// to the workflow run URL otherwise URL could be broken if GitHub
// repository name is part of a secret value used in the workflow. e.g.:
// artifact: https://github.com/docker/actions-toolkit/actions/runs/9552208295/artifacts/1609622746
// workflow: https://github.com/docker/actions-toolkit/actions/runs/9552208295
// https://github.com/docker/actions-toolkit/issues/367
const artifactRelativeURL = `./${GitHub.runId}/${opts.uploadRes.url.split('/').slice(-2).join('/')}`;
if (dbcAccount && refsSize === 1) {
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`You can also download the following build record archive and import it into Docker Desktop's Builds view. `)
.addBreak()
.addRaw(`Build records include details such as timing, dependencies, results, logs, traces, and other information about a build. `)
.addRaw(addLink('Learn more', 'https://www.docker.com/blog/new-beta-feature-deep-dive-into-github-actions-docker-builds-with-docker-desktop/?utm_source=github&utm_medium=actions'))
.addRaw('</p>');
}
else {
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`For a detailed look at the build, download the following build record archive and import it into Docker Desktop's Builds view. `)
.addBreak()
.addRaw(`Build records include details such as timing, dependencies, results, logs, traces, and other information about a build. `)
.addRaw(addLink('Learn more', 'https://www.docker.com/blog/new-beta-feature-deep-dive-into-github-actions-docker-builds-with-docker-desktop/?utm_source=github&utm_medium=actions'))
.addRaw('</p>');
}
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`:arrow_down: ${addLink(`<strong>${Util.stringToUnicodeEntities(opts.uploadRes.filename)}</strong>`, artifactRelativeURL)} (${Util.formatFileSize(opts.uploadRes.size)} - includes <strong>${refsSize} build record${refsSize > 1 ? 's' : ''}</strong>)`)
.addRaw(`</p>`);
}
else if (opts.exportRes.summaries) {
// prettier-ignore
sum.addRaw(`<p>`)
.addRaw(`The following table provides a brief summary of your build.`)
.addBreak()
.addRaw(`For a detailed look at the build, including timing, dependencies, results, logs, traces, and other information, consider enabling the export of the build record so you can import it into Docker Desktop's Builds view. `)
.addRaw(addLink('Learn more', 'https://www.docker.com/blog/new-beta-feature-deep-dive-into-github-actions-docker-builds-with-docker-desktop/?utm_source=github&utm_medium=actions'))
.addRaw(`</p>`);
}
// Feedback survey
sum.addRaw(`<p>`).addRaw(`Find this useful? `).addRaw(addLink('Let us know', 'https://docs.docker.com/feedback/gha-build-summary')).addRaw('</p>');
if (opts.exportRes.summaries) {
// Preview
sum.addRaw('<p>');
const summaryTableData = [
// prettier-ignore
[
{ header: true, data: 'ID' },
{ header: true, data: 'Name' },
{ header: true, data: 'Status' },
{ header: true, data: 'Cached' },
{ header: true, data: 'Duration' },
...(dbcAccount && refsSize > 1 ? [{ header: true, data: 'Build result URL' }] : [])
]
];
let buildError;
for (const ref in opts.exportRes.summaries) {
if (Object.prototype.hasOwnProperty.call(opts.exportRes.summaries, ref)) {
const summary = opts.exportRes.summaries[ref];
// prettier-ignore
summaryTableData.push([
{ data: `<code>${ref.substring(0, 6).toUpperCase()}</code>` },
{ data: `<strong>${Util.stringToUnicodeEntities(summary.name)}</strong>` },
{ data: `${summary.status === 'completed' ? ':white_check_mark:' : summary.status === 'canceled' ? ':no_entry_sign:' : ':x:'} ${summary.status}` },
{ data: `${summary.numCachedSteps > 0 ? Math.round((summary.numCachedSteps / summary.numTotalSteps) * 100) : 0}%` },
{ data: summary.duration },
...(dbcAccount && refsSize > 1 ? [{ data: addLink(':whale: Open', GitHubSummary.formatDBCBuildURL(dbcAccount, ref, summary.defaultPlatform)) }] : [])
]);
if (summary.error) {
buildError = summary.error;
}
}
}
sum.addTable([...summaryTableData]);
sum.addRaw(`</p>`);
// Build error
if (buildError) {
sum.addRaw(`<blockquote>`);
if (Util.countLines(buildError) > 10) {
// prettier-ignore
sum
.addRaw(`<details><summary><strong>Error</strong></summary>`)
.addCodeBlock(he.encode(buildError), 'text')
.addRaw(`</details>`);
}
else {
// prettier-ignore
sum
.addRaw(`<strong>Error</strong>`)
.addBreak()
.addRaw(`<p>`)
.addCodeBlock(he.encode(buildError), 'text')
.addRaw(`</p>`);
}
sum.addRaw(`</blockquote>`);
}
}
// Build inputs
if (opts.inputs) {
// prettier-ignore
sum.addRaw(`<details><summary><strong>Build inputs</strong></summary>`)
.addCodeBlock(yamldump(opts.inputs, {
indent: 2,
lineWidth: -1
}), 'yaml')
.addRaw(`</details>`);
}
// Bake definition
if (opts.bakeDefinition) {
// prettier-ignore
sum.addRaw(`<details><summary><strong>Bake definition</strong></summary>`)
.addCodeBlock(JSON.stringify(opts.bakeDefinition, null, 2), 'json')
.addRaw(`</details>`);
}
core.info(`Writing summary`);
await sum.addSeparator().write();
}
static formatDBCBuildURL(account, ref, platform) {
return `https://app.docker.com/build/accounts/${account}/builds/${(platform ?? 'linux/amd64').replace('/', '-')}/${ref}`;
}
}
//# sourceMappingURL=summary.js.map