Add support for downloading multiple files

Download asset from any tag/version
Adapt unit test
This commit is contained in:
Robin Raju committed 2020-07-27 01:47:32 +02:00
1 parent 651dd2a2d0
commit b7b676bd2f
11 files changed
+299 -75

No files matched your search

+7 -1
View File
@@ -20,7 +20,13 @@ jobs:
npm run all
- uses: ./
with:
repo-path: "eloots/course-management-tools"
repository: "lihaoyi/Ammonite"
latest: false
tag: "2.1.1"
fileName: "2.13-2.1.1-14-4f2a1b2-bootstrap"
tarBall: true
zipBall: true
out-file-path: "downloads"
token: ""
- name: List working directory
run: ls -lrth downloads
+6 -2
View File
@@ -3,8 +3,12 @@ import {IReleaseDownloadSettings} from "../src/download-settings"
test("run download", async () => {
const downloadSettings: IReleaseDownloadSettings = {
sourceRepoPath: "eloots/course-management-tools",
isLatest: true,
sourceRepoPath: "lihaoyi/Ammonite",
isLatest: false,
tag: "2.1.1",
fileName: "2.13-2.1.1-14-4f2a1b2-bootstrap",
tarBall: true,
zipBall: true,
outFilePath: "/tmp/release-downloader"
}
await download(downloadSettings)
+19 -3
View File
@@ -2,16 +2,32 @@ name: "release-downloader"
description: "Download release assets from private or public github repositories"
author: "Robin Raju"
inputs:
repo-path:
repository:
description: "The source repository path. Expected format {owner}/{repo}"
default: ""
required: true
latest:
description: "A flag to choose between latest release and remaining releases"
default: "true"
default: "false"
required: false
tag:
description: "The github tag to download the release from"
default: ""
required: false
fileName:
description: "Name of the file to download"
default: ""
required: false
tarBall:
description: "Download tarball from assets"
default: "false"
required: false
zipBall:
description: "Download zipball from assets"
default: "false"
required: false
out-file-path:
description: "Relative path under $GITHUB_WORKSPACE to place the downloaded file"
description: "Relative path under $GITHUB_WORKSPACE to place the downloaded files"
default: "."
required: true
token:
+83 -16
View File
@@ -2303,13 +2303,22 @@ function getInputs() {
throw new Error("$GITHUB_WORKSPACE not defined");
}
githubWorkspacePath = path.resolve(githubWorkspacePath);
const repositoryPath = core.getInput("repo-path");
const split = repositoryPath.split("/");
if (split.length !== 2 || !split[0] || !split[1]) {
const repositoryPath = core.getInput("repository");
const repo = repositoryPath.split("/");
if (repo.length !== 2 || !repo[0] || !repo[1]) {
throw new Error(`Invalid repository '${repositoryPath}'. Expected format {owner}/{repo}.`);
}
downloadSettings.sourceRepoPath = repositoryPath;
downloadSettings.isLatest = core.getInput("latest") === "true";
const latestFlag = core.getInput("latest") === "true";
const ghTag = core.getInput("tag");
if (latestFlag && ghTag.length > 0) {
throw new Error(`Invalid inputs. latest=${latestFlag} and tag=${ghTag} can't coexist`);
}
downloadSettings.isLatest = latestFlag;
downloadSettings.tag = ghTag;
downloadSettings.fileName = core.getInput("fileName");
downloadSettings.tarBall = core.getInput("tarBall") === "true";
downloadSettings.zipBall = core.getInput("zipBall") === "true";
const outFilePath = core.getInput("out-file-path") || ".";
downloadSettings.outFilePath = path.resolve(githubWorkspacePath, outFilePath);
downloadSettings.token = core.getInput("token");
@@ -2371,8 +2380,15 @@ const API_ROOT = "https://api.github.com/repos";
const httpClient = new thc.HttpClient("gh-api-client");
function download(_downloadSettings) {
return __awaiter(this, void 0, void 0, function* () {
const latestRelease = yield getlatestRelease(_downloadSettings.sourceRepoPath);
return yield downloadFile(latestRelease, _downloadSettings.outFilePath);
let ghRelease;
if (_downloadSettings.isLatest) {
ghRelease = yield getlatestRelease(_downloadSettings.sourceRepoPath);
}
else {
ghRelease = yield getReleaseByTag(_downloadSettings.sourceRepoPath, _downloadSettings.tag);
}
const resolvedAssets = resolveAssets(ghRelease, _downloadSettings);
return yield downloadReleaseAssets(resolvedAssets, _downloadSettings.outFilePath);
});
}
exports.download = download;
@@ -2382,31 +2398,82 @@ exports.download = download;
*/
function getlatestRelease(repoPath) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield httpClient.get(`${API_ROOT}/${repoPath}/releases/latest`);
core.info(`Fetching latest relase for repo ${repoPath}`);
const response = yield httpClient.get(`${API_ROOT}/${repoPath}/releases/latest`);
const responseBody = yield response.readBody();
const _release = JSON.parse(responseBody.toString());
if (_release && _release["assets"].length > 0) {
const asset = _release["assets"][0];
return [asset["name"], asset["browser_download_url"]];
}
return ["", ""];
return _release;
});
}
/**
* Gets release data of the specified tag
* @param repoPath The source repository
* @param tag The github tag to fetch release from.
*/
function getReleaseByTag(repoPath, tag) {
return __awaiter(this, void 0, void 0, function* () {
core.info(`Fetching relase ${tag} from repo ${repoPath}`);
if (tag === "") {
throw new Error("Config error: Please input a valid tag");
}
const response = yield httpClient.get(`${API_ROOT}/${repoPath}/releases/tags/${tag}`);
const responseBody = yield response.readBody();
const _release = JSON.parse(responseBody.toString());
return _release;
});
}
function resolveAssets(_release, _settings) {
const downloads = [];
if (_release && _release.assets.length > 0) {
const asset = _release.assets.find(a => a.name === _settings.fileName);
if (asset) {
const dData = {
fileName: asset["name"],
url: asset["browser_download_url"]
};
downloads.push(dData);
}
}
if (_settings.tarBall) {
const fName = _settings.sourceRepoPath.split("/")[1];
downloads.push({
fileName: `${fName}-${_release.name}.tar.gz`,
url: _release.tarball_url
});
}
if (_settings.zipBall) {
const fName = _settings.sourceRepoPath.split("/")[1];
downloads.push({
fileName: `${fName}-${_release.name}.zip`,
url: _release.zipball_url
});
}
return downloads;
}
/**
* Downloads the specified file from a given URL
* @param [fileName, downloadUrl] The filename and url
* @param out Target directory
*/
function downloadFile([fileName, downloadUrl], out) {
function downloadReleaseAssets(dData, out) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield httpClient.get(downloadUrl);
const outFileDir = path.resolve(out);
if (!fs.existsSync(outFileDir)) {
io.mkdirP(outFileDir);
}
core.info(`Downloading file: ${fileName} to: ${outFileDir}`);
const outFilePath = path.resolve(outFileDir, fileName);
const downloads = [];
for (const asset of dData) {
downloads.push(downloadFile(asset.fileName, asset.url, out));
}
const result = yield Promise.all(downloads);
return result;
});
}
function downloadFile(fileName, url, outputPath) {
return __awaiter(this, void 0, void 0, function* () {
const response = yield httpClient.get(url);
core.info(`Downloading file: ${fileName} to: ${outputPath}`);
const outFilePath = path.resolve(outputPath, fileName);
const fileStream = fs.createWriteStream(outFilePath);
if (response.message.statusCode !== 200) {
const err = new Error(`Unexpected response: ${response.message.statusCode}`);
+21 -31
View File
@@ -1,6 +1,6 @@
{
"name": "release-downloader",
"version": "0.1.0",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1670,7 +1670,8 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"base": {
"version": "0.11.2",
@@ -1750,6 +1751,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2014,7 +2016,8 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"contains-path": {
"version": "0.1.0",
@@ -3306,7 +3309,8 @@
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"fsevents": {
"version": "1.2.13",
@@ -3377,6 +3381,7 @@
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -3704,6 +3709,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -3712,7 +3718,8 @@
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"inquirer": {
"version": "6.5.2",
@@ -3757,11 +3764,6 @@
"side-channel": "^1.0.2"
}
},
"interpret": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
"integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
@@ -5247,6 +5249,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5569,6 +5572,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
@@ -5716,7 +5720,8 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"path-is-inside": {
"version": "1.0.2",
@@ -5733,7 +5738,8 @@
"path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"path-type": {
"version": "4.0.0",
@@ -6011,14 +6017,6 @@
"util.promisify": "^1.0.0"
}
},
"rechoir": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz",
"integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=",
"requires": {
"resolve": "^1.1.6"
}
},
"regenerator-runtime": {
"version": "0.13.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
@@ -6133,6 +6131,7 @@
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==",
"dev": true,
"requires": {
"path-parse": "^1.0.6"
}
@@ -6423,16 +6422,6 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
"dev": true
},
"shelljs": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
"integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
"requires": {
"glob": "^7.0.0",
"interpret": "^1.0.0",
"rechoir": "^0.6.2"
}
},
"shellwords": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
@@ -7524,7 +7513,8 @@
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"write": {
"version": "1.0.3",
-1
View File
@@ -29,7 +29,6 @@
"dependencies": {
"@actions/core": "^1.2.0",
"@actions/io": "^1.0.2",
"shelljs": "^0.8.4",
"typed-rest-client": "^1.7.3"
},
"devDependencies": {
+20
View File
@@ -9,6 +9,26 @@ export interface IReleaseDownloadSettings {
*/
isLatest: boolean
/**
* The release tag
*/
tag: string
/**
* Name of the file to download
*/
fileName: string
/**
* Download ttarball from release
*/
tarBall: boolean
/**
* Download zipball from release
*/
zipBall: boolean
/**
* Target path to download the file
*/
+105 -16
View File
@@ -4,37 +4,111 @@ import * as core from "@actions/core"
import * as io from "@actions/io"
import * as thc from "typed-rest-client/HttpClient"
import {IReleaseDownloadSettings} from "./download-settings"
import {GithubRelease, DownloadMetaData} from "./gh-api"
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> {
const latestRelease = await getlatestRelease(_downloadSettings.sourceRepoPath)
return await downloadFile(latestRelease, _downloadSettings.outFilePath)
): Promise<string[]> {
let ghRelease: GithubRelease
if (_downloadSettings.isLatest) {
ghRelease = await getlatestRelease(_downloadSettings.sourceRepoPath)
} else {
ghRelease = await getReleaseByTag(
_downloadSettings.sourceRepoPath,
_downloadSettings.tag
)
}
const resolvedAssets: DownloadMetaData[] = resolveAssets(
ghRelease,
_downloadSettings
)
return await downloadReleaseAssets(
resolvedAssets,
_downloadSettings.outFilePath
)
}
/**
* Gets the latest release metadata from github api
* @param repoPath The source repository path. {owner}/{repo}
*/
async function getlatestRelease(repoPath: string): Promise<[string, string]> {
async function getlatestRelease(repoPath: string): Promise<GithubRelease> {
core.info(`Fetching latest relase for repo ${repoPath}`)
const response = await httpClient.get(
`${API_ROOT}/${repoPath}/releases/latest`
)
core.info(`Fetching latest relase for repo ${repoPath}`)
const responseBody = await response.readBody()
const _release: GithubRelease = JSON.parse(responseBody.toString())
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
): Promise<GithubRelease> {
core.info(`Fetching relase ${tag} from repo ${repoPath}`)
if (tag === "") {
throw new Error("Config error: Please input a valid tag")
}
const response = await httpClient.get(
`${API_ROOT}/${repoPath}/releases/tags/${tag}`
)
const responseBody = await response.readBody()
const _release = JSON.parse(responseBody.toString())
const _release: GithubRelease = JSON.parse(responseBody.toString())
if (_release && _release["assets"].length > 0) {
const asset = _release["assets"][0]
return [asset["name"], asset["browser_download_url"]]
return _release
}
function resolveAssets(
_release: GithubRelease,
_settings: IReleaseDownloadSettings
): DownloadMetaData[] {
const downloads: DownloadMetaData[] = []
if (_release && _release.assets.length > 0) {
const asset = _release.assets.find(a => a.name === _settings.fileName)
if (asset) {
const dData: DownloadMetaData = {
fileName: asset["name"],
url: asset["browser_download_url"]
}
downloads.push(dData)
}
}
return ["", ""]
if (_settings.tarBall) {
const fName = _settings.sourceRepoPath.split("/")[1]
downloads.push({
fileName: `${fName}-${_release.name}.tar.gz`,
url: _release.tarball_url
})
}
if (_settings.zipBall) {
const fName = _settings.sourceRepoPath.split("/")[1]
downloads.push({
fileName: `${fName}-${_release.name}.zip`,
url: _release.zipball_url
})
}
return downloads
}
/**
@@ -42,19 +116,34 @@ async function getlatestRelease(repoPath: string): Promise<[string, string]> {
* @param [fileName, downloadUrl] The filename and url
* @param out Target directory
*/
async function downloadFile(
[fileName, downloadUrl]: [string, string],
async function downloadReleaseAssets(
dData: DownloadMetaData[],
out: string
): Promise<string> {
const response = await httpClient.get(downloadUrl)
): Promise<string[]> {
const outFileDir = path.resolve(out)
if (!fs.existsSync(outFileDir)) {
io.mkdirP(outFileDir)
}
core.info(`Downloading file: ${fileName} to: ${outFileDir}`)
const outFilePath: string = path.resolve(outFileDir, fileName)
const downloads: Promise<string>[] = []
for (const asset of dData) {
downloads.push(downloadFile(asset.fileName, asset.url, out))
}
const result = await Promise.all(downloads)
return result
}
async function downloadFile(
fileName: string,
url: string,
outputPath: string
): Promise<string> {
const response = await httpClient.get(url)
core.info(`Downloading file: ${fileName} to: ${outputPath}`)
const outFilePath: string = path.resolve(outputPath, fileName)
const fileStream: NodeJS.WritableStream = fs.createWriteStream(outFilePath)
+16
View File
@@ -0,0 +1,16 @@
interface GhAsset {
name: string
browser_download_url: string
}
export interface GithubRelease {
name: string
assets: GhAsset[]
tarball_url: string
zipball_url: string
}
export interface DownloadMetaData {
fileName: string
url: string
}
+21 -4
View File
@@ -11,16 +11,33 @@ export function getInputs(): IReleaseDownloadSettings {
}
githubWorkspacePath = path.resolve(githubWorkspacePath)
const repositoryPath = core.getInput("repo-path")
const split = repositoryPath.split("/")
if (split.length !== 2 || !split[0] || !split[1]) {
const repositoryPath = core.getInput("repository")
const repo = repositoryPath.split("/")
if (repo.length !== 2 || !repo[0] || !repo[1]) {
throw new Error(
`Invalid repository '${repositoryPath}'. Expected format {owner}/{repo}.`
)
}
downloadSettings.sourceRepoPath = repositoryPath
downloadSettings.isLatest = core.getInput("latest") === "true"
const latestFlag = core.getInput("latest") === "true"
const ghTag = core.getInput("tag")
if (latestFlag && ghTag.length > 0) {
throw new Error(
`Invalid inputs. latest=${latestFlag} and tag=${ghTag} can't coexist`
)
}
downloadSettings.isLatest = latestFlag
downloadSettings.tag = ghTag
downloadSettings.fileName = core.getInput("fileName")
downloadSettings.tarBall = core.getInput("tarBall") === "true"
downloadSettings.zipBall = core.getInput("zipBall") === "true"
const outFilePath = core.getInput("out-file-path") || "."
downloadSettings.outFilePath = path.resolve(githubWorkspacePath, outFilePath)
+1 -1
View File
@@ -6,7 +6,7 @@ async function run(): Promise<void> {
try {
const downloadSettings = inputHelper.getInputs()
const res = await download(downloadSettings)
const res: string[] = await download(downloadSettings)
core.info(`Done: ${res}`)
} catch (error) {
core.setFailed(error.message)