From e9e49e9bbce8ff2b901957ee034714cab099644a Mon Sep 17 00:00:00 2001 From: Rafael Abreu Date: Thu, 16 Feb 2023 18:55:01 +0000 Subject: [PATCH] feat: add support for multi artifacts --- .github/workflows/check-dist.yml | 2 +- .github/workflows/test.yml | 57 ++++++++++-- dist/index.js | 118 ++++++++++++++++++------- src/constants.ts | 6 +- src/download-artifact.ts | 147 ++++++++++++++++++++++--------- 5 files changed, 245 insertions(+), 85 deletions(-) diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml index 3a2344f..166692e 100644 --- a/.github/workflows/check-dist.yml +++ b/.github/workflows/check-dist.yml @@ -4,7 +4,7 @@ # For our project, we generate this file through a build process # from other source files. # We need to make sure the checked-in `index.js` actually matches what we expect it to be. -name: Check dist/ +name: Check dist on: push: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47cc4da..d04c26e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,7 +40,7 @@ jobs: run: npm run lint - name: Format - run: npm run format-check + run: npm run format-check # Test end-to-end by uploading two artifacts and then downloading them # Once upload-artifact v2 is out of preview, switch over @@ -50,7 +50,7 @@ jobs: mkdir -p path/to/artifact-B echo "Lorem ipsum dolor sit amet" > path/to/artifact-A/file-A.txt echo "Hello world from file B" > path/to/artifact-B/file-B.txt - + - name: Upload artifact A uses: actions/upload-artifact@v1 with: @@ -91,7 +91,56 @@ jobs: } shell: pwsh - # Test downloading both artifacts at once + # Test downloading multiple artifacts to the same path + - name: Download artifacts A and B to the same path + uses: ./ + with: + name: | + Artifact-A + Artifact-B + path: some/path/for/multiple/files + + - name: Verify successful download + run: | + $fileA = "some/path/for/multiple/files/file-A.txt" + $fileB = "some/path/for/multiple/files/file-B.txt" + if(!(Test-Path -path $fileA) -or !(Test-Path -path $fileB)) + { + Write-Error "Expected files do not exist" + } + if(!((Get-Content $fileA) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $fileB) -ceq "Hello world from file B")) + { + Write-Error "File contents of downloaded artifacts are incorrect" + } + shell: pwsh + + # Test downloading multiple artifacts to different paths + - name: Download artifacts A and B to different paths + uses: ./ + with: + name: | + Artifact-A + Artifact-B + path: | + some/path/for/a + some/path/for/b + + + - name: Verify successful download + run: | + $fileA = "some/path/for/a/file-A.txt" + $fileB = "some/path/for/b/file-B.txt" + if(!(Test-Path -path $fileA) -or !(Test-Path -path $fileB)) + { + Write-Error "Expected files do not exist" + } + if(!((Get-Content $fileA) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $fileB) -ceq "Hello world from file B")) + { + Write-Error "File contents of downloaded artifacts are incorrect" + } + shell: pwsh + + # Test downloading all artifacts - name: Download all Artifacts uses: ./ with: @@ -110,5 +159,3 @@ jobs: Write-Error "File contents of downloaded artifacts are incorrect" } shell: pwsh - - \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 80b6280..968d9e7 100644 --- a/dist/index.js +++ b/dist/index.js @@ -9496,12 +9496,12 @@ function wrappy (fn, cb) { Object.defineProperty(exports, "__esModule", ({ value: true })); var Inputs; (function (Inputs) { - Inputs["Name"] = "name"; - Inputs["Path"] = "path"; + Inputs["Names"] = "name"; + Inputs["Paths"] = "path"; })(Inputs = exports.Inputs || (exports.Inputs = {})); var Outputs; (function (Outputs) { - Outputs["DownloadPath"] = "download-path"; + Outputs["DownloadPaths"] = "download-path"; })(Outputs = exports.Outputs || (exports.Outputs = {})); @@ -9534,44 +9534,94 @@ const artifact = __importStar(__nccwpck_require__(2605)); const os = __importStar(__nccwpck_require__(2037)); const path_1 = __nccwpck_require__(1017); const constants_1 = __nccwpck_require__(9042); +function downloadArtifact(name, path) { + return __awaiter(this, void 0, void 0, function* () { + let resolvedPath; + // resolve tilde expansions, path.replace only replaces the first occurrence of a pattern + if (path.startsWith(`~`)) { + resolvedPath = path_1.resolve(path.replace('~', os.homedir())); + } + else { + resolvedPath = path_1.resolve(path); + } + core.debug(`Resolved path is ${resolvedPath}`); + const artifactClient = artifact.create(); + if (!name) { + // download all artifacts + core.info('No artifact name specified, downloading all artifacts'); + core.info('Creating an extra directory for each artifact that is being downloaded'); + const downloadResponse = yield artifactClient.downloadAllArtifacts(resolvedPath); + core.info(`There were ${downloadResponse.length} artifacts downloaded`); + for (const artifact of downloadResponse) { + core.info(`Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}`); + } + } + else { + // download a single artifact + core.info(`Starting download for ${name}`); + const downloadOptions = { + createArtifactFolder: false + }; + const downloadResponse = yield artifactClient.downloadArtifact(name, resolvedPath, downloadOptions); + core.info(`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`); + } + // output the directory that the artifact(s) was/were downloaded to + // if no path is provided, an empty string resolves to the current working directory + core.info('Artifact download has finished successfully'); + return resolvedPath; + }); +} function run() { return __awaiter(this, void 0, void 0, function* () { try { - const name = core.getInput(constants_1.Inputs.Name, { required: false }); - const path = core.getInput(constants_1.Inputs.Path, { required: false }); - let resolvedPath; - // resolve tilde expansions, path.replace only replaces the first occurrence of a pattern - if (path.startsWith(`~`)) { - resolvedPath = path_1.resolve(path.replace('~', os.homedir())); + const names = core.getMultilineInput(constants_1.Inputs.Names, { required: false }); + const paths = core.getMultilineInput(constants_1.Inputs.Paths, { required: false }); + core.info(`names: '${JSON.stringify(names)}' | length: ${names.length}`); + core.info(`paths: '${JSON.stringify(paths)}' | length: ${paths.length}`); + let downloadPaths = []; + // Names is set and has fewer entries than Paths + if (names.length !== 0 && paths.length > names.length) { + throw Error(`The input 'path' cannot have more entries than 'name', if 'name' is set.`); } + // Names is NOT set and Paths has more than 1 entry + else if (names.length === 0 && paths.length > 1) { + throw Error(`The input 'path' cannot have more than one entry, if 'name' is not set.`); + } + // Names is NOT set and path has at max 1 entry: download all artifacts + else if (names.length === 0 && paths.length <= 1) { + const name = names.toString(); // '' + const path = paths.toString(); // '' or 'some/path' + const downloadPath = yield downloadArtifact(name, path); + downloadPaths.push(downloadPath); + } + // Names has one or more entries and Paths has at max 1 entry + else if (names.length >= 1 && paths.length <= 1) { + const path = paths.toString(); // '' or 'some/path' + names.forEach((name) => __awaiter(this, void 0, void 0, function* () { + const downloadPath = yield downloadArtifact(name, path); + downloadPaths.push(downloadPath); + })); + } + // Names and Paths have the same numbers of entries (more than 1) + else if (names.length > 1 && + paths.length > 1 && + names.length === paths.length) { + names.forEach((name, index) => __awaiter(this, void 0, void 0, function* () { + const path = paths[index]; + const downloadPath = yield downloadArtifact(name, path); + downloadPaths.push(downloadPath); + })); + } + // Unhandled exception else { - resolvedPath = path_1.resolve(path); - } - core.debug(`Resolved path is ${resolvedPath}`); - const artifactClient = artifact.create(); - if (!name) { - // download all artifacts - core.info('No artifact name specified, downloading all artifacts'); - core.info('Creating an extra directory for each artifact that is being downloaded'); - const downloadResponse = yield artifactClient.downloadAllArtifacts(resolvedPath); - core.info(`There were ${downloadResponse.length} artifacts downloaded`); - for (const artifact of downloadResponse) { - core.info(`Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}`); - } - } - else { - // download a single artifact - core.info(`Starting download for ${name}`); - const downloadOptions = { - createArtifactFolder: false - }; - const downloadResponse = yield artifactClient.downloadArtifact(name, resolvedPath, downloadOptions); - core.info(`Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}`); + throw Error(`Unhandled scenario. This shouldn't happen. It's very likely a bug. :-()`); } + // Remove duplicates and empty strings + downloadPaths = [...new Set(downloadPaths.filter(path => path !== ''))]; + // Returns a newline-separated list of paths + const output = downloadPaths.join('\n'); // output the directory that the artifact(s) was/were downloaded to - // if no path is provided, an empty string resolves to the current working directory - core.setOutput(constants_1.Outputs.DownloadPath, resolvedPath); - core.info('Artifact download has finished successfully'); + core.setOutput(constants_1.Outputs.DownloadPaths, output); } catch (err) { core.setFailed(err.message); diff --git a/src/constants.ts b/src/constants.ts index 49d800a..94a7997 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,7 @@ export enum Inputs { - Name = 'name', - Path = 'path' + Names = 'name', + Paths = 'path' } export enum Outputs { - DownloadPath = 'download-path' + DownloadPaths = 'download-path' } diff --git a/src/download-artifact.ts b/src/download-artifact.ts index b32a7be..d743ab6 100644 --- a/src/download-artifact.ts +++ b/src/download-artifact.ts @@ -4,55 +4,118 @@ import * as os from 'os' import {resolve} from 'path' import {Inputs, Outputs} from './constants' +async function downloadArtifact(name: string, path: string): Promise { + let resolvedPath + // resolve tilde expansions, path.replace only replaces the first occurrence of a pattern + if (path.startsWith(`~`)) { + resolvedPath = resolve(path.replace('~', os.homedir())) + } else { + resolvedPath = resolve(path) + } + core.debug(`Resolved path is ${resolvedPath}`) + + const artifactClient = artifact.create() + if (!name) { + // download all artifacts + core.info('No artifact name specified, downloading all artifacts') + core.info( + 'Creating an extra directory for each artifact that is being downloaded' + ) + const downloadResponse = await artifactClient.downloadAllArtifacts( + resolvedPath + ) + core.info(`There were ${downloadResponse.length} artifacts downloaded`) + for (const artifact of downloadResponse) { + core.info( + `Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}` + ) + } + } else { + // download a single artifact + core.info(`Starting download for ${name}`) + const downloadOptions = { + createArtifactFolder: false + } + const downloadResponse = await artifactClient.downloadArtifact( + name, + resolvedPath, + downloadOptions + ) + core.info( + `Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}` + ) + } + // output the directory that the artifact(s) was/were downloaded to + // if no path is provided, an empty string resolves to the current working directory + core.info('Artifact download has finished successfully') + + return resolvedPath +} + async function run(): Promise { try { - const name = core.getInput(Inputs.Name, {required: false}) - const path = core.getInput(Inputs.Path, {required: false}) + const names = core.getMultilineInput(Inputs.Names, {required: false}) + const paths = core.getMultilineInput(Inputs.Paths, {required: false}) - let resolvedPath - // resolve tilde expansions, path.replace only replaces the first occurrence of a pattern - if (path.startsWith(`~`)) { - resolvedPath = resolve(path.replace('~', os.homedir())) - } else { - resolvedPath = resolve(path) - } - core.debug(`Resolved path is ${resolvedPath}`) + core.info(`names: '${JSON.stringify(names)}' | length: ${names.length}`) + core.info(`paths: '${JSON.stringify(paths)}' | length: ${paths.length}`) - const artifactClient = artifact.create() - if (!name) { - // download all artifacts - core.info('No artifact name specified, downloading all artifacts') - core.info( - 'Creating an extra directory for each artifact that is being downloaded' - ) - const downloadResponse = await artifactClient.downloadAllArtifacts( - resolvedPath - ) - core.info(`There were ${downloadResponse.length} artifacts downloaded`) - for (const artifact of downloadResponse) { - core.info( - `Artifact ${artifact.artifactName} was downloaded to ${artifact.downloadPath}` - ) - } - } else { - // download a single artifact - core.info(`Starting download for ${name}`) - const downloadOptions = { - createArtifactFolder: false - } - const downloadResponse = await artifactClient.downloadArtifact( - name, - resolvedPath, - downloadOptions - ) - core.info( - `Artifact ${downloadResponse.artifactName} was downloaded to ${downloadResponse.downloadPath}` + let downloadPaths: string[] = [] + + // Names is set and has fewer entries than Paths + if (names.length !== 0 && paths.length > names.length) { + throw Error( + `The input 'path' cannot have more entries than 'name', if 'name' is set.` ) } + // Names is NOT set and Paths has more than 1 entry + else if (names.length === 0 && paths.length > 1) { + throw Error( + `The input 'path' cannot have more than one entry, if 'name' is not set.` + ) + } + // Names is NOT set and path has at max 1 entry: download all artifacts + else if (names.length === 0 && paths.length <= 1) { + const name = names.toString() // '' + const path = paths.toString() // '' or 'some/path' + const downloadPath = await downloadArtifact(name, path) + downloadPaths.push(downloadPath) + } + // Names has one or more entries and Paths has at max 1 entry + else if (names.length >= 1 && paths.length <= 1) { + const path = paths.toString() // '' or 'some/path' + names.forEach(async name => { + const downloadPath = await downloadArtifact(name, path) + downloadPaths.push(downloadPath) + }) + } + // Names and Paths have the same numbers of entries (more than 1) + else if ( + names.length > 1 && + paths.length > 1 && + names.length === paths.length + ) { + names.forEach(async (name, index) => { + const path = paths[index] + const downloadPath = await downloadArtifact(name, path) + downloadPaths.push(downloadPath) + }) + } + // Unhandled exception + else { + throw Error( + `Unhandled scenario. This shouldn't happen. It's very likely a bug. :-()` + ) + } + + // Remove duplicates and empty strings + downloadPaths = [...new Set(downloadPaths.filter(path => path !== ''))] + + // Returns a newline-separated list of paths + const output = downloadPaths.join('\n') + // output the directory that the artifact(s) was/were downloaded to - // if no path is provided, an empty string resolves to the current working directory - core.setOutput(Outputs.DownloadPath, resolvedPath) - core.info('Artifact download has finished successfully') + core.setOutput(Outputs.DownloadPaths, output) } catch (err) { core.setFailed(err.message) }