Compare commits

..

9 Commits

Author SHA1 Message Date
Daniel Kennedy
bcacd8b0f6 Fix linting issues 2026-01-30 15:15:13 -05:00
Daniel Kennedy
f0a2a2d898 Add a setting to specify what to do on hash mismatch and default it to error 2026-01-30 15:12:08 -05:00
Daniel Kennedy
c9ff097e79 Update the integration test 2026-01-30 14:45:33 -05:00
Daniel Kennedy
882c7072ac Add an integration test for skip-decompress 2026-01-30 14:33:56 -05:00
Daniel Kennedy
6d4a0de04f Add tests 2026-01-30 14:19:20 -05:00
Daniel Kennedy
f45b79f03c Don't attempt to un-zip non-zipped downloads 2026-01-30 14:16:38 -05:00
Daniel Kennedy
1d636af56d Upgrade jest and typescript 2026-01-30 14:15:49 -05:00
Daniel Kennedy
43c2394920 Migrate eslint to v9 2026-01-30 14:15:20 -05:00
Daniel Kennedy
3d65dc0e0b Update .gitignore 2026-01-30 14:14:33 -05:00
16 changed files with 70867 additions and 70298 deletions

View File

@@ -1,3 +0,0 @@
node_modules/
lib/
dist/

View File

@@ -1,15 +0,0 @@
{
"env": { "node": true, "jest": true },
"parser": "@typescript-eslint/parser",
"parserOptions": { "ecmaVersion": 9, "sourceType": "module" },
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:import/typescript",
"plugin:prettier/recommended"
],
"plugins": ["@typescript-eslint"]
}

View File

@@ -131,3 +131,26 @@ jobs:
Write-Error "File contents of downloaded artifacts are incorrect"
}
shell: pwsh
# Test downloading artifact without decompressing (skip-decompress)
- name: Download artifact A without decompressing
uses: ./
with:
name: Artifact-A-${{ matrix.runs-on }}
path: skip-decompress-test
skip-decompress: true
- name: Verify skip-decompress download
run: |
$rawFile = "skip-decompress-test/artifact"
if(!(Test-Path -path $rawFile))
{
Write-Error "Expected raw artifact file does not exist at $rawFile"
}
$fileInfo = Get-Item $rawFile
if($fileInfo.Length -eq 0)
{
Write-Error "Downloaded artifact file is empty"
}
Write-Host "Successfully downloaded artifact without decompressing: $rawFile (size: $($fileInfo.Length) bytes)"
shell: pwsh

4
.gitignore vendored
View File

@@ -2,4 +2,6 @@
node_modules/
# Ignore js files that are transpiled from ts files in src/
lib/
lib/
.DS_Store

View File

@@ -1,10 +1,8 @@
import * as core from '@actions/core'
import {jest, describe, test, expect, beforeEach} from '@jest/globals'
import * as path from 'path'
import artifact, {ArtifactNotFoundError} from '@actions/artifact'
import {run} from '../src/download-artifact'
import {Inputs} from '../src/constants'
jest.mock('@actions/github', () => ({
// Mock @actions/github before importing modules that use it
jest.unstable_mockModule('@actions/github', () => ({
context: {
repo: {
owner: 'actions',
@@ -12,14 +10,46 @@ jest.mock('@actions/github', () => ({
},
runId: 123,
serverUrl: 'https://github.com'
}
},
getOctokit: jest.fn()
}))
jest.mock('@actions/core')
// Mock @actions/core
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn(),
getBooleanInput: jest.fn(),
setOutput: jest.fn(),
setFailed: jest.fn(),
setSecret: jest.fn(),
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
startGroup: jest.fn(),
endGroup: jest.fn(),
isDebug: jest.fn(() => false),
getState: jest.fn(),
saveState: jest.fn(),
exportVariable: jest.fn(),
addPath: jest.fn(),
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn(p => p),
toWin32Path: jest.fn(p => p),
toPosixPath: jest.fn(p => p)
}))
/* eslint-disable no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
const inputs = {
// Dynamic imports after mocking
const core = await import('@actions/core')
const artifact = await import('@actions/artifact')
const {run} = await import('../src/download-artifact.js')
const {Inputs} = await import('../src/constants.js')
const {ArtifactNotFoundError} = artifact
const mockInputs = (
overrides?: Partial<{[K in (typeof Inputs)[keyof typeof Inputs]]?: any}>
) => {
const inputs: Record<string, any> = {
[Inputs.Name]: 'artifact-name',
[Inputs.Path]: '/some/artifact/path',
[Inputs.GitHubToken]: 'warn',
@@ -29,10 +59,14 @@ const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
...overrides
}
;(core.getInput as jest.Mock).mockImplementation((name: string) => {
return inputs[name]
})
;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
;(core.getInput as jest.Mock<typeof core.getInput>).mockImplementation(
(name: string) => {
return inputs[name]
}
)
;(
core.getBooleanInput as jest.Mock<typeof core.getBooleanInput>
).mockImplementation((name: string) => {
return inputs[name]
})
@@ -46,13 +80,13 @@ describe('download', () => {
// Mock artifact client methods
jest
.spyOn(artifact, 'listArtifacts')
.spyOn(artifact.default, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: []}))
jest.spyOn(artifact, 'getArtifact').mockImplementation(name => {
jest.spyOn(artifact.default, 'getArtifact').mockImplementation(name => {
throw new ArtifactNotFoundError(`Artifact '${name}' not found`)
})
jest
.spyOn(artifact, 'downloadArtifact')
.spyOn(artifact.default, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: false}))
})
@@ -65,12 +99,12 @@ describe('download', () => {
}
jest
.spyOn(artifact, 'getArtifact')
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
await run()
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
mockArtifact.id,
expect.objectContaining({
expectedHash: mockArtifact.digest
@@ -102,12 +136,12 @@ describe('download', () => {
// Set up artifact mock after clearing mocks
jest
.spyOn(artifact, 'listArtifacts')
.spyOn(artifact.default, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
// Reset downloadArtifact mock as well
jest
.spyOn(artifact, 'downloadArtifact')
.spyOn(artifact.default, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: false}))
await run()
@@ -117,7 +151,7 @@ describe('download', () => {
)
expect(core.info).toHaveBeenCalledWith('Total of 2 artifact(s) downloaded')
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(2)
expect(artifact.default.downloadArtifact).toHaveBeenCalledTimes(2)
})
test('sets download path output even when no artifacts are found', async () => {
@@ -144,7 +178,7 @@ describe('download', () => {
]
jest
.spyOn(artifact, 'listArtifacts')
.spyOn(artifact.default, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
mockInputs({
@@ -154,8 +188,8 @@ describe('download', () => {
await run()
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
expect(artifact.default.downloadArtifact).toHaveBeenCalledTimes(1)
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
123,
expect.anything()
)
@@ -172,12 +206,12 @@ describe('download', () => {
})
jest
.spyOn(artifact, 'listArtifacts')
.spyOn(artifact.default, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: []}))
await run()
expect(artifact.listArtifacts).toHaveBeenCalledWith(
expect(artifact.default.listArtifacts).toHaveBeenCalledWith(
expect.objectContaining({
findBy: {
token,
@@ -200,7 +234,7 @@ describe('download', () => {
)
})
test('warns when digest validation fails', async () => {
test('errors when digest validation fails (default behavior)', async () => {
const mockArtifact = {
id: 123,
name: 'corrupted-artifact',
@@ -209,11 +243,36 @@ describe('download', () => {
}
jest
.spyOn(artifact, 'getArtifact')
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
jest
.spyOn(artifact, 'downloadArtifact')
.spyOn(artifact.default, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: true}))
await expect(run()).rejects.toThrow(
'Digest validation failed for artifact(s): corrupted-artifact'
)
})
test('warns when digest validation fails with digest-mismatch set to warn', async () => {
const mockArtifact = {
id: 123,
name: 'corrupted-artifact',
size: 1024,
digest: 'abc123'
}
mockInputs({
[Inputs.DigestMismatch]: 'warn'
})
jest
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
jest
.spyOn(artifact.default, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: true}))
await run()
@@ -223,6 +282,61 @@ describe('download', () => {
)
})
test('logs info when digest validation fails with digest-mismatch set to info', async () => {
const mockArtifact = {
id: 123,
name: 'corrupted-artifact',
size: 1024,
digest: 'abc123'
}
mockInputs({
[Inputs.DigestMismatch]: 'info'
})
jest
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
jest
.spyOn(artifact.default, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: true}))
await run()
expect(core.info).toHaveBeenCalledWith(
expect.stringContaining('digest validation failed')
)
})
test('silently continues when digest validation fails with digest-mismatch set to ignore', async () => {
const mockArtifact = {
id: 123,
name: 'corrupted-artifact',
size: 1024,
digest: 'abc123'
}
mockInputs({
[Inputs.DigestMismatch]: 'ignore'
})
jest
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
jest
.spyOn(artifact.default, 'downloadArtifact')
.mockImplementation(() => Promise.resolve({digestMismatch: true}))
await run()
expect(core.warning).not.toHaveBeenCalledWith(
expect.stringContaining('digest validation failed')
)
expect(core.info).toHaveBeenCalledWith('Total of 1 artifact(s) downloaded')
})
test('downloads a single artifact by ID', async () => {
const mockArtifact = {
id: 456,
@@ -237,7 +351,7 @@ describe('download', () => {
[Inputs.ArtifactIds]: '456'
})
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
jest.spyOn(artifact.default, 'listArtifacts').mockImplementation(() =>
Promise.resolve({
artifacts: [mockArtifact]
})
@@ -247,8 +361,8 @@ describe('download', () => {
expect(core.info).toHaveBeenCalledWith('Downloading artifacts by ID')
expect(core.debug).toHaveBeenCalledWith('Parsed artifact IDs: ["456"]')
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
expect(artifact.default.downloadArtifact).toHaveBeenCalledTimes(1)
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
456,
expect.objectContaining({
expectedHash: mockArtifact.digest
@@ -270,7 +384,7 @@ describe('download', () => {
[Inputs.ArtifactIds]: '123, 456, 789'
})
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
jest.spyOn(artifact.default, 'listArtifacts').mockImplementation(() =>
Promise.resolve({
artifacts: mockArtifacts
})
@@ -282,9 +396,9 @@ describe('download', () => {
expect(core.debug).toHaveBeenCalledWith(
'Parsed artifact IDs: ["123","456","789"]'
)
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(3)
expect(artifact.default.downloadArtifact).toHaveBeenCalledTimes(3)
mockArtifacts.forEach(mockArtifact => {
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
mockArtifact.id,
expect.objectContaining({
expectedHash: mockArtifact.digest
@@ -305,7 +419,7 @@ describe('download', () => {
[Inputs.ArtifactIds]: '123, 456, 789'
})
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
jest.spyOn(artifact.default, 'listArtifacts').mockImplementation(() =>
Promise.resolve({
artifacts: mockArtifacts
})
@@ -317,7 +431,7 @@ describe('download', () => {
'Could not find the following artifact IDs: 456, 789'
)
expect(core.debug).toHaveBeenCalledWith('Found 1 artifacts by ID')
expect(artifact.downloadArtifact).toHaveBeenCalledTimes(1)
expect(artifact.default.downloadArtifact).toHaveBeenCalledTimes(1)
})
test('throws error when no artifacts with requested IDs are found', async () => {
@@ -327,7 +441,7 @@ describe('download', () => {
[Inputs.ArtifactIds]: '123, 456'
})
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
jest.spyOn(artifact.default, 'listArtifacts').mockImplementation(() =>
Promise.resolve({
artifacts: []
})
@@ -389,7 +503,7 @@ describe('download', () => {
[Inputs.Path]: testPath
})
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
jest.spyOn(artifact.default, 'listArtifacts').mockImplementation(() =>
Promise.resolve({
artifacts: [mockArtifact]
})
@@ -398,7 +512,7 @@ describe('download', () => {
await run()
// Verify it downloads directly to the specified path (not nested in artifact name subdirectory)
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
456,
expect.objectContaining({
path: path.resolve(testPath), // Should be the resolved path directly, not nested
@@ -406,4 +520,87 @@ describe('download', () => {
})
)
})
test('passes skipDecompress option when skip-decompress input is true', async () => {
const mockArtifact = {
id: 123,
name: 'artifact-name',
size: 1024,
digest: 'abc123'
}
mockInputs({
[Inputs.SkipDecompress]: true
})
jest
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
await run()
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
mockArtifact.id,
expect.objectContaining({
skipDecompress: true,
expectedHash: mockArtifact.digest
})
)
})
test('does not pass skipDecompress when skip-decompress input is false', async () => {
const mockArtifact = {
id: 123,
name: 'artifact-name',
size: 1024,
digest: 'abc123'
}
mockInputs({
[Inputs.SkipDecompress]: false
})
jest
.spyOn(artifact.default, 'getArtifact')
.mockImplementation(() => Promise.resolve({artifact: mockArtifact}))
await run()
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
mockArtifact.id,
expect.objectContaining({
skipDecompress: false,
expectedHash: mockArtifact.digest
})
)
})
test('passes skipDecompress for multiple artifact downloads', async () => {
mockInputs({
[Inputs.Name]: '',
[Inputs.Pattern]: '',
[Inputs.SkipDecompress]: true
})
const mockArtifacts = [
{id: 123, name: 'artifact1', size: 1024, digest: 'abc123'},
{id: 456, name: 'artifact2', size: 2048, digest: 'def456'}
]
jest
.spyOn(artifact.default, 'listArtifacts')
.mockImplementation(() => Promise.resolve({artifacts: mockArtifacts}))
await run()
expect(artifact.default.downloadArtifact).toHaveBeenCalledTimes(2)
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
123,
expect.objectContaining({skipDecompress: true})
)
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(
456,
expect.objectContaining({skipDecompress: true})
)
})
})

View File

@@ -35,6 +35,16 @@ inputs:
If github-token is specified, this is the run that artifacts will be downloaded from.'
required: false
default: ${{ github.run_id }}
skip-decompress:
description: 'If true, the downloaded artifact will not be automatically extracted/decompressed.
This is useful when you want to handle the artifact as-is without extraction.'
required: false
default: 'false'
digest-mismatch:
description: 'The behavior when a downloaded artifact''s digest does not match the expected digest.
Options: ignore, info, warn, error. Default is error which will fail the action.'
required: false
default: 'error'
outputs:
download-path:
description: 'Path of artifact download'

134771
dist/index.js vendored

File diff suppressed because one or more lines are too long

3
dist/package.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

58
eslint.config.mjs Normal file
View File

@@ -0,0 +1,58 @@
import github from 'eslint-plugin-github'
import jest from 'eslint-plugin-jest'
import prettier from 'eslint-plugin-prettier/recommended'
const githubConfigs = github.getFlatConfigs()
export default [
{
ignores: ['**/node_modules/**', '**/lib/**', '**/dist/**']
},
githubConfigs.recommended,
...githubConfigs.typescript,
prettier,
{
files: ['**/*.ts'],
languageOptions: {
parserOptions: {
project: './tsconfig.eslint.json'
}
},
rules: {
// Prettier
'prettier/prettier': ['error', {endOfLine: 'auto'}],
// Disable rules that conflict with project style
'eslint-comments/no-use': 'off',
'github/no-then': 'off',
'github/filenames-match-regex': 'off',
'github/array-foreach': 'off',
'import/no-namespace': 'off',
'import/no-commonjs': 'off',
'import/named': 'off',
'import/no-unresolved': 'off',
'i18n-text/no-en': 'off',
'filenames/match-regex': 'off',
'no-shadow': 'off',
'no-unused-vars': 'off',
'no-undef': 'off',
camelcase: 'off',
// TypeScript rules
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-shadow': 'off',
'@typescript-eslint/array-type': 'off',
'@typescript-eslint/no-require-imports': 'off'
}
},
{
files: ['**/__tests__/**/*.ts'],
...jest.configs['flat/recommended'],
rules: {
...jest.configs['flat/recommended'].rules,
'jest/expect-expect': 'off',
'jest/no-conditional-expect': 'off'
}
}
]

View File

@@ -1,12 +1,24 @@
module.exports = {
export default {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
roots: ['<rootDir>'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
diagnostics: {
ignoreCodes: [151002]
}
}
]
},
extensionsToTreatAsEsm: ['.ts'],
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
verbose: true
}

5869
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
"name": "download-artifact",
"version": "7.0.0",
"description": "Download an Actions Artifact from a workflow run",
"type": "module",
"engines": {
"node": ">=24"
},
@@ -13,7 +14,7 @@
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint **/*.ts",
"test": "jest"
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
},
"repository": {
"type": "git",
@@ -32,22 +33,24 @@
},
"homepage": "https://github.com/actions/download-artifact#readme",
"dependencies": {
"@actions/artifact": "^5.0.0",
"@actions/core": "^2.0.0",
"@actions/github": "^6.0.1",
"minimatch": "^9.0.3"
"@actions/artifact": "^6.1.0",
"@actions/core": "^3.0.0",
"minimatch": "^10.1.1"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"@types/node": "^24.1.0",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@vercel/ncc": "^0.33.4",
"concurrently": "^5.2.0",
"eslint": "^8.55.0",
"eslint-plugin-github": "^4.10.1",
"eslint-plugin-prettier": "^5.0.1",
"jest": "^29.7.0",
"prettier": "^3.1.1",
"@actions/github": "^9.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^25.1.0",
"@typescript-eslint/eslint-plugin": "^8.54.0",
"@typescript-eslint/parser": "^8.54.0",
"@vercel/ncc": "^0.38.4",
"concurrently": "^9.2.1",
"eslint": "^9.39.2",
"eslint-plugin-github": "^6.0.0",
"eslint-plugin-jest": "^29.12.1",
"eslint-plugin-prettier": "^5.5.5",
"jest": "^30.2.0",
"prettier": "^3.8.1",
"ts-jest": "^29.2.6",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"

View File

@@ -6,7 +6,16 @@ export enum Inputs {
RunID = 'run-id',
Pattern = 'pattern',
MergeMultiple = 'merge-multiple',
ArtifactIds = 'artifact-ids'
ArtifactIds = 'artifact-ids',
SkipDecompress = 'skip-decompress',
DigestMismatch = 'digest-mismatch'
}
export enum DigestMismatchBehavior {
Ignore = 'ignore',
Info = 'info',
Warn = 'warn',
Error = 'error'
}
export enum Outputs {

View File

@@ -4,7 +4,7 @@ import * as core from '@actions/core'
import artifactClient from '@actions/artifact'
import type {Artifact, FindOptions} from '@actions/artifact'
import {Minimatch} from 'minimatch'
import {Inputs, Outputs} from './constants'
import {Inputs, Outputs, DigestMismatchBehavior} from './constants.js'
const PARALLEL_DOWNLOADS = 5
@@ -26,7 +26,20 @@ export async function run(): Promise<void> {
mergeMultiple: core.getBooleanInput(Inputs.MergeMultiple, {
required: false
}),
artifactIds: core.getInput(Inputs.ArtifactIds, {required: false})
artifactIds: core.getInput(Inputs.ArtifactIds, {required: false}),
skipDecompress: core.getBooleanInput(Inputs.SkipDecompress, {
required: false
}),
digestMismatch: (core.getInput(Inputs.DigestMismatch, {required: false}) ||
DigestMismatchBehavior.Error) as DigestMismatchBehavior
}
// Validate digest-mismatch input
const validBehaviors = Object.values(DigestMismatchBehavior)
if (!validBehaviors.includes(inputs.digestMismatch)) {
throw new Error(
`Invalid value for 'digest-mismatch': '${inputs.digestMismatch}'. Valid options are: ${validBehaviors.join(', ')}`
)
}
if (!inputs.path) {
@@ -179,11 +192,14 @@ export async function run(): Promise<void> {
artifacts.length === 1
? resolvedPath
: path.join(resolvedPath, artifact.name),
expectedHash: artifact.digest
expectedHash: artifact.digest,
skipDecompress: inputs.skipDecompress
})
}))
const chunkedPromises = chunk(downloadPromises, PARALLEL_DOWNLOADS)
const digestMismatches: string[] = []
for (const chunk of chunkedPromises) {
const chunkPromises = chunk.map(item => item.promise)
const results = await Promise.all(chunkPromises)
@@ -193,12 +209,38 @@ export async function run(): Promise<void> {
const artifactName = chunk[i].name
if (outcome.digestMismatch) {
core.warning(
`Artifact '${artifactName}' digest validation failed. Please verify the integrity of the artifact.`
)
digestMismatches.push(artifactName)
const message = `Artifact '${artifactName}' digest validation failed. Please verify the integrity of the artifact.`
switch (inputs.digestMismatch) {
case DigestMismatchBehavior.Ignore:
// Do nothing
break
case DigestMismatchBehavior.Info:
core.info(message)
break
case DigestMismatchBehavior.Warn:
core.warning(message)
break
case DigestMismatchBehavior.Error:
// Collect all errors and fail at the end
break
}
}
}
}
// If there were digest mismatches and behavior is 'error', fail the action
if (
digestMismatches.length > 0 &&
inputs.digestMismatch === DigestMismatchBehavior.Error
) {
throw new Error(
`Digest validation failed for artifact(s): ${digestMismatches.join(', ')}. ` +
`Use 'digest-mismatch: warn' to continue on mismatch.`
)
}
core.info(`Total of ${artifacts.length} artifact(s) downloaded`)
core.setOutput(Outputs.DownloadPath, resolvedPath)
core.info('Download artifact has finished successfully')

8
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "."
},
"include": ["src/**/*.ts", "__tests__/**/*.ts", "*.ts"],
"exclude": ["node_modules", "lib", "dist"]
}

View File

@@ -1,12 +1,12 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"target": "ES2022",
"module": "NodeNext",
"outDir": "./lib",
"rootDir": "./src",
"strict": true,
"noImplicitAny": false,
"moduleResolution": "node",
"moduleResolution": "NodeNext",
"esModuleInterop": true
},
"exclude": ["node_modules", "**/*.test.ts", "jest.config.ts", "__tests__"]