Skip to content

Commit

Permalink
Add save svg-badge directly to gist
Browse files Browse the repository at this point in the history
This adds the posibility of saving an SVG badge generated by the same
shields.io dirictly to the gist. Instead of prepering a JSON file to
be sent to their service, we use their library directly, which outputs
an SVG file that we can save to the user’s gist.

Filenames ending in `.svg` will use this library automatically.

Additionally there is a major refactoring where the older `node:http`
library has been swapped out for `fetch`.

Also swap from node 16 to node 20

fixes #24
  • Loading branch information
runarberg committed Oct 5, 2023
1 parent b7809ee commit a2d3829
Show file tree
Hide file tree
Showing 63 changed files with 4,631 additions and 136 deletions.
4 changes: 2 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ inputs:
description: 'The ID of the gist to use'
required: true
filename:
description: 'The *.json filename of the badge data'
description: 'The *.json or *.svg filename of the badge data'
required: true
label:
description: 'The left text of the badge'
Expand Down Expand Up @@ -72,5 +72,5 @@ inputs:
description: 'Lightness used by the color range feature. Defaults to 40.'
required: false
runs:
using: 'node16'
using: 'node20'
main: 'index.js'
251 changes: 121 additions & 130 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,82 @@
// Copyright: (c) 2020 Simon Schneegans //
//////////////////////////////////////////////////////////////////////////////////////////

const core = require('@actions/core');
const http = require('https');

// Performs an HTTP request and returns a Promise accordingly. See docs of
// http.request() for the available options.
function doRequest(options, data) {
return new Promise((resolve, reject) => {
const req = http.request(options, res => {
res.setEncoding('utf8');
let responseBody = '';

res.on('data', (chunk) => {
responseBody += chunk;
});

res.on('end', () => {
const {statusCode, statusMessage} = res;
resolve({statusCode, statusMessage, body: JSON.parse(responseBody)});
});
});
import core from '@actions/core';
import { makeBadge } from 'badge-maker';

req.on('error', (err) => {
reject(err);
});

req.write(data)
req.end();
});
}
const gistUrl = new URL(
core.getInput('gistID'),
'https://api.github.com/gists/'
);

// This uses the method above to update a gist with the given data. The user agent is
// required as defined in https://developer.github.com/v3/#user-agent-required
function updateGist(data) {
const updateGistOptions = {
host: 'api.github.com',
path: '/gists/' + core.getInput('gistID'),
async function updateGist(body) {
const headers = new Headers([
['Content-Type', 'application/json'],
['Content-Length', body.length],
['User-Agent', 'Schneegans'],
['Authorization', `token ${core.getInput('auth')}`],
]);

const response = await fetch(gistUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': data.length,
'User-Agent': 'Schneegans',
'Authorization': 'token ' + core.getInput('auth'),
}
};

doRequest(updateGistOptions, data).then(res => {
if (res.statusCode < 200 || res.statusCode >= 400) {
core.setFailed(
'Failed to create gist, response status code: ' + res.statusCode +
', status message: ' + res.statusMessage);
} else {
console.log('Success!');
}
headers,
body,
});

if (!response.ok) {
console.log(await response.text());
core.setFailed(
`Failed to create gist, response status code: ${response.status} ${response.statusText}`
);

return;
}

console.log('Success!');
}

// We wrap the entire action in a try / catch block so we can set it to "failed" if
// something goes wrong.
try {

// This object will be stringified and uploaded to the gist. The schemaVersion, label
// and message attributes are always required. All others are optional and added to the
// content object only if they are given to the action.
let content = {
schemaVersion: 1,
let data = {
label: core.getInput('label'),
message: core.getInput('message')
message: core.getInput('message'),
};

const filename = core.getInput('filename');
const isSvgFile = filename.endsWith('.svg');

if (!isSvgFile) {
data.schemaVersion = 1;
}

// Compute the message color based on the given inputs.
const color = core.getInput('color');
const valColorRange = core.getInput('valColorRange');
const minColorRange = core.getInput('minColorRange');
const maxColorRange = core.getInput('maxColorRange');
const invertColorRange = core.getInput('invertColorRange');
const color = core.getInput('color');
const valColorRange = core.getInput('valColorRange');
const minColorRange = core.getInput('minColorRange');
const maxColorRange = core.getInput('maxColorRange');
const invertColorRange = core.getInput('invertColorRange');
const colorRangeSaturation = core.getInput('colorRangeSaturation');
const colorRangeLightness = core.getInput('colorRangeLightness');
const colorRangeLightness = core.getInput('colorRangeLightness');

if (minColorRange != '' && maxColorRange != '' && valColorRange != '') {
const max = parseFloat(maxColorRange);
const min = parseFloat(minColorRange);
let val = parseFloat(valColorRange);
let val = parseFloat(valColorRange);

if (val < min) val = min;
if (val > max) val = max;

let hue = 0;
if (invertColorRange == '') {
hue = Math.floor((val - min) / (max - min) * 120);
hue = Math.floor(((val - min) / (max - min)) * 120);
} else {
hue = Math.floor((max - val) / (max - min) * 120);
hue = Math.floor(((max - val) / (max - min)) * 120);
}

let sat = 100;
Expand All @@ -107,116 +92,122 @@ try {
lig = parseFloat(colorRangeLightness);
}

content.color = 'hsl(' + hue + ', ' + sat + '%, ' + lig + '%)';

data.color = 'hsl(' + hue + ', ' + sat + '%, ' + lig + '%)';
} else if (color != '') {

content.color = color;
data.color = color;
}

// Get all optional attributes and add them to the content object if given.
const labelColor = core.getInput('labelColor');
const isError = core.getInput('isError');
const namedLogo = core.getInput('namedLogo');
const logoSvg = core.getInput('logoSvg');
const logoColor = core.getInput('logoColor');
const logoWidth = core.getInput('logoWidth');
const labelColor = core.getInput('labelColor');
const isError = core.getInput('isError');
const namedLogo = core.getInput('namedLogo');
const logoSvg = core.getInput('logoSvg');
const logoColor = core.getInput('logoColor');
const logoWidth = core.getInput('logoWidth');
const logoPosition = core.getInput('logoPosition');
const style = core.getInput('style');
const style = core.getInput('style');
const cacheSeconds = core.getInput('cacheSeconds');
const filename = core.getInput('filename');

if (labelColor != '') {
content.labelColor = labelColor;
data.labelColor = labelColor;
}

if (isError != '') {
content.isError = isError;
if (!isSvgFile && isError != '') {
data.isError = isError;
}

if (namedLogo != '') {
content.namedLogo = namedLogo;
if (!isSvgFile && namedLogo != '') {
data.namedLogo = namedLogo;
}

if (logoSvg != '') {
content.logoSvg = logoSvg;
if (!isSvgFile && logoSvg != '') {
data.logoSvg = logoSvg;
}

if (logoColor != '') {
content.logoColor = logoColor;
if (!isSvgFile && logoColor != '') {
data.logoColor = logoColor;
}

if (logoWidth != '') {
content.logoWidth = parseInt(logoWidth);
if (!isSvgFile && logoWidth != '') {
data.logoWidth = parseInt(logoWidth);
}

if (logoPosition != '') {
content.logoPosition = logoPosition;
if (!isSvgFile && logoPosition != '') {
data.logoPosition = logoPosition;
}

if (style != '') {
content.style = style;
data.style = style;
}

if (cacheSeconds != '') {
content.cacheSeconds = parseInt(cacheSeconds);
if (!isSvgFile && cacheSeconds != '') {
data.cacheSeconds = parseInt(cacheSeconds);
}

let content = '';

if (isSvgFile) {
content = makeBadge(data);
} else {
content = JSON.stringify({ content: data });
}

// For the POST request, the above content is set as file contents for the
// given filename.
const request =
JSON.stringify({files: {[filename]: {content: JSON.stringify(content)}}});
const body = JSON.stringify({ files: { [filename]: { content } } });

// If "forceUpdate" is set to true, we can simply update the gist. If not, we have to
// get the gist data and compare it to the new value before.
if (core.getBooleanInput('forceUpdate')) {
updateGist(request);

updateGist(body);
} else {

// Get the old gist.
const getGistOptions = {
host: 'api.github.com',
path: '/gists/' + core.getInput('gistID'),
fetch(gistUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'User-Agent': 'Schneegans',
'Authorization': 'token ' + core.getInput('auth'),
}
};

doRequest(getGistOptions, JSON.stringify({})).then(oldGist => {
if (oldGist.statusCode < 200 || oldGist.statusCode >= 400) {
// print the error, but don't fail the action.
console.log(
'Failed to get gist, response status code: ' + oldGist.statusCode +
', status message: ' + oldGist.statusMessage);
}

let shouldUpdate = true;

if (oldGist && oldGist.body && oldGist.body.files && oldGist.body.files[filename]) {
const oldContent = oldGist.body.files[filename].content;

if (oldContent === JSON.stringify(content)) {
console.log(`Content did not change, not updating gist at ${filename}.`);
shouldUpdate = false;
headers: new Headers([
['Content-Type', 'application/json'],
['User-Agent', 'Schneegans'],
['Authorization', `token ${core.getInput('auth')}`],
]),
})
.then((response) => {
if (!response.ok) {
// print the error, but don't fail the action.
console.log(
`Failed to get gist: ${response.status} ${response.statusText}`
);
response.text().then((text) => console.log(text));

return {};
}
}

if (shouldUpdate) {
if (oldGist.body.files[filename]) {
console.log(`Content changed, updating gist at ${filename}.`);
} else {
console.log(`Content didn't exist, creating gist at ${filename}.`);
return response.json();
})
.then((oldGist) => {
let shouldUpdate = true;

if (oldGist?.body?.files?.[filename]) {
const oldContent = oldGist.body.files[filename].content;

if (oldContent === content) {
console.log(
`Content did not change, not updating gist at ${filename}.`
);
shouldUpdate = false;
}
}

updateGist(request);
}
});
}
if (shouldUpdate) {
if (oldGist?.body?.files?.[filename]) {
console.log(`Content changed, updating gist at ${filename}.`);
} else {
console.log(`Content didn't exist, creating gist at ${filename}.`);
}

updateGist(body);
}
});
}
} catch (error) {
core.setFailed(error);
}
1 change: 1 addition & 0 deletions node_modules/.bin/badge

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a2d3829

Please sign in to comment.