Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add GitHub action summary #5

Merged
merged 3 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ jobs:
- name: "@nodesecure/ci analysis"
uses: ./
with:
warnings: error
warnings: warning
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Contributing to NodeSecure

Contributions to NodeSecure include code, documentation, answering user questions and
running the project's infrastructure

Expand Down
105 changes: 7 additions & 98 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,49 +1,18 @@
// Import Third-party dependencies
import core from "@actions/core";
import { runPipeline } from "@nodesecure/ci";

// Import Internal Dependencies
import { generateSummary } from "./src/summary.js";

const directory = core.getInput("directory") ?? process.env.GITHUB_WORKSPACE;
const strategy = core.getInput("strategy");
const vulnerabilities = core.getInput("vulnerabilities");
const warnings = core.getInput("warnings");
const reporters = core.getInput("reporters");

function generateOutcomeWithEmoji(reportData, hasSpecificOutcome) {
if (hasSpecificOutcome) {
if (warnings === "warning") {
return `🟡 ${reportData.length}`;
} else if (warnings === "off") {
return "(skipped)";
}
}

if (reportData.length === 0) {
return `✅ 0`;
}

return `❌ ${reportData.length}`;
}

function generateOutcomeDepsWarnings(depsWarnings) {
return depsWarnings.flatMap(({ warnings, package: packageName }) =>
warnings.map((warning) => {
const location = warning.location.flatMap((location) =>
location.join(":")
);
return `${packageName} from ${warning.file}:${location}`;
})
);
}

function generateOutcomeVulns(vulns) {
return vulns.map((vuln) => {
const vulnRanges = vuln.vulnerableRanges.join(", ");

return `[${vuln.severity}] ${vuln.package}: ${vuln.title} ${vulnRanges}`;
});
}

try {
const result = await runPipeline({
const report = await runPipeline({
warnings,
strategy,
reporters,
Expand All @@ -52,69 +21,9 @@ try {
autoExitAfterFailure: false,
});

const vulns = result.data.dependencies.vulnerabilities;
const depsWarnings = result.data.dependencies.warnings;
const globalWarnings = result.data.warnings;
const isReportSuccessful = result.status === "success";

await core.summary
.addHeading(
`${
isReportSuccessful ? "✅" : "❌"
} [${result.status.toUpperCase()}]: @nodesecure/ci analysis`
)
.addTable([
[
{ data: "Global warnings", header: true },
{ data: "Dependency warnings", header: true },
{ data: "Dependency vulnerabilities", header: true },
],
[
generateOutcomeWithEmoji(globalWarnings),
generateOutcomeWithEmoji(depsWarnings, true),
generateOutcomeWithEmoji(vulns),
],
])
.addBreak();

if (vulns.length > 0) {
await core.summary
.addHeading(
`(${generateOutcomeWithEmoji(vulns)}) Dependencies vulnerabilities:`
)
.addList(generateOutcomeVulns(vulns));
await core.summary.addSeparator();
}

if (globalWarnings.length > 0) {
await core.summary
.addHeading(
`(${generateOutcomeWithEmoji(globalWarnings)}) Global warnings:`
)
.addList(globalWarnings);
await core.summary.addSeparator();
}

if (depsWarnings.length > 0) {
await core.summary
.addHeading(
`(${generateOutcomeWithEmoji(
depsWarnings,
true
)}) Dependencies warnings:`
)
.addList(generateOutcomeDepsWarnings(depsWarnings));
}

await core.summary
.addSeparator()
.addLink(
"View @nodesecure/ci documentation",
"https://github.com/NodeSecure/ci"
)
.write();
await generateSummary(report);

if (result.status === "failure") {
if (report.status === "failure") {
core.setFailed(`[FAILURE]: @nodesecure/ci checks failed.`);
}
} catch (error) {
Expand Down
159 changes: 159 additions & 0 deletions src/summary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Import Third-party dependencies
import core from "@actions/core";

const kSuccessEmoji = "✅";
const kFailureEmoji = "❌";
const kInfoEmoji = "🟡";
const kNodeSecureLogoSrc =
"https://avatars.githubusercontent.com/u/85318671?s=96&v=4";
const kActionBadges = [
"https://img.shields.io/badge/Maintained%3F-yes-green.svg",
"https://img.shields.io/github/license/Naereen/StrapDown.js.svg",
"https://img.shields.io/badge/dynamic/json.svg?url=https://raw.githubusercontent.com/NodeSecure/ci-action/master/package.json&query=$.version&label=Version",
];

function generateOutcomeEmoji(reportData, hasSpecificOutcome) {
if (hasSpecificOutcome) {
const warnings = core.getInput("warnings");
if (warnings === "warning") {
return `${kInfoEmoji} ${reportData.length}`;
} else if (warnings === "off") {
return "(skipped)";
}
}

if (reportData.length === 0) {
return `${kSuccessEmoji} 0`;
}

return `${kFailureEmoji} ${reportData.length}`;
}

function generateOutcomeGlobalWarnings(globalWarnings) {
return `<ul>
${globalWarnings.map((warning) => `<li>${warning}</li>`).join("")}
</ul>`;
}

function generateOutcomeDepsWarnings(depsWarnings) {
return `
<br />
<table>
<tbody>
<tr>
<th>Package</th>
<th>Kind</th>
<th>File</th>
<th>Location</th>
</tr>
${depsWarnings
.flatMap(({ warnings, package: packageName }) =>
warnings.map((warning) => {
const location = warning.location.flatMap((location) =>
location.join(":")
);
return `<tr>
<td>${packageName}</td>
<td>${warning.kind}</td>
<td>${warning.file}</td>
<td>${location}</td>
</tr>`;
})
)
.join("")}
</tbody>
</table>
`;
}

function generateOutcomeVulns(vulns) {
return `
<br />
<table>
<tbody>
<tr>
<th>Package</th>
<th>Severity</th>
<th>Title</th>
<th>Ranges</th>
</tr>
${vulns
.map((vuln) => {
const vulnRanges = vuln.vulnerableRanges.join(", ");
return `<tr>
<td>${vuln.package}</td>
<td><${vuln.severity}</td>
<td>${vuln.title}</td>
<td>${vulnRanges}</td>
</tr>`;
})
.join("")}
</tbody>
</table>
`;
}

function generateOutcomeTitle(report) {
const isReportSuccessful = report.status === "success";
const emojiOutcome = isReportSuccessful ? kSuccessEmoji : kFailureEmoji;
const outcomeStatus = isReportSuccessful ? "successful" : "failed";
return `${emojiOutcome} [${report.status.toUpperCase()}]: @nodesecure/ci security checks ${outcomeStatus}.`;
}

export async function generateSummary(report) {
const vulns = report.data.dependencies.vulnerabilities;
const depsWarnings = report.data.dependencies.warnings;
const globalWarnings = report.data.warnings;

core.summary
.addImage(kNodeSecureLogoSrc, "NodeSecure", { width: 50, height: 50 })
.addHeading(generateOutcomeTitle(report), 3)
.addTable([
[
{ data: "Global warnings", header: true },
{ data: "Dependency warnings", header: true },
{ data: "Dependency vulnerabilities", header: true },
],
[
generateOutcomeEmoji(globalWarnings),
generateOutcomeEmoji(depsWarnings, true),
generateOutcomeEmoji(vulns),
],
])
.addBreak();

if (globalWarnings.length > 0) {
core.summary.addDetails(
`[${generateOutcomeEmoji(globalWarnings)}] Global warnings:`,
generateOutcomeGlobalWarnings(globalWarnings)
);
core.summary.addSeparator();
}

if (depsWarnings.length > 0) {
core.summary.addDetails(
`[${generateOutcomeEmoji(depsWarnings, true)}] Dependencies warnings:`,
generateOutcomeDepsWarnings(depsWarnings)
);
core.summary.addSeparator();
}

if (vulns.length > 0) {
core.summary.addDetails(
`[${generateOutcomeEmoji(vulns)}] Dependencies vulnerabilities:`,
generateOutcomeVulns(vulns)
);
core.summary.addSeparator();
}

for (const badgeSrc of kActionBadges) {
core.summary.addImage(badgeSrc);
}

await core.summary
.addLink(
"View @nodesecure/ci documentation",
"https://github.com/NodeSecure/ci"
)
.write();
}