add merge artifact sub-action

This commit is contained in:
Rob Herley
2024-01-22 21:46:12 -05:00
parent 52899c8c02
commit 997fffa355
11 changed files with 1100 additions and 61 deletions

9
src/merge/constants.ts Normal file
View File

@ -0,0 +1,9 @@
/* eslint-disable no-unused-vars */
export enum Inputs {
Name = 'name',
Pattern = 'pattern',
SeparateDirectories = 'separate-directories',
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
DeleteMerged = 'delete-merged'
}

6
src/merge/index.ts Normal file
View File

@ -0,0 +1,6 @@
import * as core from '@actions/core'
import {run} from './merge-artifacts'
run().catch(error => {
core.setFailed((error as Error).message)
})

44
src/merge/input-helper.ts Normal file
View File

@ -0,0 +1,44 @@
import * as core from '@actions/core'
import {Inputs} from './constants'
import {MergeInputs} from './merge-inputs'
/**
* Helper to get all the inputs for the action
*/
export function getInputs(): MergeInputs {
const name = core.getInput(Inputs.Name, {required: true})
const pattern = core.getInput(Inputs.Pattern, {required: true})
const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories)
const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged)
const inputs = {
name,
pattern,
separateDirectories,
deleteMerged,
retentionDays: 0,
compressionLevel: 6
} as MergeInputs
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
if (retentionDaysStr) {
inputs.retentionDays = parseInt(retentionDaysStr)
if (isNaN(inputs.retentionDays)) {
core.setFailed('Invalid retention-days')
}
}
const compressionLevelStr = core.getInput(Inputs.CompressionLevel)
if (compressionLevelStr) {
inputs.compressionLevel = parseInt(compressionLevelStr)
if (isNaN(inputs.compressionLevel)) {
core.setFailed('Invalid compression-level')
}
if (inputs.compressionLevel < 0 || inputs.compressionLevel > 9) {
core.setFailed('Invalid compression-level. Valid values are 0-9')
}
}
return inputs
}

View File

@ -0,0 +1,93 @@
import * as path from 'path'
import {mkdtemp, rm} from 'fs/promises'
import * as core from '@actions/core'
import {Minimatch} from 'minimatch'
import artifactClient, {UploadArtifactOptions} from '@actions/artifact'
import {getInputs} from './input-helper'
import {uploadArtifact} from '../shared/upload-artifact'
import {findFilesToUpload} from '../shared/search'
const PARALLEL_DOWNLOADS = 5
export const chunk = <T>(arr: T[], n: number): T[][] =>
arr.reduce((acc, cur, i) => {
const index = Math.floor(i / n)
acc[index] = [...(acc[index] || []), cur]
return acc
}, [] as T[][])
export async function run(): Promise<void> {
const inputs = getInputs()
const tmpDir = await mkdtemp('merge-artifact')
const listArtifactResponse = await artifactClient.listArtifacts({
latest: true
})
const matcher = new Minimatch(inputs.pattern)
const artifacts = listArtifactResponse.artifacts.filter(artifact =>
matcher.match(artifact.name)
)
core.debug(
`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`
)
if (artifacts.length === 0) {
throw new Error(`No artifacts found matching pattern '${inputs.pattern}'`)
}
core.info(`Preparing to download the following artifacts:`)
artifacts.forEach(artifact => {
core.info(`- ${artifact.name} (ID: ${artifact.id}, Size: ${artifact.size})`)
})
const downloadPromises = artifacts.map(artifact =>
artifactClient.downloadArtifact(artifact.id, {
path: inputs.separateDirectories
? path.join(tmpDir, artifact.name)
: tmpDir
})
)
const chunkedPromises = chunk(downloadPromises, PARALLEL_DOWNLOADS)
for (const chunk of chunkedPromises) {
await Promise.all(chunk)
}
const options: UploadArtifactOptions = {}
if (inputs.retentionDays) {
options.retentionDays = inputs.retentionDays
}
if (typeof inputs.compressionLevel !== 'undefined') {
options.compressionLevel = inputs.compressionLevel
}
const searchResult = await findFilesToUpload(tmpDir)
await uploadArtifact(
inputs.name,
searchResult.filesToUpload,
searchResult.rootDirectory,
options
)
core.info(
`The ${artifacts.length} artifact(s) have been successfully merged!`
)
if (inputs.deleteMerged) {
const deletePromises = artifacts.map(artifact =>
artifactClient.deleteArtifact(artifact.name)
)
await Promise.all(deletePromises)
core.info(`The ${artifacts.length} artifact(s) have been deleted`)
}
try {
await rm(tmpDir, {recursive: true})
} catch (error) {
core.warning(
`Unable to remove temporary directory: ${(error as Error).message}`
)
}
}

33
src/merge/merge-inputs.ts Normal file
View File

@ -0,0 +1,33 @@
export interface MergeInputs {
/**
* The name of the artifact that the artifacts will be merged into
*/
name: string
/**
* A glob pattern matching the artifacts that should be merged.
*/
pattern: string
/**
* Duration after which artifact will expire in days
*/
retentionDays: number
/**
* The level of compression for Zlib to be applied to the artifact archive.
*/
compressionLevel?: number
/**
* If true, the artifacts that were merged will be deleted.
* If false, the artifacts will still exist.
*/
deleteMerged: boolean
/**
* If true, the artifacts will be merged into separate directories.
* If false, the artifacts will be merged into the root of the destination.
*/
separateDirectories: boolean
}