1
0
Fork 0
mirror of https://code.forgejo.org/forgejo/upload-artifact.git synced 2024-11-24 17:22:41 +00:00

V2 Upload Artifact (#70)

* V2 Preview (#54)

* V2 Upload Artifact

* Improve logs

* Update release

* Update test.yml

* Update test.yml

* Update test.yml

* @actions/artifact v0.2.0 package

* Add extra YAML test

* Extra Documentation

* Update README.md

* Update README.md

* Update NPM packages

* Update to @actions/artifact 0.3.1

* Update readme from v2 preview to v2

* Add .gitattributes

* Misc Updates

* macOS to macos

* Update YAML so only push events trigger on the master branch
This commit is contained in:
Konrad Pabjan 2020-04-28 17:18:53 +02:00 committed by GitHub
parent 1283ca12b6
commit e7eefc4917
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 16404 additions and 12 deletions

3
.eslintignore Normal file
View file

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

19
.eslintrc.json Normal file
View file

@ -0,0 +1,19 @@
{
"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",
"prettier/@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-empty-function": "off"
},
"plugins": ["@typescript-eslint", "jest"]
}

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
* text=auto eol=lf

140
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,140 @@
name: Test
on:
push:
branches:
- master
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
jobs:
build:
name: Build
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.runs-on }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: npm install
run: npm install
- name: Compile
run: npm run build
- name: npm test
run: npm test
- name: Lint
run: npm run lint
- name: Format
run: npm run format-check
# Test end-to-end by uploading two artifacts and then downloading them
- name: Create artifact files
run: |
mkdir -p path/to/dir-1
mkdir -p path/to/dir-2
mkdir -p path/to/dir-3
echo "Lorem ipsum dolor sit amet" > path/to/dir-1/file1.txt
echo "Hello world from file #2" > path/to/dir-2/file2.txt
echo "This is a going to be a test for a large enough file that should get compressed with GZip. The @actions/artifact package uses GZip to upload files. This text should have a compression ratio greater than 100% so it should get uploaded using GZip" > path/to/dir-3/gzip.txt
# Upload a single file artifact
- name: 'Upload artifact #1'
uses: ./
with:
name: 'Artifact-A'
path: path/to/dir-1/file1.txt
# Upload using a wildcard pattern, name should default to 'artifact' if not provided
- name: 'Upload artifact #2'
uses: ./
with:
path: path/**/dir*/
# Upload a directory that contains a file that will be uploaded with GZip
- name: 'Upload artifact #3'
uses: ./
with:
name: 'GZip-Artifact'
path: path/to/dir-3/
# Verify artifacts. Switch to download-artifact@v2 once it's out of preview
# Download Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1'
uses: actions/download-artifact@v1
with:
name: 'Artifact-A'
path: some/new/path
- name: 'Verify Artifact #1'
run: |
$file = "some/new/path/file1.txt"
if(!(Test-Path -path $file))
{
Write-Error "Expected file does not exist"
}
if(!((Get-Content $file) -ceq "Lorem ipsum dolor sit amet"))
{
Write-Error "File contents of downloaded artifact are incorrect"
}
shell: pwsh
# Download Artifact #2 and verify the correctness of the content
- name: 'Download artifact #2'
uses: actions/download-artifact@v1
with:
name: 'artifact'
path: some/other/path
- name: 'Verify Artifact #2'
run: |
$file1 = "some/other/path/to/dir-1/file1.txt"
$file2 = "some/other/path/to/dir-2/file2.txt"
if(!(Test-Path -path $file1) -or !(Test-Path -path $file2))
{
Write-Error "Expected files do not exist"
}
if(!((Get-Content $file1) -ceq "Lorem ipsum dolor sit amet") -or !((Get-Content $file2) -ceq "Hello world from file #2"))
{
Write-Error "File contents of downloaded artifacts are incorrect"
}
shell: pwsh
# Download Artifact #3 and verify the correctness of the content
- name: 'Download artifact #3'
uses: actions/download-artifact@v1
with:
name: 'GZip-Artifact'
path: gzip/artifact/path
# Because a directory was used as input during the upload the parent directories, path/to/dir-3/, should not be included in the uploaded artifact
- name: 'Verify Artifact #3'
run: |
$gzipFile = "gzip/artifact/path/gzip.txt"
if(!(Test-Path -path $gzipFile))
{
Write-Error "Expected file do not exist"
}
if(!((Get-Content $gzipFile) -ceq "This is a going to be a test for a large enough file that should get compressed with GZip. The @actions/artifact package uses GZip to upload files. This text should have a compression ratio greater than 100% so it should get uploaded using GZip"))
{
Write-Error "File contents of downloaded artifact is incorrect"
}
shell: pwsh

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules/
lib/
__tests__/_temp/

3
.prettierignore Normal file
View file

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

11
.prettierrc.json Normal file
View file

@ -0,0 +1,11 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
}

120
README.md
View file

@ -1,38 +1,137 @@
# upload-artifact # Upload-Artifact v2
This uploads artifacts from your workflow. This uploads artifacts from your workflow allowing you to share data between jobs and store data once a workflow is complete.
See also [download-artifact](https://github.com/actions/download-artifact). See also [download-artifact](https://github.com/actions/download-artifact).
# What's new
- Easier upload
- Specify a wildcard pattern
- Specify an individual file
- Specify a directory (previously you were limited to only this option)
- Upload an artifact without providing a name
- Fix for artifact uploads sometimes not working with containers
- Proxy support out of the box
- Port entire action to typescript from a runner plugin so it is easier to collaborate and accept contributions
Refer [here](https://github.com/actions/upload-artifact/tree/releases/v1) for the previous version
# Usage # Usage
See [action.yml](action.yml) See [action.yml](action.yml)
Basic: ### Upload an Individual File
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- run: mkdir -p path/to/artifact - run: mkdir -p path/to/artifact
- run: echo hello > path/to/artifact/world.txt - run: echo hello > path/to/artifact/world.txt
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v2
with: with:
name: my-artifact name: my-artifact
path: path/to/artifact path: path/to/artifact/world.txt
``` ```
### Upload an Entire Directory
```yaml
- uses: actions/upload-artifact@v2
with:
name: my-artifact
path: path/to/artifact/ # or path/to/artifact
```
### Upload using a Wildcard Pattern:
```yaml
- uses: actions/upload-artifact@v2
with:
name: my-artifact
path: path/**/[abc]rtifac?/*
```
For supported wildcards along with behavior and documentation, see [@actions/glob](https://github.com/actions/toolkit/tree/master/packages/glob) which is used internally to search for files.
Relative and absolute file paths are both allowed. Relative paths are rooted against the current working directory. Paths that begin with a wildcard character should be quoted to avoid being interpreted as YAML aliases.
The [@actions/artifact](https://github.com/actions/toolkit/tree/master/packages/artifact) package is also used internally to handle most of the logic around uploading an artifact. There is extra documentation around upload limitations and behavior in the toolkit repo that is worth checking out.
### Conditional Artifact Upload
To upload artifacts only when the previous step of a job failed, use [`if: failure()`](https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#job-status-check-functions): To upload artifacts only when the previous step of a job failed, use [`if: failure()`](https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions#job-status-check-functions):
```yaml ```yaml
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: my-artifact name: my-artifact
path: path/to/artifact path: path/to/artifact/
``` ```
### Uploading without an artifact name
You can upload an artifact without specifying a name
```yaml
- uses: actions/upload-artifact@v2
with:
path: path/to/artifact/world.txt
```
If not provided, `artifact` will be used as the default name which will manifest itself in the UI after upload.
### Uploading to the same artifact
Each artifact behaves as a file share. Uploading to the same artifact multiple times in the same workflow can overwrite and append already uploaded files
```yaml
- run: echo hi > world.txt
- uses: actions/upload-artifact@v2
with:
path: world.txt
- run: echo howdy > extra-file.txt
- uses: actions/upload-artifact@v2
with:
path: extra-file.txt
- run: echo hello > world.txt
- uses: actions/upload-artifact@v2
with:
path: world.txt
```
With the following example, the available artifact (named `artifact` which is the default if no name is provided) would contain both `world.txt` (`hello`) and `extra-file.txt` (`howdy`).
### Environment Variables and Tilde Expansion
You can use `~` in the path input as a substitute for `$HOME`. Basic tilde expansion is supported.
```yaml
- run: |
mkdir -p ~/new/artifact
echo hello > ~/new/artifact/world.txt
- uses: actions/upload-artifact@v2
with:
name: 'Artifacts-V2'
path: '~/new/**/*'
```
Environment variables along with context expressions can also be used for input. For documentation see [context and expression syntax](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions).
```yaml
env:
name: my-artifact
steps:
- run: |
mkdir -p ${{ github.workspace }}/artifact
echo hello > ${{ github.workspace }}/artifact/world.txt
- uses: actions/upload-artifact@v2
with:
name: ${{ env.name }}-name
path: ${{ github.workspace }}/artifact/**/*
```
## Where does the upload go? ## Where does the upload go?
In the top right corner of a workflow run, once the run is over, if you used this action, there will be a `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like<br/> In the top right corner of a workflow run, once the run is over, if you used this action, there will be a `Artifacts` dropdown which you can download items from. Here's a screenshot of what it looks like<br/>
@ -40,6 +139,11 @@ In the top right corner of a workflow run, once the run is over, if you used thi
There is a trashcan icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository. There is a trashcan icon that can be used to delete the artifact. This icon will only appear for users who have write permissions to the repository.
## Additional Documentation
See [persisting workflow data using artifacts](https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts) for additional examples and tips.
See extra documentation for the [@actions/artifact](https://github.com/actions/toolkit/blob/master/packages/artifact/docs/additional-information.md) package that is used internally regarding certain behaviors and limitations.
# License # License

289
__tests__/search.test.ts Normal file
View file

@ -0,0 +1,289 @@
import * as core from '@actions/core'
import * as path from 'path'
import * as io from '@actions/io'
import {promises as fs} from 'fs'
import {findFilesToUpload} from '../src/search'
const root = path.join(__dirname, '_temp', 'search')
const searchItem1Path = path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'search-item1.txt'
)
const searchItem2Path = path.join(root, 'folder-d', 'search-item2.txt')
const searchItem3Path = path.join(root, 'folder-d', 'search-item3.txt')
const searchItem4Path = path.join(root, 'folder-d', 'search-item4.txt')
const searchItem5Path = path.join(root, 'search-item5.txt')
const extraSearchItem1Path = path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'extraSearch-item1.txt'
)
const extraSearchItem2Path = path.join(
root,
'folder-d',
'extraSearch-item2.txt'
)
const extraSearchItem3Path = path.join(
root,
'folder-f',
'extraSearch-item3.txt'
)
const extraSearchItem4Path = path.join(
root,
'folder-h',
'folder-i',
'extraSearch-item4.txt'
)
const extraSearchItem5Path = path.join(
root,
'folder-h',
'folder-i',
'extraSearch-item5.txt'
)
const extraFileInFolderCPath = path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'extra-file-in-folder-c.txt'
)
const amazingFileInFolderHPath = path.join(root, 'folder-h', 'amazing-item.txt')
const lonelyFilePath = path.join(
root,
'folder-h',
'folder-j',
'folder-k',
'lonely-file.txt'
)
describe('Search', () => {
beforeAll(async () => {
// mock all output so that there is less noise when running tests
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)
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-c'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-e'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-d'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-f'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-g'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-h', 'folder-i'), {
recursive: true
})
await fs.mkdir(path.join(root, 'folder-h', 'folder-j', 'folder-k'), {
recursive: true
})
await fs.writeFile(searchItem1Path, 'search item1 file')
await fs.writeFile(searchItem2Path, 'search item2 file')
await fs.writeFile(searchItem3Path, 'search item3 file')
await fs.writeFile(searchItem4Path, 'search item4 file')
await fs.writeFile(searchItem5Path, 'search item5 file')
await fs.writeFile(extraSearchItem1Path, 'extraSearch item1 file')
await fs.writeFile(extraSearchItem2Path, 'extraSearch item2 file')
await fs.writeFile(extraSearchItem3Path, 'extraSearch item3 file')
await fs.writeFile(extraSearchItem4Path, 'extraSearch item4 file')
await fs.writeFile(extraSearchItem5Path, 'extraSearch item5 file')
await fs.writeFile(extraFileInFolderCPath, 'extra file')
await fs.writeFile(amazingFileInFolderHPath, 'amazing file')
await fs.writeFile(lonelyFilePath, 'all by itself')
/*
Directory structure of files that get created:
root/
folder-a/
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
search-item5.txt
*/
})
it('Single file search - Absolute Path', async () => {
const searchResult = await findFilesToUpload(extraFileInFolderCPath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(extraFileInFolderCPath)
expect(searchResult.rootDirectory).toEqual(
path.join(root, 'folder-a', 'folder-b', 'folder-c')
)
})
it('Single file search - Relative Path', async () => {
const relativePath = path.join(
'__tests__',
'_temp',
'search',
'folder-a',
'folder-b',
'folder-c',
'search-item1.txt'
)
const searchResult = await findFilesToUpload(relativePath)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(searchItem1Path)
expect(searchResult.rootDirectory).toEqual(
path.join(root, 'folder-a', 'folder-b', 'folder-c')
)
})
it('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)
expect(searchResult.filesToUpload.length).toEqual(1)
expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath)
expect(searchResult.rootDirectory).toEqual(expectedRoot)
})
it('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)
expect(searchResult.filesToUpload[0]).toEqual(lonelyFilePath)
expect(searchResult.rootDirectory).toEqual(searchPath)
})
it('Directory search - Absolute Path', async () => {
const searchPath = path.join(root, 'folder-h')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(4)
expect(
searchResult.filesToUpload.includes(amazingFileInFolderHPath)
).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
expect(searchResult.rootDirectory).toEqual(searchPath)
})
it('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)
expect(searchResult.filesToUpload.length).toEqual(4)
expect(
searchResult.filesToUpload.includes(amazingFileInFolderHPath)
).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
expect(searchResult.rootDirectory).toEqual(expectedRootDirectory)
})
it('Wildcard search - Absolute Path', async () => {
const searchPath = path.join(root, '**/*[Ss]earch*')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(10)
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem4Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem5Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem1Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem2Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem3Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.rootDirectory).toEqual(root)
})
it('Wildcard search - Relative Path', async () => {
const searchPath = path.join(
'__tests__',
'_temp',
'search',
'**/*[Ss]earch*'
)
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload.length).toEqual(10)
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem4Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem5Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem1Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem2Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem3Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem4Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem5Path)).toEqual(
true
)
expect(searchResult.rootDirectory).toEqual(root)
})
})

View file

@ -4,10 +4,10 @@ author: 'GitHub'
inputs: inputs:
name: name:
description: 'Artifact name' description: 'Artifact name'
required: true required: false
path: path:
description: 'Directory containing files to upload' description: 'A file, directory or wildcard pattern that describes what to upload'
required: true required: true
runs: runs:
# Plugins live on the runner and are only available to a certain set of first party actions. using: 'node12'
plugin: 'publish' main: 'dist/index.js'

8526
dist/index.js vendored Normal file

File diff suppressed because it is too large Load diff

12
jest.config.js Normal file
View file

@ -0,0 +1,12 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
roots: ['<rootDir>'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

7096
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

51
package.json Normal file
View file

@ -0,0 +1,51 @@
{
"name": "upload-artifact",
"version": "2.0.0",
"description": "Upload a build artifact that can be used by subsequent workflow steps",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"release": "ncc build src/upload-artifact.ts && git add -f dist/",
"check-all": "concurrently \"npm:format-check\" \"npm:lint\" \"npm:test\" \"npm:build\"",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"lint": "eslint **/*.ts",
"test": "jest --testTimeout 10000"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/upload-artifact.git"
},
"keywords": [
"Actions",
"GitHub",
"Artifacts",
"Upload"
],
"author": "GitHub",
"license": "MIT",
"bugs": {
"url": "https://github.com/actions/upload-artifact/issues"
},
"homepage": "https://github.com/actions/upload-artifact#readme",
"devDependencies": {
"@actions/artifact": "^0.3.1",
"@actions/core": "^1.2.3",
"@actions/glob": "^0.1.0",
"@actions/io": "^1.0.2",
"@types/jest": "^25.2.1",
"@types/node": "^13.11.1",
"@typescript-eslint/parser": "^2.27.0",
"@zeit/ncc": "^0.22.1",
"concurrently": "^5.1.0",
"eslint": "^6.8.0",
"eslint-plugin-github": "^3.4.1",
"eslint-plugin-jest": "^23.8.2",
"glob": "^7.1.6",
"jest": "^25.3.0",
"jest-circus": "^25.3.0",
"prettier": "^2.0.4",
"ts-jest": "^25.3.1",
"typescript": "^3.8.3"
}
}

8
src/constants.ts Normal file
View file

@ -0,0 +1,8 @@
export enum Inputs {
Name = 'name',
Path = 'path'
}
export function getDefaultArtifactName(): string {
return 'artifact'
}

69
src/search.ts Normal file
View file

@ -0,0 +1,69 @@
import * as glob from '@actions/glob'
import {debug} from '@actions/core'
import {lstatSync} from 'fs'
import {dirname} from 'path'
export interface SearchResult {
filesToUpload: string[]
rootDirectory: string
}
function getDefaultGlobOptions(): glob.GlobOptions {
return {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true
}
}
export async function findFilesToUpload(
searchPath: string,
globOptions?: glob.GlobOptions
): Promise<SearchResult> {
const searchResults: string[] = []
const globber = await glob.create(
searchPath,
globOptions || getDefaultGlobOptions()
)
const rawSearchResults: string[] = await globber.glob()
/*
Directories will be rejected if attempted to be uploaded. This includes just empty
directories so filter any directories out from the raw search results
*/
for (const searchResult of rawSearchResults) {
if (!lstatSync(searchResult).isDirectory()) {
debug(`File:${searchResult} was found using the provided searchPath`)
searchResults.push(searchResult)
} else {
debug(
`Removing ${searchResult} from rawSearchResults because it is a directory`
)
}
}
/*
Only a single search pattern is being included so only 1 searchResult is expected. In the future if multiple search patterns are
simultaneously supported this will change
*/
const searchPaths: string[] = globber.getSearchPaths()
if (searchPaths.length > 1) {
throw new Error('Only 1 search path should be returned')
}
/*
Special case for a single file artifact that is uploaded without a directory or wildcard pattern. The directory structure is
not preserved and the root directory will be the single files parent directory
*/
if (searchResults.length === 1 && searchPaths[0] === searchResults[0]) {
return {
filesToUpload: searchResults,
rootDirectory: dirname(searchResults[0])
}
}
return {
filesToUpload: searchResults,
rootDirectory: searchPaths[0]
}
}

40
src/upload-artifact.ts Normal file
View file

@ -0,0 +1,40 @@
import * as core from '@actions/core'
import {create, UploadOptions} from '@actions/artifact'
import {Inputs, getDefaultArtifactName} from './constants'
import {findFilesToUpload} from './search'
async function run(): Promise<void> {
try {
const name = core.getInput(Inputs.Name, {required: false})
const path = core.getInput(Inputs.Path, {required: true})
const searchResult = await findFilesToUpload(path)
if (searchResult.filesToUpload.length === 0) {
core.warning(
`No files were found for the provided path: ${path}. No artifacts will be uploaded.`
)
} else {
core.info(
`With the provided path, there will be ${searchResult.filesToUpload.length} files uploaded`
)
core.debug(`Root artifact directory is ${searchResult.rootDirectory}`)
const artifactClient = create()
const options: UploadOptions = {
continueOnError: true
}
await artifactClient.uploadArtifact(
name || getDefaultArtifactName(),
searchResult.filesToUpload,
searchResult.rootDirectory,
options
)
core.info('Artifact upload has finished successfully!')
}
} catch (err) {
core.setFailed(err.message)
}
}
run()

17
tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"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"]
}