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 support for generating checksums file automatically #6

Merged
merged 1 commit into from
Jan 4, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## [4.1.0] - 2023-01-DD
- Add support for generating checksums automatically

## [4.0.2] - 2022-12-29
- Prevent API overuse [#5](https://github.com/termux/upload-release-action/issues/5)

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ Optional Arguments
- `release_name`: Explicitly set a release name. (Defaults: implicitly same as `tag` via GitHub API).
- `body`: Content of the release text (Default: `""`).
- `repo_name`: Specify the name of the GitHub repository in which the GitHub release will be created, edited, and deleted. If the repository is other than the current, it is required to create a personal access token with `repo`, `user`, `admin:repo_hook` scopes to the foreign repository and add it as a secret. (Default: current repository).
- `checksums`: List of cryptographic checksums to calculate.

## Output variables

- `browser_download_url`: The publicly available URL of the asset.
- `browser_download_urls`: Array of publicly available URLs of the assets.

## Usage

Expand Down
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ inputs:
description: 'Content of the release text. Empty by default.'
repo_name:
description: 'Specify the name of the GitHub repository in which the GitHub release will be created, edited, and deleted. If the repository is other than the current, it is required to create a personal access token with `repo`, `user`, `admin:repo_hook` scopes to the foreign repository and add it as a secret. Defaults to the current repository'
checksums:
description: 'List of cryptographic checksums to calculate'
outputs:
browser_download_urls:
description: 'Array of publicly available URLs of the assets.'
Expand Down
73 changes: 61 additions & 12 deletions dist/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({

/***/ 9125:
/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {

"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
const node_crypto_1 = __nccwpck_require__(6005);
function calculateChecksum(fileBytes, algorithms) {
const checksum = {};
for (const algorithm of algorithms) {
const hash = (0, node_crypto_1.createHash)(algorithm);
hash.update(fileBytes);
checksum[algorithm] = hash.digest('hex');
}
return checksum;
}
exports["default"] = calculateChecksum;


/***/ }),

/***/ 2749:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

Expand Down Expand Up @@ -171,6 +192,7 @@ const glob = __importStar(__nccwpck_require__(1957));
const uploadToRelease_1 = __importDefault(__nccwpck_require__(2126));
const getReleaseByTag_1 = __importDefault(__nccwpck_require__(2749));
const getRepo_1 = __importDefault(__nccwpck_require__(9859));
const crypto_1 = __nccwpck_require__(6113);
function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
Expand All @@ -181,11 +203,20 @@ function run() {
.getInput('tag', { required: true })
.replace('refs/tags/', '')
.replace('refs/heads/', '');
const file_glob = core.getInput('file_glob') == 'true' ? true : false;
const overwrite = core.getInput('overwrite') == 'true' ? true : false;
const prerelease = core.getInput('prerelease') == 'true' ? true : false;
const file_glob = core.getBooleanInput('file_glob');
const overwrite = core.getBooleanInput('overwrite');
const prerelease = core.getBooleanInput('prerelease');
const release_name = core.getInput('release_name');
const body = core.getInput('body');
const checksums_algos = core.getMultilineInput('checksums');
const checksums = {};
// Make sure all checksums_algos are available
const availableHashes = (0, crypto_1.getHashes)();
for (const algo of checksums_algos) {
if (!availableHashes.includes(algo)) {
throw new Error('Unsupported cryptographic algorithm');
}
}
const octokit = github.getOctokit(token);
const release = yield (0, getReleaseByTag_1.default)(tag, prerelease, release_name, body, octokit);
// For checking duplicates
Expand All @@ -196,7 +227,7 @@ function run() {
const asset_download_urls = [];
for (const file of files) {
const asset_name = path.basename(file);
const asset_download_url = yield (0, uploadToRelease_1.default)(release, file, asset_name, tag, overwrite, octokit, assets);
const asset_download_url = yield (0, uploadToRelease_1.default)(release, file, asset_name, tag, overwrite, octokit, assets, checksums_algos, checksums);
if (typeof asset_download_url != 'undefined') {
asset_download_urls.push(asset_download_url);
}
Expand All @@ -217,7 +248,7 @@ function run() {
const asset_name = core.getInput('asset_name') !== ''
? core.getInput('asset_name').replace(/\$tag/g, tag)
: path.basename(file_name);
const asset_download_url = yield (0, uploadToRelease_1.default)(release, file_name, asset_name, tag, overwrite, octokit, assets);
const asset_download_url = yield (0, uploadToRelease_1.default)(release, file_name, asset_name, tag, overwrite, octokit, assets, checksums_algos, checksums);
core.setOutput('browser_download_urls', [asset_download_url]);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -273,10 +304,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.uploadFile = void 0;
const getRepo_1 = __importDefault(__nccwpck_require__(9859));
const core = __importStar(__nccwpck_require__(2186));
const fs_1 = __nccwpck_require__(7147);
function uploadToRelease(release, file, asset_name, tag, overwrite, octokit, assets) {
const calculateChecksum_1 = __importDefault(__nccwpck_require__(9125));
function uploadFile(release, file, file_bytes, file_size, asset_name, tag, octokit) {
return __awaiter(this, void 0, void 0, function* () {
core.debug(`Uploading ${file} to ${asset_name} in release ${tag}.`);
const uploaded_asset = yield octokit.rest.repos.uploadReleaseAsset(Object.assign(Object.assign({}, (0, getRepo_1.default)()), { name: asset_name, data: file_bytes, release_id: release.data.id, headers: {
'content-type': 'binary/octet-stream',
'content-length': file_size
} }));
return uploaded_asset.data.browser_download_url;
});
}
exports.uploadFile = uploadFile;
function uploadToRelease(release, file, asset_name, tag, overwrite, octokit, assets, checksum_algos, checksums) {
return __awaiter(this, void 0, void 0, function* () {
const stat = (0, fs_1.statSync)(file);
if (!stat.isFile()) {
Expand All @@ -285,6 +329,8 @@ function uploadToRelease(release, file, asset_name, tag, overwrite, octokit, ass
}
const file_size = stat.size;
const file_bytes = (0, fs_1.readFileSync)(file);
const checksum = (0, calculateChecksum_1.default)(file_bytes, checksum_algos);
checksums[file] = checksum;
// Check for duplicates
const duplicate_asset = assets.find(a => a.name === asset_name);
if (duplicate_asset !== undefined) {
Expand All @@ -300,12 +346,7 @@ function uploadToRelease(release, file, asset_name, tag, overwrite, octokit, ass
else {
core.debug(`No pre-existing asset called ${asset_name} found in release ${tag}. All good.`);
}
core.debug(`Uploading ${file} to ${asset_name} in release ${tag}.`);
const uploaded_asset = yield octokit.rest.repos.uploadReleaseAsset(Object.assign(Object.assign({}, (0, getRepo_1.default)()), { name: asset_name, data: file_bytes, release_id: release.data.id, headers: {
'content-type': 'binary/octet-stream',
'content-length': file_size
} }));
return uploaded_asset.data.browser_download_url;
return uploadFile(release, file, file_bytes, file_size, asset_name, tag, octokit);
});
}
exports["default"] = uploadToRelease;
Expand Down Expand Up @@ -13039,6 +13080,14 @@ module.exports = require("net");

/***/ }),

/***/ 6005:
/***/ ((module) => {

"use strict";
module.exports = require("node:crypto");

/***/ }),

/***/ 2037:
/***/ ((module) => {

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "upload-release-action",
"version": "4.0.2",
"version": "4.1.0",
"private": true,
"description": "Upload files to a GitHub release",
"main": "lib/main.js",
Expand Down
15 changes: 15 additions & 0 deletions src/calculateChecksum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {createHash} from 'node:crypto';
import {Checksum} from './types';

export default function calculateChecksum(
fileBytes: Buffer,
algorithms: string[]
): Checksum {
const checksum: Checksum = {};
for (const algorithm of algorithms) {
const hash = createHash(algorithm);
hash.update(fileBytes);
checksum[algorithm] = hash.digest('hex');
}
return checksum;
}
28 changes: 22 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import * as glob from 'glob';
import uploadToRelease from './uploadToRelease';
import getReleaseByTag from './getReleaseByTag';
import getRepo from './getRepo';
import {RepoAssetsResp} from './types';
import {Checksums, RepoAssetsResp} from './types';
import {getHashes} from 'crypto';

async function run(): Promise<void> {
try {
Expand All @@ -17,11 +18,21 @@ async function run(): Promise<void> {
.replace('refs/tags/', '')
.replace('refs/heads/', '');

const file_glob = core.getInput('file_glob') == 'true' ? true : false;
const overwrite = core.getInput('overwrite') == 'true' ? true : false;
const prerelease = core.getInput('prerelease') == 'true' ? true : false;
const file_glob = core.getBooleanInput('file_glob');
const overwrite = core.getBooleanInput('overwrite');
const prerelease = core.getBooleanInput('prerelease');
const release_name = core.getInput('release_name');
const body = core.getInput('body');
const checksums_algos = core.getMultilineInput('checksums');
const checksums: Checksums = {};

// Make sure all checksums_algos are available
const availableHashes = getHashes();
for (const algo of checksums_algos) {
if (!availableHashes.includes(algo)) {
throw new Error('Unsupported cryptographic algorithm');
}
}

const octokit = github.getOctokit(token);
const release = await getReleaseByTag(
Expand Down Expand Up @@ -55,7 +66,9 @@ async function run(): Promise<void> {
tag,
overwrite,
octokit,
assets
assets,
checksums_algos,
checksums
);
if (typeof asset_download_url != 'undefined') {
asset_download_urls.push(asset_download_url);
Expand Down Expand Up @@ -83,10 +96,13 @@ async function run(): Promise<void> {
tag,
overwrite,
octokit,
assets
assets,
checksums_algos,
checksums
);
core.setOutput('browser_download_urls', [asset_download_url]);
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
core.setFailed(error.message);
Expand Down
8 changes: 8 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ export type CreateReleaseResp =
Endpoints['POST /repos/{owner}/{repo}/releases']['response'];
export type UploadAssetResp =
Endpoints['POST {origin}/repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}']['response'];

export interface Checksum {
[key: string]: string;
}

export interface Checksums {
[fileName: string]: Checksum;
}
28 changes: 28 additions & 0 deletions src/uploadChecksums.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Checksums, CreateReleaseResp, ReleaseByTagResp} from './types';
import {GitHub} from '@actions/github/lib/utils';
import {uploadFile} from './uploadToRelease';

export default function uploadChecksums(
checksums: Checksums,
algos: string[],
release: ReleaseByTagResp | CreateReleaseResp,
tag: string,
octokit: InstanceType<typeof GitHub>
): void {
for (const algo of algos) {
let checksumsFileContent = '';
const checksumsFileName = 'CHECKSUMS-' + algo + '.txt';
for (const file of Object.keys(checksums)) {
checksumsFileContent += `${file}\t${checksums[file][algo]}\n`;
}
uploadFile(
release,
checksumsFileName,
Buffer.from(checksumsFileContent),
checksumsFileContent.length,
checksumsFileName,
tag,
octokit
);
}
}
56 changes: 41 additions & 15 deletions src/uploadToRelease.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,34 @@ import {
ReleaseByTagResp,
CreateReleaseResp,
UploadAssetResp,
RepoAssetsResp
RepoAssetsResp,
Checksums
} from './types';
import calculateChecksum from './calculateChecksum';

export async function uploadFile(
release: ReleaseByTagResp | CreateReleaseResp,
file: string,
file_bytes: Buffer,
file_size: number,
asset_name: string,
tag: string,
octokit: InstanceType<typeof GitHub>
): Promise<undefined | string> {
core.debug(`Uploading ${file} to ${asset_name} in release ${tag}.`);
const uploaded_asset: UploadAssetResp =
await octokit.rest.repos.uploadReleaseAsset({
...getRepo(),
name: asset_name,
data: file_bytes as unknown as string,
release_id: release.data.id,
headers: {
'content-type': 'binary/octet-stream',
'content-length': file_size
}
});
return uploaded_asset.data.browser_download_url;
}

export default async function uploadToRelease(
release: ReleaseByTagResp | CreateReleaseResp,
Expand All @@ -16,7 +42,9 @@ export default async function uploadToRelease(
tag: string,
overwrite: boolean,
octokit: InstanceType<typeof GitHub>,
assets: RepoAssetsResp
assets: RepoAssetsResp,
checksum_algos: string[],
checksums: Checksums
): Promise<undefined | string> {
const stat = statSync(file);
if (!stat.isFile()) {
Expand All @@ -25,6 +53,8 @@ export default async function uploadToRelease(
}
const file_size = stat.size;
const file_bytes = readFileSync(file);
const checksum = calculateChecksum(file_bytes, checksum_algos);
checksums[file] = checksum;

// Check for duplicates
const duplicate_asset = assets.find(a => a.name === asset_name);
Expand All @@ -47,17 +77,13 @@ export default async function uploadToRelease(
);
}

core.debug(`Uploading ${file} to ${asset_name} in release ${tag}.`);
const uploaded_asset: UploadAssetResp =
await octokit.rest.repos.uploadReleaseAsset({
...getRepo(),
name: asset_name,
data: file_bytes as unknown as string,
release_id: release.data.id,
headers: {
'content-type': 'binary/octet-stream',
'content-length': file_size
}
});
return uploaded_asset.data.browser_download_url;
return uploadFile(
release,
file,
file_bytes,
file_size,
asset_name,
tag,
octokit
);
}