mirror of
https://github.com/actions/upload-artifact.git
synced 2026-02-26 05:32:32 +00:00
Upgrade the module to ESM and bump dependencies
This commit is contained in:
@ -1,16 +0,0 @@
|
||||
{
|
||||
"env": { "node": true, "jest": true },
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": { "ecmaVersion": 9, "sourceType": "module" },
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/typescript",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "jest"]
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
lib/
|
||||
__tests__/_temp/
|
||||
__tests__/_temp/
|
||||
.DS_Store
|
||||
@ -1,8 +1,65 @@
|
||||
import * as core from '@actions/core'
|
||||
import artifact from '@actions/artifact'
|
||||
import {run} from '../src/merge/merge-artifacts'
|
||||
import {Inputs} from '../src/merge/constants'
|
||||
import * as search from '../src/shared/search'
|
||||
import {jest, describe, test, expect, beforeEach} from '@jest/globals'
|
||||
|
||||
// Mock @actions/github before importing modules that use it
|
||||
jest.unstable_mockModule('@actions/github', () => ({
|
||||
context: {
|
||||
repo: {
|
||||
owner: 'actions',
|
||||
repo: 'toolkit'
|
||||
},
|
||||
runId: 123,
|
||||
serverUrl: 'https://github.com'
|
||||
},
|
||||
getOctokit: jest.fn()
|
||||
}))
|
||||
|
||||
// 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: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}))
|
||||
|
||||
// Mock fs/promises
|
||||
const actualFsPromises = await import('fs/promises')
|
||||
jest.unstable_mockModule('fs/promises', () => ({
|
||||
...actualFsPromises,
|
||||
mkdtemp:
|
||||
jest.fn<() => Promise<string>>().mockResolvedValue('/tmp/merge-artifact'),
|
||||
rm: jest.fn<() => Promise<void>>().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
// Mock shared search module
|
||||
const mockFindFilesToUpload = jest.fn<
|
||||
() => Promise<{filesToUpload: string[]; rootDirectory: string}>
|
||||
>()
|
||||
jest.unstable_mockModule('../src/shared/search.js', () => ({
|
||||
findFilesToUpload: mockFindFilesToUpload
|
||||
}))
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core')
|
||||
const artifact = await import('@actions/artifact')
|
||||
const {run} = await import('../src/merge/merge-artifacts.js')
|
||||
const {Inputs} = await import('../src/merge/constants.js')
|
||||
|
||||
const fixtures = {
|
||||
artifactName: 'my-merged-artifact',
|
||||
@ -34,27 +91,10 @@ const fixtures = {
|
||||
]
|
||||
}
|
||||
|
||||
jest.mock('@actions/github', () => ({
|
||||
context: {
|
||||
repo: {
|
||||
owner: 'actions',
|
||||
repo: 'toolkit'
|
||||
},
|
||||
runId: 123,
|
||||
serverUrl: 'https://github.com'
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock('@actions/core')
|
||||
|
||||
jest.mock('fs/promises', () => ({
|
||||
mkdtemp: jest.fn().mockResolvedValue('/tmp/merge-artifact'),
|
||||
rm: jest.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
|
||||
const inputs = {
|
||||
const mockInputs = (
|
||||
overrides?: Partial<{[K in (typeof Inputs)[keyof typeof Inputs]]?: any}>
|
||||
) => {
|
||||
const inputs: Record<string, any> = {
|
||||
[Inputs.Name]: 'my-merged-artifact',
|
||||
[Inputs.Pattern]: '*',
|
||||
[Inputs.SeparateDirectories]: false,
|
||||
@ -64,10 +104,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]
|
||||
})
|
||||
|
||||
@ -77,44 +121,45 @@ const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
|
||||
describe('merge', () => {
|
||||
beforeEach(async () => {
|
||||
mockInputs()
|
||||
jest.clearAllMocks()
|
||||
|
||||
jest
|
||||
.spyOn(artifact, 'listArtifacts')
|
||||
.spyOn(artifact.default, 'listArtifacts')
|
||||
.mockResolvedValue({artifacts: fixtures.artifacts})
|
||||
|
||||
jest.spyOn(artifact, 'downloadArtifact').mockResolvedValue({
|
||||
jest.spyOn(artifact.default, 'downloadArtifact').mockResolvedValue({
|
||||
downloadPath: fixtures.tmpDirectory
|
||||
})
|
||||
|
||||
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
|
||||
mockFindFilesToUpload.mockResolvedValue({
|
||||
filesToUpload: fixtures.filesToUpload,
|
||||
rootDirectory: fixtures.tmpDirectory
|
||||
})
|
||||
|
||||
jest.spyOn(artifact, 'uploadArtifact').mockResolvedValue({
|
||||
jest.spyOn(artifact.default, 'uploadArtifact').mockResolvedValue({
|
||||
size: 123,
|
||||
id: 1337
|
||||
})
|
||||
|
||||
jest
|
||||
.spyOn(artifact, 'deleteArtifact')
|
||||
.mockImplementation(async artifactName => {
|
||||
const artifact = fixtures.artifacts.find(a => a.name === artifactName)
|
||||
if (!artifact) throw new Error(`Artifact ${artifactName} not found`)
|
||||
return {id: artifact.id}
|
||||
.spyOn(artifact.default, 'deleteArtifact')
|
||||
.mockImplementation(async (artifactName: string) => {
|
||||
const found = fixtures.artifacts.find(a => a.name === artifactName)
|
||||
if (!found) throw new Error(`Artifact ${artifactName} not found`)
|
||||
return {id: found.id}
|
||||
})
|
||||
})
|
||||
|
||||
it('merges artifacts', async () => {
|
||||
test('merges artifacts', async () => {
|
||||
await run()
|
||||
|
||||
for (const a of fixtures.artifacts) {
|
||||
expect(artifact.downloadArtifact).toHaveBeenCalledWith(a.id, {
|
||||
expect(artifact.default.downloadArtifact).toHaveBeenCalledWith(a.id, {
|
||||
path: fixtures.tmpDirectory
|
||||
})
|
||||
}
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.tmpDirectory,
|
||||
@ -122,23 +167,23 @@ describe('merge', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('fails if no artifacts found', async () => {
|
||||
test('fails if no artifacts found', async () => {
|
||||
mockInputs({[Inputs.Pattern]: 'this-does-not-match'})
|
||||
|
||||
expect(run()).rejects.toThrow()
|
||||
await expect(run()).rejects.toThrow()
|
||||
|
||||
expect(artifact.uploadArtifact).not.toBeCalled()
|
||||
expect(artifact.downloadArtifact).not.toBeCalled()
|
||||
expect(artifact.default.uploadArtifact).not.toHaveBeenCalled()
|
||||
expect(artifact.default.downloadArtifact).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('supports custom compression level', async () => {
|
||||
test('supports custom compression level', async () => {
|
||||
mockInputs({
|
||||
[Inputs.CompressionLevel]: 2
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.tmpDirectory,
|
||||
@ -146,14 +191,14 @@ describe('merge', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports custom retention days', async () => {
|
||||
test('supports custom retention days', async () => {
|
||||
mockInputs({
|
||||
[Inputs.RetentionDays]: 7
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.tmpDirectory,
|
||||
@ -161,7 +206,7 @@ describe('merge', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports deleting artifacts after merge', async () => {
|
||||
test('supports deleting artifacts after merge', async () => {
|
||||
mockInputs({
|
||||
[Inputs.DeleteMerged]: true
|
||||
})
|
||||
@ -169,7 +214,7 @@ describe('merge', () => {
|
||||
await run()
|
||||
|
||||
for (const a of fixtures.artifacts) {
|
||||
expect(artifact.deleteArtifact).toHaveBeenCalledWith(a.name)
|
||||
expect(artifact.default.deleteArtifact).toHaveBeenCalledWith(a.name)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,37 @@
|
||||
import * as core from '@actions/core'
|
||||
import {jest, describe, test, expect, beforeAll} from '@jest/globals'
|
||||
import * as path from 'path'
|
||||
import * as io from '@actions/io'
|
||||
import {promises as fs} from 'fs'
|
||||
import {findFilesToUpload} from '../src/shared/search'
|
||||
import {fileURLToPath} from 'url'
|
||||
|
||||
// Mock @actions/core to suppress output during tests
|
||||
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: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}))
|
||||
|
||||
const {findFilesToUpload} = await import('../src/shared/search.js')
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const root = path.join(__dirname, '_temp', 'search')
|
||||
const searchItem1Path = path.join(
|
||||
@ -77,11 +106,8 @@ const fileInHiddenFolderInFolderA = path.join(
|
||||
|
||||
describe('Search', () => {
|
||||
beforeAll(async () => {
|
||||
// mock all output so that there is less noise when running tests
|
||||
// mock console.log to reduce noise
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {})
|
||||
jest.spyOn(core, 'debug').mockImplementation(() => {})
|
||||
jest.spyOn(core, 'info').mockImplementation(() => {})
|
||||
jest.spyOn(core, 'warning').mockImplementation(() => {})
|
||||
|
||||
// clear temp directory
|
||||
await io.rmRF(root)
|
||||
@ -136,43 +162,9 @@ describe('Search', () => {
|
||||
await fs.writeFile(hiddenFile, 'hidden file')
|
||||
await fs.writeFile(fileInHiddenFolderPath, 'file in hidden directory')
|
||||
await fs.writeFile(fileInHiddenFolderInFolderA, 'file in hidden directory')
|
||||
/*
|
||||
Directory structure of files that get created:
|
||||
root/
|
||||
.hidden-folder/
|
||||
folder-in-hidden-folder/
|
||||
file.txt
|
||||
folder-a/
|
||||
.hidden-folder-in-folder-a/
|
||||
file.txt
|
||||
folder-b/
|
||||
folder-c/
|
||||
search-item1.txt
|
||||
extraSearch-item1.txt
|
||||
extra-file-in-folder-c.txt
|
||||
folder-e/
|
||||
folder-d/
|
||||
search-item2.txt
|
||||
search-item3.txt
|
||||
search-item4.txt
|
||||
extraSearch-item2.txt
|
||||
folder-f/
|
||||
extraSearch-item3.txt
|
||||
folder-g/
|
||||
folder-h/
|
||||
amazing-item.txt
|
||||
folder-i/
|
||||
extraSearch-item4.txt
|
||||
extraSearch-item5.txt
|
||||
folder-j/
|
||||
folder-k/
|
||||
lonely-file.txt
|
||||
.hidden-file.txt
|
||||
search-item5.txt
|
||||
*/
|
||||
})
|
||||
|
||||
it('Single file search - Absolute Path', async () => {
|
||||
test('Single file search - Absolute Path', async () => {
|
||||
const searchResult = await findFilesToUpload(extraFileInFolderCPath)
|
||||
expect(searchResult.filesToUpload.length).toEqual(1)
|
||||
expect(searchResult.filesToUpload[0]).toEqual(extraFileInFolderCPath)
|
||||
@ -181,7 +173,7 @@ describe('Search', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('Single file search - Relative Path', async () => {
|
||||
test('Single file search - Relative Path', async () => {
|
||||
const relativePath = path.join(
|
||||
'__tests__',
|
||||
'_temp',
|
||||
@ -200,7 +192,7 @@ describe('Search', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('Single file using wildcard', async () => {
|
||||
test('Single file using wildcard', async () => {
|
||||
const expectedRoot = path.join(root, 'folder-h')
|
||||
const searchPath = path.join(root, 'folder-h', '**/*lonely*')
|
||||
const searchResult = await findFilesToUpload(searchPath)
|
||||
@ -209,7 +201,7 @@ describe('Search', () => {
|
||||
expect(searchResult.rootDirectory).toEqual(expectedRoot)
|
||||
})
|
||||
|
||||
it('Single file using directory', async () => {
|
||||
test('Single file using directory', async () => {
|
||||
const searchPath = path.join(root, 'folder-h', 'folder-j')
|
||||
const searchResult = await findFilesToUpload(searchPath)
|
||||
expect(searchResult.filesToUpload.length).toEqual(1)
|
||||
@ -217,7 +209,7 @@ describe('Search', () => {
|
||||
expect(searchResult.rootDirectory).toEqual(searchPath)
|
||||
})
|
||||
|
||||
it('Directory search - Absolute Path', async () => {
|
||||
test('Directory search - Absolute Path', async () => {
|
||||
const searchPath = path.join(root, 'folder-h')
|
||||
const searchResult = await findFilesToUpload(searchPath)
|
||||
expect(searchResult.filesToUpload.length).toEqual(4)
|
||||
@ -236,7 +228,7 @@ describe('Search', () => {
|
||||
expect(searchResult.rootDirectory).toEqual(searchPath)
|
||||
})
|
||||
|
||||
it('Directory search - Relative Path', async () => {
|
||||
test('Directory search - Relative Path', async () => {
|
||||
const searchPath = path.join('__tests__', '_temp', 'search', 'folder-h')
|
||||
const expectedRootDirectory = path.join(root, 'folder-h')
|
||||
const searchResult = await findFilesToUpload(searchPath)
|
||||
@ -256,7 +248,7 @@ describe('Search', () => {
|
||||
expect(searchResult.rootDirectory).toEqual(expectedRootDirectory)
|
||||
})
|
||||
|
||||
it('Wildcard search - Absolute Path', async () => {
|
||||
test('Wildcard search - Absolute Path', async () => {
|
||||
const searchPath = path.join(root, '**/*[Ss]earch*')
|
||||
const searchResult = await findFilesToUpload(searchPath)
|
||||
expect(searchResult.filesToUpload.length).toEqual(10)
|
||||
@ -285,7 +277,7 @@ describe('Search', () => {
|
||||
expect(searchResult.rootDirectory).toEqual(root)
|
||||
})
|
||||
|
||||
it('Wildcard search - Relative Path', async () => {
|
||||
test('Wildcard search - Relative Path', async () => {
|
||||
const searchPath = path.join(
|
||||
'__tests__',
|
||||
'_temp',
|
||||
@ -319,7 +311,7 @@ describe('Search', () => {
|
||||
expect(searchResult.rootDirectory).toEqual(root)
|
||||
})
|
||||
|
||||
it('Multi path search - root directory', async () => {
|
||||
test('Multi path search - root directory', async () => {
|
||||
const searchPath1 = path.join(root, 'folder-a')
|
||||
const searchPath2 = path.join(root, 'folder-d')
|
||||
|
||||
@ -343,7 +335,7 @@ describe('Search', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('Multi path search - with exclude character', async () => {
|
||||
test('Multi path search - with exclude character', async () => {
|
||||
const searchPath1 = path.join(root, 'folder-a')
|
||||
const searchPath2 = path.join(root, 'folder-d')
|
||||
const searchPath3 = path.join(root, 'folder-a', 'folder-b', '**/extra*.txt')
|
||||
@ -363,7 +355,7 @@ describe('Search', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('Multi path search - non root directory', async () => {
|
||||
test('Multi path search - non root directory', async () => {
|
||||
const searchPath1 = path.join(root, 'folder-h', 'folder-i')
|
||||
const searchPath2 = path.join(root, 'folder-h', 'folder-j', 'folder-k')
|
||||
const searchPath3 = amazingFileInFolderHPath
|
||||
@ -385,7 +377,7 @@ describe('Search', () => {
|
||||
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
|
||||
})
|
||||
|
||||
it('Hidden files ignored by default', async () => {
|
||||
test('Hidden files ignored by default', async () => {
|
||||
const searchPath = path.join(root, '**/*')
|
||||
const searchResult = await findFilesToUpload(searchPath)
|
||||
|
||||
@ -396,7 +388,7 @@ describe('Search', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('Hidden files included', async () => {
|
||||
test('Hidden files included', async () => {
|
||||
const searchPath = path.join(root, '**/*')
|
||||
const searchResult = await findFilesToUpload(searchPath, true)
|
||||
|
||||
|
||||
@ -1,9 +1,58 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as github from '@actions/github'
|
||||
import artifact, {ArtifactNotFoundError} from '@actions/artifact'
|
||||
import {run} from '../src/upload/upload-artifact'
|
||||
import {Inputs} from '../src/upload/constants'
|
||||
import * as search from '../src/shared/search'
|
||||
import {jest, describe, test, expect, beforeEach} from '@jest/globals'
|
||||
|
||||
// Mock @actions/github before importing modules that use it
|
||||
jest.unstable_mockModule('@actions/github', () => ({
|
||||
context: {
|
||||
repo: {
|
||||
owner: 'actions',
|
||||
repo: 'toolkit'
|
||||
},
|
||||
runId: 123,
|
||||
serverUrl: 'https://github.com'
|
||||
},
|
||||
getOctokit: jest.fn()
|
||||
}))
|
||||
|
||||
// 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: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}))
|
||||
|
||||
// Mock shared search module
|
||||
const mockFindFilesToUpload = jest.fn<
|
||||
() => Promise<{filesToUpload: string[]; rootDirectory: string}>
|
||||
>()
|
||||
jest.unstable_mockModule('../src/shared/search.js', () => ({
|
||||
findFilesToUpload: mockFindFilesToUpload
|
||||
}))
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core')
|
||||
const github = await import('@actions/github')
|
||||
const artifact = await import('@actions/artifact')
|
||||
const {run} = await import('../src/upload/upload-artifact.js')
|
||||
const {Inputs} = await import('../src/upload/constants.js')
|
||||
const {ArtifactNotFoundError} = artifact
|
||||
|
||||
const fixtures = {
|
||||
artifactName: 'artifact-name',
|
||||
@ -14,22 +63,10 @@ const fixtures = {
|
||||
]
|
||||
}
|
||||
|
||||
jest.mock('@actions/github', () => ({
|
||||
context: {
|
||||
repo: {
|
||||
owner: 'actions',
|
||||
repo: 'toolkit'
|
||||
},
|
||||
runId: 123,
|
||||
serverUrl: 'https://github.com'
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock('@actions/core')
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
|
||||
const inputs = {
|
||||
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.IfNoFilesFound]: 'warn',
|
||||
@ -39,10 +76,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]
|
||||
})
|
||||
|
||||
@ -52,28 +93,29 @@ const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
|
||||
describe('upload', () => {
|
||||
beforeEach(async () => {
|
||||
mockInputs()
|
||||
jest.clearAllMocks()
|
||||
|
||||
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
|
||||
mockFindFilesToUpload.mockResolvedValue({
|
||||
filesToUpload: fixtures.filesToUpload,
|
||||
rootDirectory: fixtures.rootDirectory
|
||||
})
|
||||
|
||||
jest.spyOn(artifact, 'uploadArtifact').mockResolvedValue({
|
||||
jest.spyOn(artifact.default, 'uploadArtifact').mockResolvedValue({
|
||||
size: 123,
|
||||
id: 1337,
|
||||
digest: 'facefeed'
|
||||
})
|
||||
})
|
||||
|
||||
it('uploads a single file', async () => {
|
||||
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
|
||||
test('uploads a single file', async () => {
|
||||
mockFindFilesToUpload.mockResolvedValue({
|
||||
filesToUpload: [fixtures.filesToUpload[0]],
|
||||
rootDirectory: fixtures.rootDirectory
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
[fixtures.filesToUpload[0]],
|
||||
fixtures.rootDirectory,
|
||||
@ -81,10 +123,10 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('uploads multiple files', async () => {
|
||||
test('uploads multiple files', async () => {
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.rootDirectory,
|
||||
@ -92,27 +134,25 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('sets outputs', async () => {
|
||||
test('sets outputs', async () => {
|
||||
await run()
|
||||
|
||||
expect(core.setOutput).toHaveBeenCalledWith('artifact-id', 1337)
|
||||
expect(core.setOutput).toHaveBeenCalledWith('artifact-digest', 'facefeed')
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
'artifact-url',
|
||||
`${github.context.serverUrl}/${github.context.repo.owner}/${
|
||||
github.context.repo.repo
|
||||
}/actions/runs/${github.context.runId}/artifacts/${1337}`
|
||||
`${github.context.serverUrl}/${github.context.repo.owner}/${github.context.repo.repo}/actions/runs/${github.context.runId}/artifacts/${1337}`
|
||||
)
|
||||
})
|
||||
|
||||
it('supports custom compression level', async () => {
|
||||
test('supports custom compression level', async () => {
|
||||
mockInputs({
|
||||
[Inputs.CompressionLevel]: 2
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.rootDirectory,
|
||||
@ -120,14 +160,14 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports custom retention days', async () => {
|
||||
test('supports custom retention days', async () => {
|
||||
mockInputs({
|
||||
[Inputs.RetentionDays]: 7
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.rootDirectory,
|
||||
@ -135,12 +175,12 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports warn if-no-files-found', async () => {
|
||||
test('supports warn if-no-files-found', async () => {
|
||||
mockInputs({
|
||||
[Inputs.IfNoFilesFound]: 'warn'
|
||||
})
|
||||
|
||||
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
|
||||
mockFindFilesToUpload.mockResolvedValue({
|
||||
filesToUpload: [],
|
||||
rootDirectory: fixtures.rootDirectory
|
||||
})
|
||||
@ -152,12 +192,12 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports error if-no-files-found', async () => {
|
||||
test('supports error if-no-files-found', async () => {
|
||||
mockInputs({
|
||||
[Inputs.IfNoFilesFound]: 'error'
|
||||
})
|
||||
|
||||
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
|
||||
mockFindFilesToUpload.mockResolvedValue({
|
||||
filesToUpload: [],
|
||||
rootDirectory: fixtures.rootDirectory
|
||||
})
|
||||
@ -169,12 +209,12 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports ignore if-no-files-found', async () => {
|
||||
test('supports ignore if-no-files-found', async () => {
|
||||
mockInputs({
|
||||
[Inputs.IfNoFilesFound]: 'ignore'
|
||||
})
|
||||
|
||||
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
|
||||
mockFindFilesToUpload.mockResolvedValue({
|
||||
filesToUpload: [],
|
||||
rootDirectory: fixtures.rootDirectory
|
||||
})
|
||||
@ -186,46 +226,50 @@ describe('upload', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('supports overwrite', async () => {
|
||||
test('supports overwrite', async () => {
|
||||
mockInputs({
|
||||
[Inputs.Overwrite]: true
|
||||
})
|
||||
|
||||
jest.spyOn(artifact, 'deleteArtifact').mockResolvedValue({
|
||||
jest.spyOn(artifact.default, 'deleteArtifact').mockResolvedValue({
|
||||
id: 1337
|
||||
})
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.rootDirectory,
|
||||
{compressionLevel: 6}
|
||||
)
|
||||
|
||||
expect(artifact.deleteArtifact).toHaveBeenCalledWith(fixtures.artifactName)
|
||||
expect(artifact.default.deleteArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName
|
||||
)
|
||||
})
|
||||
|
||||
it('supports overwrite and continues if not found', async () => {
|
||||
test('supports overwrite and continues if not found', async () => {
|
||||
mockInputs({
|
||||
[Inputs.Overwrite]: true
|
||||
})
|
||||
|
||||
jest
|
||||
.spyOn(artifact, 'deleteArtifact')
|
||||
.spyOn(artifact.default, 'deleteArtifact')
|
||||
.mockRejectedValue(new ArtifactNotFoundError('not found'))
|
||||
|
||||
await run()
|
||||
|
||||
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
|
||||
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName,
|
||||
fixtures.filesToUpload,
|
||||
fixtures.rootDirectory,
|
||||
{compressionLevel: 6}
|
||||
)
|
||||
|
||||
expect(artifact.deleteArtifact).toHaveBeenCalledWith(fixtures.artifactName)
|
||||
expect(artifact.default.deleteArtifact).toHaveBeenCalledWith(
|
||||
fixtures.artifactName
|
||||
)
|
||||
expect(core.debug).toHaveBeenCalledWith(
|
||||
`Skipping deletion of '${fixtures.artifactName}', it does not exist`
|
||||
)
|
||||
|
||||
53
eslint.config.mjs
Normal file
53
eslint.config.mjs
Normal file
@ -0,0 +1,53 @@
|
||||
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': ['error', {endOfLine: 'auto'}],
|
||||
'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-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'
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1,12 +0,0 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
roots: ['<rootDir>'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
testRunner: 'jest-circus/runner',
|
||||
transform: {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
24
jest.config.ts
Normal file
24
jest.config.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
roots: ['<rootDir>'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
diagnostics: {
|
||||
ignoreCodes: [151002]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1'
|
||||
},
|
||||
verbose: true
|
||||
}
|
||||
4426
package-lock.json
generated
4426
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
46
package.json
46
package.json
@ -2,6 +2,7 @@
|
||||
"name": "upload-artifact",
|
||||
"version": "6.0.0",
|
||||
"description": "Upload an Actions Artifact in a workflow run",
|
||||
"type": "module",
|
||||
"main": "dist/upload/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
@ -10,7 +11,7 @@
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"lint": "eslint **/*.ts",
|
||||
"test": "jest --testTimeout 10000"
|
||||
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testTimeout 10000"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -32,32 +33,29 @@
|
||||
"node": ">=24"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.0",
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/artifact": "^6.1.0",
|
||||
"@actions/core": "^3.0.0",
|
||||
"@actions/github": "^9.0.0",
|
||||
"@actions/glob": "^0.6.1",
|
||||
"@actions/io": "^3.0.2",
|
||||
"minimatch": "^9.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.2.5",
|
||||
"@types/node": "^24.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vercel/ncc": "^0.36.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"eslint": "^8.31.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-github": "^4.6.0",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
"eslint-plugin-jest": "^28.14.0",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"glob": "^8.0.3",
|
||||
"jest": "^29.3.1",
|
||||
"jest-circus": "^29.3.1",
|
||||
"prettier": "^3.7.4",
|
||||
"ts-jest": "^29.0.3",
|
||||
"typescript": "^5.2.2"
|
||||
"@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"
|
||||
},
|
||||
"overrides": {
|
||||
"uri-js": "npm:uri-js-replace@^1.0.1"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as core from '@actions/core'
|
||||
import {run} from './merge-artifacts'
|
||||
import {run} from './merge-artifacts.js'
|
||||
|
||||
run().catch(error => {
|
||||
core.setFailed((error as Error).message)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Inputs} from './constants'
|
||||
import {MergeInputs} from './merge-inputs'
|
||||
import {Inputs} from './constants.js'
|
||||
import {MergeInputs} from './merge-inputs.js'
|
||||
|
||||
/**
|
||||
* Helper to get all the inputs for the action
|
||||
|
||||
@ -3,9 +3,9 @@ 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'
|
||||
import {getInputs} from './input-helper.js'
|
||||
import {uploadArtifact} from '../shared/upload-artifact.js'
|
||||
import {findFilesToUpload} from '../shared/search.js'
|
||||
|
||||
const PARALLEL_DOWNLOADS = 5
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as core from '@actions/core'
|
||||
import {run} from './upload-artifact'
|
||||
import {run} from './upload-artifact.js'
|
||||
|
||||
run().catch(error => {
|
||||
core.setFailed((error as Error).message)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import * as core from '@actions/core'
|
||||
import {Inputs, NoFileOptions} from './constants'
|
||||
import {UploadInputs} from './upload-inputs'
|
||||
import {Inputs, NoFileOptions} from './constants.js'
|
||||
import {UploadInputs} from './upload-inputs.js'
|
||||
|
||||
/**
|
||||
* Helper to get all the inputs for the action
|
||||
|
||||
@ -3,10 +3,10 @@ import artifact, {
|
||||
UploadArtifactOptions,
|
||||
ArtifactNotFoundError
|
||||
} from '@actions/artifact'
|
||||
import {findFilesToUpload} from '../shared/search'
|
||||
import {getInputs} from './input-helper'
|
||||
import {NoFileOptions} from './constants'
|
||||
import {uploadArtifact} from '../shared/upload-artifact'
|
||||
import {findFilesToUpload} from '../shared/search.js'
|
||||
import {getInputs} from './input-helper.js'
|
||||
import {NoFileOptions} from './constants.js'
|
||||
import {uploadArtifact} from '../shared/upload-artifact.js'
|
||||
|
||||
async function deleteArtifactIfExists(artifactName: string): Promise<void> {
|
||||
try {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {NoFileOptions} from './constants'
|
||||
import {NoFileOptions} from './constants.js'
|
||||
|
||||
export interface UploadInputs {
|
||||
/**
|
||||
|
||||
8
tsconfig.eslint.json
Normal file
8
tsconfig.eslint.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src/**/*.ts", "__tests__/**/*.ts", "*.ts"],
|
||||
"exclude": ["node_modules", "lib", "dist"]
|
||||
}
|
||||
@ -1,17 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "node",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": false,
|
||||
"sourceMap": true,
|
||||
"lib": ["es6"]
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts"]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"exclude": ["node_modules", "**/*.test.ts", "jest.config.ts", "__tests__"]
|
||||
}
|
||||
Reference in New Issue
Block a user