mirror of
https://github.com/actions/upload-artifact.git
synced 2024-11-23 12:39:02 +00:00
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
This commit is contained in:
parent
8eb149d680
commit
c9be818b8a
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
lib/
|
||||||
|
dist/
|
19
.eslintrc.json
Normal file
19
.eslintrc.json
Normal 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"]
|
||||||
|
}
|
142
.github/workflows/test.yml
vendored
Normal file
142
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
name: Test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- v2-preview
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- v2-preview
|
||||||
|
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
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
lib/
|
||||||
|
__tests__/_temp/
|
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
11
.prettierrc.json
Normal file
11
.prettierrc.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"parser": "typescript"
|
||||||
|
}
|
77
README.md
77
README.md
@ -1,6 +1,6 @@
|
|||||||
# upload-artifact
|
# Upload-Artifact v2 Preview
|
||||||
|
|
||||||
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).
|
||||||
|
|
||||||
@ -8,37 +8,96 @@ See also [download-artifact](https://github.com/actions/download-artifact).
|
|||||||
|
|
||||||
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-preview
|
||||||
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-preview
|
||||||
|
with:
|
||||||
|
name: my-artifact
|
||||||
|
path: path/to/artifact/ # or path/to/artifact
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload using a Wildcard Pattern:
|
||||||
|
```yaml
|
||||||
|
- uses: actions/upload-artifact@v2-preview
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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-preview
|
||||||
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-preview
|
||||||
|
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-preview
|
||||||
|
with:
|
||||||
|
path: world.txt
|
||||||
|
|
||||||
|
- run: echo howdy > extra-file.txt
|
||||||
|
- uses: actions/upload-artifact@v2-preview
|
||||||
|
with:
|
||||||
|
path: extra-file.txt
|
||||||
|
|
||||||
|
- run: echo hello > world.txt
|
||||||
|
- uses: actions/upload-artifact@v2-preview
|
||||||
|
with:
|
||||||
|
path: world.txt
|
||||||
|
```
|
||||||
|
With the following example, the available artifact (named `artifact`) would contain both `world.txt` (`hello`) and `extra-file.txt` (`howdy`).
|
||||||
|
|
||||||
## 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/>
|
||||||
<img src="https://user-images.githubusercontent.com/16109154/72556687-20235a80-386d-11ea-9e2a-b534faa77083.png" width="375" height="140">
|
<img src="https://user-images.githubusercontent.com/16109154/72556687-20235a80-386d-11ea-9e2a-b534faa77083.png" width="375" height="140">
|
||||||
|
|
||||||
There is a trash can 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 trash can 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.
|
||||||
|
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
289
__tests__/search.test.ts
Normal file
289
__tests__/search.test.ts
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
@ -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'
|
8314
dist/index.js
vendored
Normal file
8314
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
jest.config.js
Normal file
12
jest.config.js
Normal 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
|
||||||
|
}
|
6671
package-lock.json
generated
Normal file
6671
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
Normal file
51
package.json
Normal 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.2.0",
|
||||||
|
"@actions/core": "^1.2.3",
|
||||||
|
"@actions/glob": "^0.1.0",
|
||||||
|
"@actions/io": "^1.0.2",
|
||||||
|
"@types/jest": "^25.1.4",
|
||||||
|
"@types/node": "^12.12.30",
|
||||||
|
"@typescript-eslint/parser": "^2.23.0",
|
||||||
|
"@zeit/ncc": "^0.20.5",
|
||||||
|
"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.1.0",
|
||||||
|
"jest-circus": "^25.1.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"ts-jest": "^25.2.1",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
|
}
|
||||||
|
}
|
8
src/constants.ts
Normal file
8
src/constants.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export enum Inputs {
|
||||||
|
Name = 'name',
|
||||||
|
Path = 'path'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultArtifactName(): string {
|
||||||
|
return 'artifact'
|
||||||
|
}
|
69
src/search.ts
Normal file
69
src/search.ts
Normal 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
40
src/upload-artifact.ts
Normal 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
17
tsconfig.json
Normal 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"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user