Refactor downloader (#448)

* Refactor: Move downloader to a class

* Use BearerCredentialHandler from typed-rest-client for authentication
Add http response mock to unit tests
This commit is contained in:
Robin Raju authored and GitHub committed 2022-06-06 21:27:38 +02:00
1 parent 6785de63f4
commit e0d223890b
11 files changed
+399 -285

No files matched your search

+53 -12
View File
@@ -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)
+36
View File
@@ -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": ""
}
+1
View File
@@ -0,0 +1 @@
v1.0 robinraju/probable-potato/releases/latest
+1 -1
View File
@@ -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"
+60 -10
View File
@@ -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",
+2 -1
View File
@@ -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"
-5
View File
@@ -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
}
-252
View File
@@ -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<string[]> {
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<GithubRelease> {
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<GithubRelease> {
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<string[]> {
const outFileDir = path.resolve(out)
if (!fs.existsSync(outFileDir)) {
io.mkdirP(outFileDir)
}
const downloads: Promise<string>[] = []
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<string> {
// 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<string> {
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)
})
})
}
+1 -2
View File
@@ -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
}
+15 -2
View File
@@ -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<void> {
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) {
+230
View File
@@ -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<string[]> {
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<GithubRelease> {
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<GithubRelease> {
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<string[]> {
const outFileDir = path.resolve(out)
if (!fs.existsSync(outFileDir)) {
io.mkdirP(outFileDir)
}
const downloads: Promise<string>[] = []
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<string> {
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<string> {
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)
})
})
}
}