diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..6c25f3a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node +{ + "name": "gitea-release-action", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/javascript-node:4-22-bookworm" + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "yarn install", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 1380f2e..83a928a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -40594,6 +40594,8 @@ var __webpack_exports__ = {}; // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. (() => { +;// CONCATENATED MODULE: external "node:fs/promises" +const promises_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:fs/promises"); // EXTERNAL MODULE: external "fs" var external_fs_ = __nccwpck_require__(7147); var external_fs_namespaceObject = /*#__PURE__*/__nccwpck_require__.t(external_fs_, 2); @@ -43826,7 +43828,7 @@ var external_path_ = __nccwpck_require__(1017); // EXTERNAL MODULE: external "url" var external_url_ = __nccwpck_require__(7310); ;// CONCATENATED MODULE: external "fs/promises" -const promises_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("fs/promises"); +const external_fs_promises_namespaceObject = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("fs/promises"); // EXTERNAL MODULE: external "events" var external_events_ = __nccwpck_require__(2361); // EXTERNAL MODULE: external "stream" @@ -44870,10 +44872,10 @@ const defaultFS = { readlinkSync: external_fs_.readlinkSync, realpathSync, promises: { - lstat: promises_namespaceObject.lstat, - readdir: promises_namespaceObject.readdir, - readlink: promises_namespaceObject.readlink, - realpath: promises_namespaceObject.realpath, + lstat: external_fs_promises_namespaceObject.lstat, + readdir: external_fs_promises_namespaceObject.readdir, + readlink: external_fs_promises_namespaceObject.readlink, + realpath: external_fs_promises_namespaceObject.realpath, }, }; // if they just gave us require('fs') then use our default @@ -48144,6 +48146,7 @@ var crypto_js = __nccwpck_require__(4134); + function getIsTrue(v) { const trueValue = ['true', 'True', 'TRUE'] return trueValue.includes(v) @@ -48264,6 +48267,68 @@ function paths(patterns) { }, []); }; +async function createStreamableFile(fpath) { + const name = external_path_.basename(fpath); + const handle = await promises_namespaceObject.open(fpath); + const { size } = await handle.stat(); + + const file = new external_buffer_.File([], name); + file.stream = () => handle.readableWebStream(); + file.close = async () => await handle?.close(); + + // Set correct size otherwise, fetch will encounter UND_ERR_REQ_CONTENT_LENGTH_MISMATCH + Object.defineProperty(file, 'size', { get: () => size }); + + return file; +} + + +async function calculateMultipleHashes(file, algorithms = ['md5', 'sha256']) { + const stream = file.stream(); + const reader = stream.getReader(); + + const hashers = algorithms.map(alg => { + switch(alg.toLowerCase()) { + case 'md5': + return { name: 'md5', instance: crypto_js.algo.MD5.create() }; + case 'sha1': + return { name: 'sha1', instance: crypto_js.algo.SHA1.create() }; + case 'sha256': + return { name: 'sha256', instance: crypto_js.algo.SHA256.create() }; + case 'sha512': + return { name: 'sha512', instance: crypto_js.algo.SHA512.create() }; + default: + throw new Error(`not support hash: ${alg}`); + } + }); + + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + break; + } + + const wordArray = crypto_js.lib.WordArray.create(value); + + hashers.forEach(hasher => { + hasher.instance.update(wordArray); + }); + } + + const result = {}; + hashers.forEach(hasher => { + result[hasher.name] = hasher.instance.finalize().toString(crypto_js.enc.Hex); + }); + + return result; + } finally { + reader.releaseLock(); + } +} + + /** * * @param {gitea.GiteaApi} client @@ -48304,19 +48369,31 @@ async function uploadFiles(client, owner, repo, release_id, all_files, params) { } // upload new release attachment for (const filepath of all_files) { - const content = external_fs_.readFileSync(filepath); - let blob = new external_buffer_.Blob([content]); + let curfile = await createStreamableFile(filepath) await client.repository.repoCreateReleaseAttachment({ owner: owner, repo: repo, id: release_id, - attachment: blob, + attachment: curfile, name: external_path_.basename(filepath), }) + await curfile.close(); + let algorithms = []; if (params.md5sum) { - let wordArray = crypto_js.lib.WordArray.create(content); - let hash = crypto_js.MD5(wordArray).toString(); - blob = new external_buffer_.Blob([hash], { type : 'plain/text' }); + algorithms = algorithms.concat('md5'); + } + if (params.sha256sum) { + algorithms = algorithms.concat('sha256'); + } + let hashes = {}; + if (algorithms.length !== 0) { + curfile = await createStreamableFile(filepath) + hashes = await calculateMultipleHashes(curfile, algorithms) + await curfile.close(); + } + if (params.md5sum) { + let hash = hashes.md5; + let blob = new external_buffer_.Blob([hash], { type : 'plain/text' }); await client.repository.repoCreateReleaseAttachment({ owner: owner, repo: repo, @@ -48326,9 +48403,8 @@ async function uploadFiles(client, owner, repo, release_id, all_files, params) { }) } if (params.sha256sum) { - let wordArray = crypto_js.lib.WordArray.create(content); - let hash = crypto_js.SHA256(wordArray).toString(); - blob = new external_buffer_.Blob([hash], { type : 'plain/text' }); + let hash = hashes.sha256; + let blob = new external_buffer_.Blob([hash], { type : 'plain/text' }); await client.repository.repoCreateReleaseAttachment({ owner: owner, repo: repo, diff --git a/main.js b/main.js index 656886a..7aa2cb2 100644 --- a/main.js +++ b/main.js @@ -1,5 +1,6 @@ +import asyncfs from "node:fs/promises"; import fs from "fs"; -import { Blob } from "buffer"; +import { Blob, File } from "buffer"; import * as glob from "glob"; import core from "@actions/core"; @@ -128,6 +129,68 @@ function paths(patterns) { }, []); }; +async function createStreamableFile(fpath) { + const name = path.basename(fpath); + const handle = await asyncfs.open(fpath); + const { size } = await handle.stat(); + + const file = new File([], name); + file.stream = () => handle.readableWebStream(); + file.close = async () => await handle?.close(); + + // Set correct size otherwise, fetch will encounter UND_ERR_REQ_CONTENT_LENGTH_MISMATCH + Object.defineProperty(file, 'size', { get: () => size }); + + return file; +} + + +async function calculateMultipleHashes(file, algorithms = ['md5', 'sha256']) { + const stream = file.stream(); + const reader = stream.getReader(); + + const hashers = algorithms.map(alg => { + switch(alg.toLowerCase()) { + case 'md5': + return { name: 'md5', instance: CryptoJS.algo.MD5.create() }; + case 'sha1': + return { name: 'sha1', instance: CryptoJS.algo.SHA1.create() }; + case 'sha256': + return { name: 'sha256', instance: CryptoJS.algo.SHA256.create() }; + case 'sha512': + return { name: 'sha512', instance: CryptoJS.algo.SHA512.create() }; + default: + throw new Error(`not support hash: ${alg}`); + } + }); + + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + break; + } + + const wordArray = CryptoJS.lib.WordArray.create(value); + + hashers.forEach(hasher => { + hasher.instance.update(wordArray); + }); + } + + const result = {}; + hashers.forEach(hasher => { + result[hasher.name] = hasher.instance.finalize().toString(CryptoJS.enc.Hex); + }); + + return result; + } finally { + reader.releaseLock(); + } +} + + /** * * @param {gitea.GiteaApi} client @@ -168,19 +231,31 @@ async function uploadFiles(client, owner, repo, release_id, all_files, params) { } // upload new release attachment for (const filepath of all_files) { - const content = fs.readFileSync(filepath); - let blob = new Blob([content]); + let curfile = await createStreamableFile(filepath) await client.repository.repoCreateReleaseAttachment({ owner: owner, repo: repo, id: release_id, - attachment: blob, + attachment: curfile, name: path.basename(filepath), }) + await curfile.close(); + let algorithms = []; if (params.md5sum) { - let wordArray = CryptoJS.lib.WordArray.create(content); - let hash = CryptoJS.MD5(wordArray).toString(); - blob = new Blob([hash], { type : 'plain/text' }); + algorithms = algorithms.concat('md5'); + } + if (params.sha256sum) { + algorithms = algorithms.concat('sha256'); + } + let hashes = {}; + if (algorithms.length !== 0) { + curfile = await createStreamableFile(filepath) + hashes = await calculateMultipleHashes(curfile, algorithms) + await curfile.close(); + } + if (params.md5sum) { + let hash = hashes.md5; + let blob = new Blob([hash], { type : 'plain/text' }); await client.repository.repoCreateReleaseAttachment({ owner: owner, repo: repo, @@ -190,9 +265,8 @@ async function uploadFiles(client, owner, repo, release_id, all_files, params) { }) } if (params.sha256sum) { - let wordArray = CryptoJS.lib.WordArray.create(content); - let hash = CryptoJS.SHA256(wordArray).toString(); - blob = new Blob([hash], { type : 'plain/text' }); + let hash = hashes.sha256; + let blob = new Blob([hash], { type : 'plain/text' }); await client.repository.repoCreateReleaseAttachment({ owner: owner, repo: repo,