diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 7bda026..cd1bdef 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -1,18 +1,59 @@ -import {IReleaseDownloadSettings} from "../src/download-settings" -import {download} from "../src/download" +import * as core from "@actions/core" +import * as fs from "fs" +import * as handlers from "typed-rest-client/Handlers" +import * as io from "@actions/io" +import * as thc from "typed-rest-client/HttpClient" -test("run download", async () => { - - const githubtoken = process.env.GITHUB_TOKEN ?? "" +import {IReleaseDownloadSettings} from "../src/download-settings" +import {ReleaseDownloader} from "../src/release-downloader" + +const nock = require("nock") + +let downloader: ReleaseDownloader +let httpClent: thc.HttpClient +const outputFilePath = "./target" + +beforeAll(() => { + const githubtoken = process.env.REPO_TOKEN || "" + + const credentialHandler = new handlers.BearerCredentialHandler( + githubtoken, + false + ) + httpClent = new thc.HttpClient("gh-api-client", [credentialHandler]) + downloader = new ReleaseDownloader(httpClent) + + nock("https://api.github.com") + .get("/repos/robinraju/probable-potato/releases/latest") + .reply(200, readFromFile("1-release-latest.json")) + + nock("https://api.github.com", { + reqheaders: {accept: "application/octet-stream"} + }) + .get("/repos/robinraju/probable-potato/releases/assets/66946546") + .replyWithFile(200, __dirname + "/resource/assets/test-1.txt") +}) + +afterAll(() => { + io.rmRF(outputFilePath) +}) + +function readFromFile(fileName: string): string { + return fs.readFileSync(`./__tests__/resource/${fileName}`, { + encoding: "utf-8" + }) +} + +test("Download from public repo", async () => { const downloadSettings: IReleaseDownloadSettings = { - sourceRepoPath: "lihaoyi/Ammonite", - isLatest: false, - tag: "2.1.1", - fileName: "2.13-2.1.1-14-4f2a1b2-bootstrap", + sourceRepoPath: "robinraju/probable-potato", + isLatest: true, + tag: "", + fileName: "*", tarBall: false, zipBall: false, - outFilePath: "./target", - token: githubtoken + outFilePath: outputFilePath } - await download(downloadSettings) + const result = await downloader.download(downloadSettings) + expect(result.length).toBe(1) }, 10000) diff --git a/__tests__/resource/1-release-latest.json b/__tests__/resource/1-release-latest.json new file mode 100644 index 0000000..233dc21 --- /dev/null +++ b/__tests__/resource/1-release-latest.json @@ -0,0 +1,36 @@ +{ + "url": "https://api.github.com/repos/robinraju/probable-potato/releases/68092191", + "assets_url": "https://api.github.com/repos/robinraju/probable-potato/releases/68092191/assets", + "upload_url": "https://uploads.github.com/repos/robinraju/probable-potato/releases/68092191/assets{?name,label}", + "html_url": "https://github.com/robinraju/probable-potato/releases/tag/1.0.1", + "id": 68092191, + "author": {}, + "node_id": "RE_kwDOHahpL84EDwEf", + "tag_name": "1.0.1", + "target_commitish": "main", + "name": "Probable potato - v1.0.1", + "draft": false, + "prerelease": false, + "created_at": "2022-05-29T12:03:47Z", + "published_at": "2022-05-29T12:04:42Z", + "assets": [ + { + "url": "https://api.github.com/repos/robinraju/probable-potato/releases/assets/66946546", + "id": 66946546, + "node_id": "RA_kwDOHahpL84D_YXy", + "name": "test-1.txt", + "label": null, + "uploader": {}, + "content_type": "image/jpeg", + "state": "uploaded", + "size": 7253, + "download_count": 56, + "created_at": "2022-05-29T12:08:23Z", + "updated_at": "2022-05-29T12:08:23Z", + "browser_download_url": "https://github.com/robinraju/probable-potato/releases/download/1.0.1/potato.jpeg" + } + ], + "tarball_url": "https://api.github.com/repos/robinraju/probable-potato/tarball/1.0.1", + "zipball_url": "https://api.github.com/repos/robinraju/probable-potato/zipball/1.0.1", + "body": "" +} \ No newline at end of file diff --git a/__tests__/resource/assets/test-1.txt b/__tests__/resource/assets/test-1.txt new file mode 100644 index 0000000..779e1eb --- /dev/null +++ b/__tests__/resource/assets/test-1.txt @@ -0,0 +1 @@ +v1.0 robinraju/probable-potato/releases/latest \ No newline at end of file diff --git a/action.yml b/action.yml index 41a4e7d..c83f976 100644 --- a/action.yml +++ b/action.yml @@ -16,7 +16,7 @@ inputs: required: false fileName: description: "Name of the file to download (use '*' to download all assets other than tarball or zipball)" - default: "" + default: "*" required: false tarBall: description: "Download tarball from assets" diff --git a/package-lock.json b/package-lock.json index 886ac6b..794bea4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "jest": "^27.5.1", "jest-circus": "^28.1.0", "js-yaml": "^4.1.0", + "nock": "^13.2.6", "prettier": "2.6.2", "ts-jest": "^27.1.4", "typescript": "^4.7.2" @@ -5551,6 +5552,12 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, "node_modules/json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -5797,6 +5804,21 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/nock": { + "version": "13.2.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.2.6.tgz", + "integrity": "sha512-GbyeSwSEP0FYouzETZ0l/XNm5tNcDNcXJKw3LCAb+mx8bZSwg1wEEvdL0FAyg5TkBJYiWSCtw6ag4XfmBy60FA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.21", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6154,6 +6176,15 @@ "node": ">= 6" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -8613,8 +8644,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "7.2.0", @@ -9335,8 +9365,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.6", @@ -9445,8 +9474,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-i18n-text/-/eslint-plugin-i18n-text-1.0.1.tgz", "integrity": "sha512-3G3UetST6rdqhqW9SfcfzNYMpQXS7wNkJvp6dsXnjzGiku6Iu5hl3B0kmk6lIcFPwYjhQIY+tXVRtK9TlGT7RA==", - "dev": true, - "requires": {} + "dev": true }, "eslint-plugin-import": { "version": "2.25.3", @@ -11079,8 +11107,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "27.5.1", @@ -11363,6 +11390,12 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -11563,6 +11596,18 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "nock": { + "version": "13.2.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.2.6.tgz", + "integrity": "sha512-GbyeSwSEP0FYouzETZ0l/XNm5tNcDNcXJKw3LCAb+mx8bZSwg1wEEvdL0FAyg5TkBJYiWSCtw6ag4XfmBy60FA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.21", + "propagate": "^2.0.0" + } + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -11820,6 +11865,12 @@ "sisteransi": "^1.0.5" } }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -12500,8 +12551,7 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true, - "requires": {} + "dev": true }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index a418f70..1d4c213 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "typed-rest-client": "^1.8.5" }, "devDependencies": { - "@types/node": "^17.0.36", "@types/jest": "^27.4.1", + "@types/node": "^17.0.36", "@typescript-eslint/parser": "^5.27.0", "@vercel/ncc": "^0.33.4", "eslint": "^8.16.0", @@ -40,6 +40,7 @@ "jest": "^27.5.1", "jest-circus": "^28.1.0", "js-yaml": "^4.1.0", + "nock": "^13.2.6", "prettier": "2.6.2", "ts-jest": "^27.1.4", "typescript": "^4.7.2" diff --git a/src/download-settings.ts b/src/download-settings.ts index 0fc6377..b9f0f3f 100644 --- a/src/download-settings.ts +++ b/src/download-settings.ts @@ -33,9 +33,4 @@ export interface IReleaseDownloadSettings { * Target path to download the file */ outFilePath: string - - /** - * Github access token to download from private repos (Optional) - */ - token: string } diff --git a/src/download.ts b/src/download.ts deleted file mode 100644 index fe93a01..0000000 --- a/src/download.ts +++ /dev/null @@ -1,252 +0,0 @@ -import * as core from "@actions/core" -import * as fs from "fs" -import * as io from "@actions/io" -import * as path from "path" -import * as thc from "typed-rest-client/HttpClient" - -import {DownloadMetaData, GithubRelease} from "./gh-api" -import {IHeaders, IHttpClientResponse} from "typed-rest-client/Interfaces" - -import {IReleaseDownloadSettings} from "./download-settings" - -const API_ROOT = "https://api.github.com/repos" -const httpClient: thc.HttpClient = new thc.HttpClient("gh-api-client") - -export async function download( - _downloadSettings: IReleaseDownloadSettings -): Promise { - let ghRelease: GithubRelease - - if (_downloadSettings.isLatest) { - ghRelease = await getlatestRelease( - _downloadSettings.sourceRepoPath, - _downloadSettings.token - ) - } else { - ghRelease = await getReleaseByTag( - _downloadSettings.sourceRepoPath, - _downloadSettings.tag, - _downloadSettings.token - ) - } - - const resolvedAssets: DownloadMetaData[] = resolveAssets( - ghRelease, - _downloadSettings - ) - - return await downloadReleaseAssets( - resolvedAssets, - _downloadSettings.outFilePath, - _downloadSettings.token - ) -} - -/** - * Gets the latest release metadata from github api - * @param repoPath The source repository path. {owner}/{repo} - */ -async function getlatestRelease( - repoPath: string, - token: string -): Promise { - core.info(`Fetching latest release for repo ${repoPath}`) - - const headers: IHeaders = {Accept: "application/vnd.github.v3+json"} - if (token !== "") { - headers["Authorization"] = `token ${token}` - } - - const response = await httpClient.get( - `${API_ROOT}/${repoPath}/releases/latest`, - headers - ) - - if (response.message.statusCode !== 200) { - const err: Error = new Error( - `[getlatestRelease] Unexpected response: ${response.message.statusCode}` - ) - throw err - } - - const responseBody = await response.readBody() - const _release: GithubRelease = JSON.parse(responseBody.toString()) - - core.info(`Found latest release version: ${_release.tag_name}`) - return _release -} - -/** - * Gets release data of the specified tag - * @param repoPath The source repository - * @param tag The github tag to fetch release from. - */ -async function getReleaseByTag( - repoPath: string, - tag: string, - token: string -): Promise { - core.info(`Fetching release ${tag} from repo ${repoPath}`) - - if (tag === "") { - throw new Error("Config error: Please input a valid tag") - } - - const headers: IHeaders = {Accept: "application/vnd.github.v3+json"} - if (token !== "") { - headers["Authorization"] = `token ${token}` - } - - const response = await httpClient.get( - `${API_ROOT}/${repoPath}/releases/tags/${tag}`, - headers - ) - - if (response.message.statusCode !== 200) { - const err: Error = new Error( - `[getReleaseByTag] Unexpected response: ${response.message.statusCode}` - ) - throw err - } - - const responseBody = await response.readBody() - const _release: GithubRelease = JSON.parse(responseBody.toString()) - - return _release -} - -function resolveAssets( - _release: GithubRelease, - _settings: IReleaseDownloadSettings -): DownloadMetaData[] { - const downloads: DownloadMetaData[] = [] - - if (_release && _release.assets.length > 0) { - if (_settings.fileName === "*") { - // Download all assets - for (const asset of _release.assets) { - const dData: DownloadMetaData = { - fileName: asset["name"], - url: asset["url"], - isTarBallOrZipBall: false - } - downloads.push(dData) - } - } else { - const asset = _release.assets.find(a => a.name === _settings.fileName) - if (asset) { - const dData: DownloadMetaData = { - fileName: asset["name"], - url: asset["url"], - isTarBallOrZipBall: false - } - downloads.push(dData) - } - } - } - - if (_settings.tarBall) { - const fName = _settings.sourceRepoPath.split("/")[1] - downloads.push({ - fileName: `${fName}-${_release.name}.tar.gz`, - url: _release.tarball_url, - isTarBallOrZipBall: true - }) - } - - if (_settings.zipBall) { - const fName = _settings.sourceRepoPath.split("/")[1] - downloads.push({ - fileName: `${fName}-${_release.name}.zip`, - url: _release.zipball_url, - isTarBallOrZipBall: true - }) - } - - return downloads -} - -/** - * Downloads the specified assets from a given URL - * @param dData The download metadata - * @param out Target directory - * @param token Personal access token to for private repos - */ -async function downloadReleaseAssets( - dData: DownloadMetaData[], - out: string, - token: string -): Promise { - const outFileDir = path.resolve(out) - - if (!fs.existsSync(outFileDir)) { - io.mkdirP(outFileDir) - } - - const downloads: Promise[] = [] - - for (const asset of dData) { - downloads.push(downloadFile(asset, out, token)) - } - - const result = await Promise.all(downloads) - return result -} - -async function downloadFile( - asset: DownloadMetaData, - outputPath: string, - token: string -): Promise { - // Temporary fix for https://github.com/microsoft/typed-rest-client/issues/302 - const ghClient: thc.HttpClient = new thc.HttpClient("gh-api-client", [], { - allowRedirects: false - }) - - const headers: IHeaders = { - Accept: "application/octet-stream" - } - - if (asset.isTarBallOrZipBall) { - headers["Accept"] = "*/*" - } - - if (token !== "") { - headers["Authorization"] = `token ${token}` - } - - core.info(`Downloading file: ${asset.fileName} to: ${outputPath}`) - const response = await ghClient.get(asset.url, headers) - - if (response.message.statusCode === 200) { - return saveFile(outputPath, asset.fileName, response) - } else if (response.message.statusCode === 302) { - delete headers["Authorization"] - const assetLocation = response.message.headers.location as string - const assetResponse = await ghClient.get(assetLocation, headers) - return saveFile(outputPath, asset.fileName, assetResponse) - } else { - const err: Error = new Error( - `Unexpected response: ${response.message.statusCode}` - ) - throw err - } -} - -async function saveFile( - outputPath: string, - fileName: string, - httpClientResponse: IHttpClientResponse -): Promise { - const outFilePath: string = path.resolve(outputPath, fileName) - const fileStream: fs.WriteStream = fs.createWriteStream(outFilePath) - - return new Promise((resolve, reject) => { - fileStream.on("error", err => reject(err)) - const outStream = httpClientResponse.message.pipe(fileStream) - - outStream.on("close", () => { - resolve(outFilePath) - }) - }) -} diff --git a/src/input-helper.ts b/src/input-helper.ts index 6aa3e4b..bd60cbc 100644 --- a/src/input-helper.ts +++ b/src/input-helper.ts @@ -1,5 +1,6 @@ import * as core from "@actions/core" import * as path from "path" + import {IReleaseDownloadSettings} from "./download-settings" export function getInputs(): IReleaseDownloadSettings { @@ -42,7 +43,5 @@ export function getInputs(): IReleaseDownloadSettings { const outFilePath = core.getInput("out-file-path") || "." downloadSettings.outFilePath = path.resolve(githubWorkspacePath, outFilePath) - downloadSettings.token = core.getInput("token") - return downloadSettings } diff --git a/src/main.ts b/src/main.ts index 6fa8187..69494ee 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,26 @@ import * as core from "@actions/core" +import * as handlers from "typed-rest-client/Handlers" import * as inputHelper from "./input-helper" +import * as thc from "typed-rest-client/HttpClient" -import {download} from "./download" +import {ReleaseDownloader} from "./release-downloader" async function run(): Promise { try { const downloadSettings = inputHelper.getInputs() + const authToken = core.getInput("token") - const res: string[] = await download(downloadSettings) + const credentialHandler = new handlers.BearerCredentialHandler( + authToken, + false + ) + const httpClient: thc.HttpClient = new thc.HttpClient("gh-api-client", [ + credentialHandler + ]) + + const downloader = new ReleaseDownloader(httpClient) + + const res: string[] = await downloader.download(downloadSettings) core.info(`Done: ${res}`) } catch (error) { if (error instanceof Error) { diff --git a/src/release-downloader.ts b/src/release-downloader.ts new file mode 100644 index 0000000..22a8b09 --- /dev/null +++ b/src/release-downloader.ts @@ -0,0 +1,230 @@ +import * as core from "@actions/core" +import * as fs from "fs" +import * as io from "@actions/io" +import * as path from "path" +import * as thc from "typed-rest-client/HttpClient" + +import {DownloadMetaData, GithubRelease} from "./gh-api" +import {IHeaders, IHttpClientResponse} from "typed-rest-client/Interfaces" + +import {IReleaseDownloadSettings} from "./download-settings" + +export class ReleaseDownloader { + private httpClient: thc.HttpClient + + private _apiRoot = "https://api.github.com/repos" + + constructor(httpClient: thc.HttpClient) { + this.httpClient = httpClient + } + + async download( + downloadSettings: IReleaseDownloadSettings + ): Promise { + let ghRelease: GithubRelease + + if (downloadSettings.isLatest) { + ghRelease = await this.getlatestRelease(downloadSettings.sourceRepoPath) + } else { + ghRelease = await this.getReleaseByTag( + downloadSettings.sourceRepoPath, + downloadSettings.tag + ) + } + + const resolvedAssets: DownloadMetaData[] = this.resolveAssets( + ghRelease, + downloadSettings + ) + + return await this.downloadReleaseAssets( + resolvedAssets, + downloadSettings.outFilePath + ) + } + + /** + * Gets the latest release metadata from github api + * @param repoPath The source repository path. {owner}/{repo} + */ + private async getlatestRelease(repoPath: string): Promise { + core.info(`Fetching latest release for repo ${repoPath}`) + + const headers: IHeaders = {Accept: "application/vnd.github.v3+json"} + + const response = await this.httpClient.get( + `${this._apiRoot}/${repoPath}/releases/latest`, + headers + ) + + if (response.message.statusCode !== 200) { + const err: Error = new Error( + `[getlatestRelease] Unexpected response: ${response.message.statusCode}` + ) + throw err + } + + const responseBody = await response.readBody() + const release: GithubRelease = JSON.parse(responseBody.toString()) + + core.info(`Found latest release version: ${release.tag_name}`) + return release + } + + /** + * Gets release data of the specified tag + * @param repoPath The source repository + * @param tag The github tag to fetch release from. + */ + private async getReleaseByTag( + repoPath: string, + tag: string + ): Promise { + core.info(`Fetching release ${tag} from repo ${repoPath}`) + + if (tag === "") { + throw new Error("Config error: Please input a valid tag") + } + + const headers: IHeaders = {Accept: "application/vnd.github.v3+json"} + + const response = await this.httpClient.get( + `${this._apiRoot}/${repoPath}/releases/tags/${tag}`, + headers + ) + + if (response.message.statusCode !== 200) { + const err: Error = new Error( + `[getReleaseByTag] Unexpected response: ${response.message.statusCode}` + ) + throw err + } + + const responseBody = await response.readBody() + const release: GithubRelease = JSON.parse(responseBody.toString()) + core.info(`Found release tag: ${release.tag_name}`) + + return release + } + + private resolveAssets( + ghRelease: GithubRelease, + downloadSettings: IReleaseDownloadSettings + ): DownloadMetaData[] { + const downloads: DownloadMetaData[] = [] + + if (ghRelease && ghRelease.assets.length > 0) { + if (downloadSettings.fileName === "*") { + // Download all assets + for (const asset of ghRelease.assets) { + const dData: DownloadMetaData = { + fileName: asset["name"], + url: asset["url"], + isTarBallOrZipBall: false + } + downloads.push(dData) + } + } else { + const asset = ghRelease.assets.find( + a => a.name === downloadSettings.fileName + ) + if (asset) { + const dData: DownloadMetaData = { + fileName: asset["name"], + url: asset["url"], + isTarBallOrZipBall: false + } + downloads.push(dData) + } + } + } + + if (downloadSettings.tarBall) { + const fName = downloadSettings.sourceRepoPath.split("/")[1] + downloads.push({ + fileName: `${fName}-${ghRelease.name}.tar.gz`, + url: ghRelease.tarball_url, + isTarBallOrZipBall: true + }) + } + + if (downloadSettings.zipBall) { + const fName = downloadSettings.sourceRepoPath.split("/")[1] + downloads.push({ + fileName: `${fName}-${ghRelease.name}.zip`, + url: ghRelease.zipball_url, + isTarBallOrZipBall: true + }) + } + + return downloads + } + + /** + * Downloads the specified assets from a given URL + * @param dData The download metadata + * @param out Target directory + */ + private async downloadReleaseAssets( + dData: DownloadMetaData[], + out: string + ): Promise { + const outFileDir = path.resolve(out) + + if (!fs.existsSync(outFileDir)) { + io.mkdirP(outFileDir) + } + + const downloads: Promise[] = [] + + for (const asset of dData) { + downloads.push(this.downloadFile(asset, out)) + } + + const result = await Promise.all(downloads) + return result + } + + private async downloadFile( + asset: DownloadMetaData, + outputPath: string + ): Promise { + const headers: IHeaders = { + Accept: "application/octet-stream" + } + + if (asset.isTarBallOrZipBall) { + headers["Accept"] = "*/*" + } + + core.info(`Downloading file: ${asset.fileName} to: ${outputPath}`) + const response = await this.httpClient.get(asset.url, headers) + + if (response.message.statusCode === 200) { + return this.saveFile(outputPath, asset.fileName, response) + } else { + const err: Error = new Error( + `Unexpected response: ${response.message.statusCode}` + ) + throw err + } + } + + private async saveFile( + outputPath: string, + fileName: string, + httpClientResponse: IHttpClientResponse + ): Promise { + const outFilePath: string = path.resolve(outputPath, fileName) + const fileStream: fs.WriteStream = fs.createWriteStream(outFilePath) + + return new Promise((resolve, reject) => { + fileStream.on("error", err => reject(err)) + const outStream = httpClientResponse.message.pipe(fileStream) + + outStream.on("close", () => { + resolve(outFilePath) + }) + }) + } +}