Compare commits

...

101 Commits

Author SHA1 Message Date
Daniel Kennedy
2aa2914a67 eslint: don't allow common-js imports 2026-02-25 13:49:28 -05:00
Daniel Kennedy
c36c5fbf26 More licensed fixes 2026-02-25 13:44:14 -05:00
Daniel Kennedy
8c63b6d07a Try fixing licenced issues 2026-02-25 13:30:34 -05:00
Daniel Kennedy
0508a90968 Bump minimatch to 10.1.1 2026-02-25 13:24:55 -05:00
Daniel Kennedy
a2008cd1d8 Cache licenses 2026-02-25 13:18:03 -05:00
Daniel Kennedy
7d7cf5541f Bump the package version 2026-02-25 13:13:12 -05:00
Daniel Kennedy
56b3ba9990 Fix linting issues 2026-02-25 13:12:50 -05:00
Daniel Kennedy
d025a8c768 Push build files 2026-02-25 13:08:22 -05:00
Daniel Kennedy
15be44e9f1 CI: bump node versions 2026-02-25 13:05:51 -05:00
Daniel Kennedy
f119f587c2 Upgrade the module to ESM and bump dependencies 2026-02-25 12:57:27 -05:00
Bassem Dghaidi
47309c993a Merge pull request #754 from actions/Link-/add-proxy-integration-tests
Add proxy integration test
2026-01-30 16:47:01 +01:00
Bassem Dghaidi
02a8460834 Add proxy integration test 2026-01-30 02:29:30 -08:00
Salman Chishti
b7c566a772 Merge pull request #745 from actions/upload-artifact-v6-release
prepare release v6.0.0 for Node.js 24 support
2025-12-12 17:58:04 +00:00
Salman Muin Kayser Chishti
e516bc8500 docs: correct description of Node.js 24 support in README 2025-12-12 17:41:47 +00:00
Salman Muin Kayser Chishti
ddc45ed9bc docs: update README to correct action name for Node.js 24 support 2025-12-12 17:38:00 +00:00
Salman Muin Kayser Chishti
615b319bd2 chore: release v6.0.0 for Node.js 24 support
- Update to Node.js 24 runtime (runs.using: node24)
- Bump version to 6.0.0
- Update README with v6 release notes
- Rebuild dist
2025-12-12 17:31:54 +00:00
Salman Chishti
017748b48f Merge pull request #744 from actions/fix-storage-blob
fix: update @actions/artifact for Node.js 24 punycode deprecation
2025-12-12 17:15:18 +00:00
Salman Muin Kayser Chishti
38d4c7997f chore: rebuild dist 2025-12-12 16:46:49 +00:00
Salman Muin Kayser Chishti
7d27270e0c chore: add missing license cache files for @actions/core, @actions/io, and minimatch 2025-12-12 16:43:44 +00:00
Salman Muin Kayser Chishti
5f643d3c94 chore: update license files for @actions/artifact@5.0.1 dependencies 2025-12-12 16:39:57 +00:00
Salman Muin Kayser Chishti
1df1684032 chore: update package-lock.json with @actions/artifact@5.0.1 2025-12-12 16:39:51 +00:00
Salman Muin Kayser Chishti
b5b1a91840 fix: update @actions/artifact to ^5.0.0 for Node.js 24 punycode fix
Updates @actions/artifact dependency to use the published npm version
which includes @azure/storage-blob ^12.29.1 that fixes the punycode
deprecation warning on Node.js 24.
2025-12-12 16:39:45 +00:00
Salman Muin Kayser Chishti
184708f738 fix: update @actions/artifact with storage-blob fix for Node.js 24 punycode deprecation 2025-12-12 15:06:59 +00:00
Salman Chishti
b340c4ff46 Merge pull request #719 from actions/node24
Upload Artifact Node 24 support
2025-12-12 14:39:28 +00:00
Salman Muin Kayser Chishti
867bba2a6b chore: add missing eslint plugins and update to compatible versions 2025-12-12 09:08:26 +00:00
Salman Muin Kayser Chishti
471b053ce1 chore: add eslint-plugin-prettier and upgrade prettier to v3 2025-12-12 09:02:29 +00:00
Salman Muin Kayser Chishti
0d59ab325a chore: add unversioned license files for @actions/core, @actions/io, and minimatch 2025-12-12 09:00:21 +00:00
Salman Muin Kayser Chishti
38cbf00a3a chore: rebuild dist files 2025-12-12 08:55:51 +00:00
Salman Muin Kayser Chishti
65c405260d fix: update license files for @bufbuild/protobuf and sax 2025-12-12 08:55:13 +00:00
Salman Muin Kayser Chishti
985108560d fix: use published @actions/artifact@5.0.0 from npm 2025-12-12 08:52:57 +00:00
Salman Muin Kayser Chishti
0d8ecc924d Merge origin/main and resolve conflicts for node24
- Updated @actions/artifact to ^5.0.0
- Updated @actions/core to ^2.0.0
- Regenerated dist files and license cache
2025-12-12 08:32:24 +00:00
Salman Muin Kayser Chishti
75274a8f39 chore: update package-lock.json 2025-12-11 23:05:12 +00:00
Salman Muin Kayser Chishti
f77a7e9888 chore: regenerate license cache for updated dependencies 2025-12-11 23:05:06 +00:00
Salman Muin Kayser Chishti
bed0e56780 chore: add license text for packages with missing licenses
- @bufbuild/protobuf: Apache-2.0 (from upstream LICENSE file)
- buffers: marked as reviewed (no license in npm)
- chainsaw: MIT/X11 (from npm registry)
2025-12-11 23:05:00 +00:00
Salman Muin Kayser Chishti
91ba4ec18c chore: update .licensed.yml config for node24 dependencies
- Add 0bsd and blueoak-1.0.0 to allowed licenses
- Add reviewed packages for dependencies with non-standard licenses
- Enable UTF-8 encoding
2025-12-11 23:04:52 +00:00
Salman Muin Kayser Chishti
87458809df chore: update dependencies to use published npm packages 2025-12-11 19:36:14 +00:00
Salman Muin Kayser Chishti
8b8de53667 chore: use local @actions/core, glob, io packages 2025-12-09 11:55:30 +00:00
Daniel Kennedy
330a01c490 Merge pull request #734 from actions/danwkennedy/prepare-5.0.0
Prepare `v5.0.0`
2025-10-24 14:15:34 -04:00
Daniel Kennedy
03f2824452 Update github.dep.yml 2025-10-24 14:06:12 -04:00
Daniel Kennedy
905a1ecb59 Prepare v5.0.0 2025-10-24 14:01:51 -04:00
Zachary Taylor
2d9f9cdfa9 Merge pull request #725 from patrikpolyak/patch-1
Update GHES guidance to include reference to Node 20 version
2025-10-23 10:20:12 -04:00
Salman Muin Kayser Chishti
d2e7727e56 update dist 2025-10-15 15:20:28 +01:00
Patrik Polyak
9687587dec Merge branch 'main' into patch-1 2025-10-01 11:18:18 +02:00
Salman Muin Kayser Chishti
bdfe531d8f licence updates 2025-09-29 14:50:01 +01:00
Salman Muin Kayser Chishti
98d3bf521e upadate spacing 2025-09-29 14:48:36 +01:00
Salman Muin Kayser Chishti
48ddad3e02 Update to use Node 24 with local artifact package and security fixes 2025-09-29 14:44:42 +01:00
Yang Cao
2848b2cda0 Merge pull request #727 from danwkennedy/patch-1
Readme: spell out the first use of GHES
2025-09-24 09:57:02 -04:00
Daniel Kennedy
9b511775fd Spell out the first use of GHES 2025-09-24 09:42:45 -04:00
Patrik Polyak
cd231ca1ed Update GHES guidance to include reference to Node 20 version
`v3` uses Node 16 which is deprecated. `v3-node20` has been available for a while which uses Node 20 so we should reflect this in the documentation.
2025-09-15 12:12:01 +02:00
Salman Chishti
6c33d18d0a update to use node 24 2025-07-25 11:27:43 +00:00
Ben De St Paer-Gotch
de65e23aa2 Merge pull request #712 from actions/nebuk89-patch-1
Update README.md
2025-06-17 12:09:38 +01:00
Ben De St Paer-Gotch
8747d8cd76 Update README.md 2025-06-16 10:11:27 +01:00
Rob Herley
6027e3dd17 Merge pull request #681 from GhadimiR/patch-1
Update README.md
2025-03-25 09:24:05 -04:00
Ryan Ghadimi
d7079ed267 Merge branch 'main' into patch-1 2025-03-25 09:29:28 +00:00
Yang Cao
ea165f8d65 Merge pull request #685 from salmanmkc/salmanmkc/3-new-upload-artifacts-release
Update to use artifact 2.3.2 package & prepare for new upload-artifact release
2025-03-19 10:34:59 -07:00
Salman Chishti
08396203c1 Prepare for new release of actions/upload-artifact with new toolkit cache version 2025-03-19 05:38:34 -07:00
Ryan Ghadimi
99ec7df36b Update README.md 2025-03-14 15:03:00 +00:00
Yang Cao
4cec3d8aa0 Merge pull request #673 from actions/yacaovsnc/artifact_2.2.2
Update to use artifact 2.2.2 package
2025-02-21 13:56:39 -05:00
Yang Cao
e9fad966cc license cache update for artifact 2025-02-21 13:16:50 -05:00
Yang Cao
b26fd06e9d Update to use artifact 2.2.2 package 2025-02-21 13:05:59 -05:00
Yang Cao
65c4c4a1dd Merge pull request #662 from actions/yacaovsnc/add_variable_for_concurrency_and_timeout
Expose env vars to control concurrency and timeout
2025-01-09 10:32:47 -05:00
Yang Cao
0207619228 move files back to satisfy licensed ci 2025-01-09 10:18:40 -05:00
Yang Cao
1ecca81102 licensed cache updates 2025-01-09 09:57:34 -05:00
Yang Cao
97422693d3 Expose env vars to controll concurrency and timeout 2025-01-09 14:41:08 +00:00
Rob Herley
6f51ac03b9 Merge pull request #656 from bdehamer/bdehamer/artifact-digest
Add new `artifact-digest` output
2024-12-17 17:02:00 -05:00
Brian DeHamer
c40c16d999 add new artifact-digest output
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2024-12-17 13:48:06 -08:00
Brian DeHamer
735efb4a0a bump @actions/artifact from 2.1.11 to 2.2.0
Signed-off-by: Brian DeHamer <bdehamer@github.com>
2024-12-17 12:10:23 -08:00
Josh Gross
184d73b71b Merge pull request #578 from hamirmahal/fix/deprecated-nodejs-usage-in-action
fix: deprecated `Node.js` version in action
2024-10-09 13:44:23 -04:00
Josh Gross
b4a0a984a0 Merge branch 'main' into fix/deprecated-nodejs-usage-in-action 2024-10-09 13:29:53 -04:00
Josh Gross
b4b15b8c7c Merge pull request #632 from actions/joshmgross/undo-dependency-changes
Undo indirect dependency updates from #627
2024-10-09 13:16:31 -04:00
Josh Gross
92b01ebffa Undo indirect dependency updates from #627 2024-10-09 12:35:22 -04:00
Rob Herley
84480863f2 Merge pull request #627 from actions/robherley/v4.4.2
Bump `@actions/artifact` to 2.1.11
2024-10-08 14:15:05 -04:00
Rob Herley
b1d4642b69 add explicit relative and absolute symlinks to workflow 2024-10-08 13:39:45 -04:00
Rob Herley
d50e66084c bump version 2024-10-08 13:35:54 -04:00
Rob Herley
aabe6f8050 build with @actions/artifact v2.1.11 2024-10-08 12:46:18 -04:00
Rob Herley
604373da63 Merge pull request #625 from actions/robherley/artifact-2.1.10
Update @actions/artifact to latest version, includes symlink and timeout fixes
2024-10-07 11:46:25 -04:00
Rob Herley
0150148bdf paste right core version 2024-10-07 09:57:16 -04:00
Rob Herley
a009b25faa update licenses 2024-10-07 09:55:37 -04:00
Rob Herley
9f6f6f402e update @actions/core and @actions/artifact to latest versions 2024-10-07 09:45:59 -04:00
Joel Ambass
3eadd8b791 Merge pull request #621 from actions/Jcambass-patch-1
Add workflow file for publishing releases to immutable action package
2024-09-26 08:20:21 +02:00
Joel Ambass
aeba9f7961 Add workflow file for publishing releases to immutable action package
This workflow file publishes new action releases to the immutable action package of the same name as this repo.

This is part of the Immutable Actions project which is not yet fully released to the public. First party actions like this one are part of our initial testing of this feature.
2024-09-24 15:53:26 +02:00
Josh Gross
b18b1d32f3 Merge pull request #607 from actions/joshmgross/hidden-files-readme
Add a section about hidden files
2024-09-02 10:39:50 -04:00
Josh Gross
d7c12077c4 Add a section about hidden files 2024-09-02 10:30:03 -04:00
Josh Gross
50769540e7 Merge pull request #598 from actions/joshmgross/exclude-hidden-files
Exclude hidden files by default
2024-08-29 10:38:39 -04:00
Josh Gross
d52396ac5d Add a warning about enabling include-hidden-files 2024-08-15 19:45:59 -04:00
Josh Gross
710f362075 Remove "merged" from include-hidden-files input description 2024-08-15 19:39:39 -04:00
Josh Gross
3b315f26f6 npm run release again 🙂 2024-08-15 17:36:20 -04:00
Josh Gross
3be2180eb7 Remove another trailing comma 2024-08-15 17:35:00 -04:00
Josh Gross
453e8d0a40 Update glob license 2024-08-15 17:31:21 -04:00
Josh Gross
0a398c1480 npm run release 2024-08-15 17:30:44 -04:00
Josh Gross
a0c40cf602 Update to latest @actions/glob and fix tests 2024-08-15 17:26:43 -04:00
Josh Gross
acb59e4776 lint 2024-08-15 16:31:57 -04:00
Josh Gross
cb6558bb10 Exclude hidden files by default 2024-08-15 16:22:09 -04:00
Rob Herley
834a144ee9 Merge pull request #594 from actions/robherley/4.3.6
Revert to @actions/artifact 2.1.8
2024-08-06 10:41:08 -04:00
Rob Herley
134dcf33c0 v4.3.6 2024-08-06 10:24:34 -04:00
Rob Herley
73a0b9c954 revert back to @actions/artifact 2.1.8 2024-08-06 10:23:43 -04:00
Rob Herley
89ef406dd8 Merge pull request #588 from actions/robherley/4.3.5
Bump @actions/artifact to v2.1.9
2024-08-02 09:59:06 -04:00
Rob Herley
23d796df36 license updates 2024-08-01 15:50:32 -04:00
Rob Herley
e445c64bc2 bump @actions/artifact to v2.1.9 2024-08-01 15:46:49 -04:00
Hamir Mahal
a954e16c38 fix: deprecated Node.js version in action 2024-06-17 13:25:23 -07:00
Hamir Mahal
a1d85e775a ci: update CodeQL Action before deprecation 2024-06-17 13:00:34 -07:00
47 changed files with 154358 additions and 166772 deletions

View File

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

View File

@@ -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"]
}

View File

@@ -24,10 +24,10 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup Node 20
- name: Setup Node 24
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 24.x
cache: 'npm'
- name: Install dependencies

View File

@@ -17,11 +17,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -29,7 +29,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -43,4 +43,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

View File

@@ -0,0 +1,20 @@
name: 'Publish Immutable Action Version'
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
packages: write
steps:
- name: Checking out
uses: actions/checkout@v4
- name: Publish
id: publish
uses: actions/publish-immutable-action@0.0.3

114
.github/workflows/test-proxy.yml vendored Normal file
View File

@@ -0,0 +1,114 @@
name: Test Proxy
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
permissions:
contents: read
jobs:
# End to end upload with proxy
test-proxy-upload:
runs-on: ubuntu-latest
container:
image: ubuntu:latest
options: --cap-add=NET_ADMIN
services:
squid-proxy:
image: ubuntu/squid:latest
ports:
- 3128:3128
env:
http_proxy: http://squid-proxy:3128
https_proxy: http://squid-proxy:3128
steps:
- name: Wait for proxy to be ready
shell: bash
run: |
echo "Waiting for squid proxy to be ready..."
echo "Resolving squid-proxy hostname:"
getent hosts squid-proxy || echo "DNS resolution failed"
for i in $(seq 1 30); do
if (echo > /dev/tcp/squid-proxy/3128) 2>/dev/null; then
echo "Proxy is ready!"
exit 0
fi
echo "Attempt $i: Proxy not ready, waiting..."
sleep 2
done
echo "Proxy failed to become ready"
exit 1
env:
http_proxy: ""
https_proxy: ""
- name: Install dependencies
run: |
apt-get update
apt-get install -y iptables curl
- name: Verify proxy is working
run: |
echo "Testing proxy connectivity..."
curl -s -o /dev/null -w "%{http_code}" --proxy http://squid-proxy:3128 http://github.com || true
echo "Proxy verification complete"
- name: Block direct traffic (enforce proxy usage)
run: |
# Get the squid-proxy container IP
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
echo "Proxy IP: $PROXY_IP"
# Allow loopback traffic
iptables -A OUTPUT -o lo -j ACCEPT
# Allow traffic to the proxy container
iptables -A OUTPUT -d $PROXY_IP -j ACCEPT
# Allow established connections
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow DNS (needed for initial resolution)
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
# Block all other outbound traffic (HTTP/HTTPS)
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
# Log the iptables rules for debugging
iptables -L -v -n
- name: Verify direct HTTPS is blocked
run: |
echo "Testing that direct HTTPS requests fail..."
if curl --noproxy '*' -s --connect-timeout 5 https://github.com > /dev/null 2>&1; then
echo "ERROR: Direct HTTPS request succeeded - blocking is not working!"
exit 1
else
echo "SUCCESS: Direct HTTPS request was blocked as expected"
fi
echo "Testing that HTTPS through proxy succeeds..."
if curl --proxy http://squid-proxy:3128 -s --connect-timeout 10 https://github.com > /dev/null 2>&1; then
echo "SUCCESS: HTTPS request through proxy succeeded"
else
echo "ERROR: HTTPS request through proxy failed!"
exit 1
fi
- name: Checkout
uses: actions/checkout@v4
- name: Create artifact file
run: |
mkdir -p test-artifacts
echo "Proxy test artifact - $GITHUB_RUN_ID" > test-artifacts/proxy-test.txt
echo "Random data: $RANDOM $RANDOM $RANDOM" >> test-artifacts/proxy-test.txt
cat test-artifacts/proxy-test.txt
- name: Upload artifact through proxy
uses: ./
with:
name: 'Proxy-Test-Artifact-${{ github.run_id }}'
path: test-artifacts/proxy-test.txt

View File

@@ -25,10 +25,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node 20
- name: Setup Node 24
uses: actions/setup-node@v4
with:
node-version: 20.x
node-version: 24.x
cache: 'npm'
- name: Install dependencies
@@ -46,14 +46,19 @@ jobs:
- name: Test
run: npm run test
# Test end-to-end by uploading two artifacts and then downloading them
# Test end-to-end by uploading a few 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
mkdir -p symlink/
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 "Hello from a symlinked file" > symlink/original.txt
ln -s $(pwd)/symlink/original.txt symlink/abs.txt
ln -s original.txt symlink/rel.txt
shell: bash
# Upload a single file artifact
- name: 'Upload artifact #1'
@@ -79,6 +84,14 @@ jobs:
path/to/dir-[23]/*
!path/to/dir-3/*.txt
- name: 'Upload symlinked artifact'
uses: ./
with:
name: 'Symlinked-Artifact-${{ matrix.runs-on }}'
path: |
symlink/abs.txt
symlink/rel.txt
# Download Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1'
uses: actions/download-artifact@v4
@@ -141,6 +154,34 @@ jobs:
}
shell: pwsh
- name: 'Download symlinked artifact'
uses: actions/download-artifact@v4
with:
name: 'Symlinked-Artifact-${{ matrix.runs-on }}'
path: from/symlink
- name: 'Verify symlinked artifact'
run: |
$abs = "from/symlink/abs.txt"
if(!(Test-Path -path $abs))
{
Write-Error "Expected file does not exist"
}
if(!((Get-Content $abs) -ceq "Hello from a symlinked file"))
{
Write-Error "File contents of downloaded artifact are incorrect"
}
$rel = "from/symlink/rel.txt"
if(!(Test-Path -path $rel))
{
Write-Error "Expected file does not exist"
}
if(!((Get-Content $rel) -ceq "Hello from a symlinked file"))
{
Write-Error "File contents of downloaded artifact are incorrect"
}
shell: pwsh
- name: 'Alter file 1 content'
run: |
echo "This file has changed" > path/to/dir-1/file1.txt

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/
lib/
__tests__/_temp/
__tests__/_temp/
.DS_Store

View File

@@ -1,6 +1,9 @@
sources:
npm: true
# Force UTF-8 encoding
encoding: 'utf-8'
allowed:
- apache-2.0
- bsd-2-clause
@@ -9,7 +12,30 @@ allowed:
- mit
- cc0-1.0
- unlicense
- 0bsd
- blueoak-1.0.0
reviewed:
npm:
- fs.realpath
- fs.realpath
- "@actions/http-client" # MIT
- "@bufbuild/protobuf" # Apache-2.0
- "@pkgjs/parseargs" # MIT
- "@protobuf-ts/runtime" # Apache-2.0
- argparse # Python-2.0
- buffers # MIT
- chainsaw # MIT
- color-convert # MIT
- ieee754 # BSD-3-Clause
- lodash # MIT
- mdurl # MIT
- neo-async # MIT
- package-json-from-dist # ISC
- readable-stream # MIT
- sax # ISC
- source-map # BSD-3-Clause
- string_decoder # MIT
- traverse # MIT
- tslib # 0BSD
- uglify-js # BSD-2-Clause
- wordwrap # MIT

View File

@@ -1,9 +1,9 @@
---
name: "@actions/artifact"
version: 2.1.8
version: 6.2.0
type: npm
summary:
homepage:
summary: Actions artifact lib
homepage: https://github.com/actions/toolkit/tree/main/packages/artifact
license: mit
licenses:
- sources: LICENSE.md

View File

@@ -1,9 +1,9 @@
---
name: "@actions/core"
version: 1.10.1
version: 3.0.0
type: npm
summary:
homepage:
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core
license: mit
licenses:
- sources: LICENSE.md

View File

@@ -1,9 +1,9 @@
---
name: "@actions/github"
version: 6.0.0
version: 9.0.0
type: npm
summary:
homepage:
summary: Actions github lib
homepage: https://github.com/actions/toolkit/tree/main/packages/github
license: mit
licenses:
- sources: LICENSE.md

View File

@@ -1,9 +1,9 @@
---
name: "@actions/glob"
version: 0.3.0
version: 0.6.1
type: npm
summary:
homepage:
summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
license: mit
licenses:
- sources: LICENSE.md

View File

@@ -1,9 +1,9 @@
---
name: "@actions/io"
version: 1.1.2
version: 3.0.2
type: npm
summary:
homepage:
summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io
license: mit
licenses:
- sources: LICENSE.md

View File

@@ -1,26 +1,66 @@
---
name: minimatch
version: 9.0.3
version: 10.2.4
type: npm
summary:
homepage:
license: isc
license: blueoak-1.0.0
licenses:
- sources: LICENSE
- sources: LICENSE.md
text: |
The ISC License
# Blue Oak Model License
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
Version 1.0.0
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
## Purpose
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**
notices: []

36
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Jest Tests",
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
"args": [
"--runInBand",
"--testTimeout",
"10000"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true
},
{
"type": "node",
"request": "launch",
"name": "Debug Current Test File",
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js",
"args": [
"--runInBand",
"--testTimeout",
"10000",
"${relativeFile}"
],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true
}
]
}

View File

@@ -11,6 +11,7 @@ Upload [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/st
See also [download-artifact](https://github.com/actions/download-artifact).
- [`@actions/upload-artifact`](#actionsupload-artifact)
- [v6 - What's new](#v6---whats-new)
- [v4 - What's new](#v4---whats-new)
- [Improvements](#improvements)
- [Breaking Changes](#breaking-changes)
@@ -38,10 +39,19 @@ See also [download-artifact](https://github.com/actions/download-artifact).
- [Where does the upload go?](#where-does-the-upload-go)
## v6 - What's new
> [!IMPORTANT]
> actions/upload-artifact@v6 now runs on Node.js 24 (`runs.using: node24`) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.
### Node.js 24
This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.
## v4 - What's new
> [!IMPORTANT]
> upload-artifact@v4+ is not currently supported on GHES yet. If you are on GHES, you must use [v3](https://github.com/actions/upload-artifact/releases/tag/v3).
> upload-artifact@v4+ is not currently supported on GitHub Enterprise Server (GHES) yet. If you are on GHES, you must use [v3](https://github.com/actions/upload-artifact/releases/tag/v3) (Node 16) or [v3-node20](https://github.com/actions/upload-artifact/releases/tag/v3-node20) (Node 20).
The release of upload-artifact@v4 and download-artifact@v4 are major changes to the backend architecture of Artifacts. They have numerous performance and behavioral improvements.
@@ -64,9 +74,28 @@ There is also a new sub-action, `actions/upload-artifact/merge`. For more info,
Due to how Artifacts are created in this new version, it is no longer possible to upload to the same named Artifact multiple times. You must either split the uploads into multiple Artifacts with different names, or only upload once. Otherwise you _will_ encounter an error.
3. Limit of Artifacts for an individual job. Each job in a workflow run now has a limit of 500 artifacts.
4. With `v4.4` and later, hidden files are excluded by default.
For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
## Note
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
We are taking the following steps to better direct requests related to GitHub Actions, including:
1. We will be directing questions and support requests to our [Community Discussions area](https://github.com/orgs/community/discussions/categories/actions)
2. High Priority bugs can be reported through Community Discussions or you can report these to our support team https://support.github.com/contact/bug-report.
3. Security Issues should be handled as per our [security.md](SECURITY.md).
We will still provide security updates for this project and fix major breaking changes during this time.
You are welcome to still raise bugs in this repo.
## Usage
### Inputs
@@ -107,6 +136,12 @@ For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
# Does not fail if the artifact does not exist.
# Optional. Default is 'false'
overwrite:
# Whether to include hidden files in the provided path in the artifact
# The file contents of any hidden files in the path should be validated before
# enabled this to avoid uploading sensitive information.
# Optional. Default is 'false'
include-hidden-files:
```
### Outputs
@@ -115,6 +150,7 @@ For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
| - | - | - |
| `artifact-id` | GitHub ID of an Artifact, can be used by the REST API | `1234` |
| `artifact-url` | URL to download an Artifact. Can be used in many scenarios such as linking to artifacts in issues or pull requests. Users must be logged-in in order for this URL to work. This URL is valid as long as the artifact has not expired or the artifact, run or repository have not been deleted | `https://github.com/example-org/example-repo/actions/runs/1/artifacts/1234` |
| `artifact-digest` | SHA-256 digest of an Artifact | 0fde654d4c6e659b45783a725dc92f1bfb0baa6c2de64b34e814dc206ff4aaaf |
## Examples
@@ -410,6 +446,28 @@ jobs:
overwrite: true
```
### Uploading Hidden Files
By default, hidden files are ignored by this action to avoid unintentionally uploading sensitive information.
If you need to upload hidden files, you can use the `include-hidden-files` input.
Any files that contain sensitive information that should not be in the uploaded artifact can be excluded
using the `path`:
```yaml
- uses: actions/upload-artifact@v4
with:
name: my-artifact
include-hidden-files: true
path: |
path/output/
!path/output/.production.env
```
Hidden files are defined as any file beginning with `.` or files within folders beginning with `.`.
On Windows, files and directories with the hidden attribute are not considered hidden files unless
they have the `.` prefix.
## Limitations
### Number of Artifacts
@@ -443,8 +501,9 @@ If you must preserve permissions, you can `tar` all of your files together befor
At the bottom of the workflow summary page, there is a dedicated section for artifacts. Here's a screenshot of something you might see:
<img src="https://user-images.githubusercontent.com/16109154/103645952-223c6880-4f59-11eb-8268-8dca6937b5f9.png" width="700" height="300">
<img src="https://github.com/user-attachments/assets/bcb7120f-f445-4a3e-9596-77f85f7e0af0" width="700" height="300">
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.
The size of the artifact is denoted in bytes. The displayed artifact size denotes the size of the zip that `upload-artifact` creates during upload.
The size of the artifact is denoted in bytes. The displayed artifact size denotes the size of the zip that `upload-artifact` creates during upload. The Digest column will display the SHA256 digest of the artifact being uploaded.

View File

@@ -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)
}
})
})

View File

@@ -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(
@@ -61,13 +90,24 @@ const lonelyFilePath = path.join(
'lonely-file.txt'
)
const hiddenFile = path.join(root, '.hidden-file.txt')
const fileInHiddenFolderPath = path.join(
root,
'.hidden-folder',
'folder-in-hidden-folder',
'file.txt'
)
const fileInHiddenFolderInFolderA = path.join(
root,
'folder-a',
'.hidden-folder-in-folder-a',
'file.txt'
)
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)
@@ -93,6 +133,14 @@ describe('Search', () => {
recursive: true
})
await fs.mkdir(
path.join(root, '.hidden-folder', 'folder-in-hidden-folder'),
{recursive: true}
)
await fs.mkdir(path.join(root, 'folder-a', '.hidden-folder-in-folder-a'), {
recursive: true
})
await fs.writeFile(searchItem1Path, 'search item1 file')
await fs.writeFile(searchItem2Path, 'search item2 file')
await fs.writeFile(searchItem3Path, 'search item3 file')
@@ -110,37 +158,13 @@ describe('Search', () => {
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
*/
await fs.writeFile(hiddenFile, 'hidden file')
await fs.writeFile(fileInHiddenFolderPath, 'file in hidden directory')
await fs.writeFile(fileInHiddenFolderInFolderA, 'file in hidden directory')
})
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)
@@ -149,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',
@@ -168,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)
@@ -177,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)
@@ -185,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)
@@ -204,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)
@@ -224,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)
@@ -253,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',
@@ -287,11 +311,11 @@ 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')
const searchPaths = searchPath1 + '\n' + searchPath2
const searchPaths = `${searchPath1}\n${searchPath2}`
const searchResult = await findFilesToUpload(searchPaths)
expect(searchResult.rootDirectory).toEqual(root)
@@ -311,13 +335,13 @@ 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')
// negating the third search path
const searchPaths = searchPath1 + '\n' + searchPath2 + '\n!' + searchPath3
const searchPaths = `${searchPath1}\n${searchPath2}\n!${searchPath3}`
const searchResult = await findFilesToUpload(searchPaths)
expect(searchResult.rootDirectory).toEqual(root)
@@ -331,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
@@ -352,4 +376,24 @@ describe('Search', () => {
)
expect(searchResult.filesToUpload.includes(lonelyFilePath)).toEqual(true)
})
test('Hidden files ignored by default', async () => {
const searchPath = path.join(root, '**/*')
const searchResult = await findFilesToUpload(searchPath)
expect(searchResult.filesToUpload).not.toContain(hiddenFile)
expect(searchResult.filesToUpload).not.toContain(fileInHiddenFolderPath)
expect(searchResult.filesToUpload).not.toContain(
fileInHiddenFolderInFolderA
)
})
test('Hidden files included', async () => {
const searchPath = path.join(root, '**/*')
const searchResult = await findFilesToUpload(searchPath, true)
expect(searchResult.filesToUpload).toContain(hiddenFile)
expect(searchResult.filesToUpload).toContain(fileInHiddenFolderPath)
expect(searchResult.filesToUpload).toContain(fileInHiddenFolderInFolderA)
})
})

View File

@@ -1,9 +1,57 @@
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 +62,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 +75,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,27 +92,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
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,
@@ -80,10 +122,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,
@@ -91,26 +133,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,
@@ -118,14 +159,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,
@@ -133,12 +174,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
})
@@ -150,12 +191,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
})
@@ -167,12 +208,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
})
@@ -184,46 +225,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`
)

View File

@@ -40,6 +40,11 @@ inputs:
If false, the action will fail if an artifact for the given name already exists.
Does not fail if the artifact does not exist.
default: 'false'
include-hidden-files:
description: >
If true, hidden files will be included in the artifact.
If false, hidden files will be excluded from the artifact.
default: 'false'
outputs:
artifact-id:
@@ -56,6 +61,9 @@ outputs:
This URL will be valid for as long as the artifact exists and the workflow run and repository exists. Once an artifact has expired this URL will no longer work.
Common uses cases for such a download URL can be adding download links to artifacts in descriptions or comments on pull requests or issues.
artifact-digest:
description: >
SHA-256 digest for the artifact that was just uploaded. Empty if the artifact upload failed.
runs:
using: 'node20'
using: 'node24'
main: 'dist/upload/index.js'

153633
dist/merge/index.js vendored

File diff suppressed because one or more lines are too long

3
dist/merge/package.json vendored Normal file
View File

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

151134
dist/upload/index.js vendored

File diff suppressed because one or more lines are too long

3
dist/upload/package.json vendored Normal file
View File

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

View File

@@ -4,6 +4,7 @@
- [Multiple uploads to the same named Artifact](#multiple-uploads-to-the-same-named-artifact)
- [Overwriting an Artifact](#overwriting-an-artifact)
- [Merging multiple artifacts](#merging-multiple-artifacts)
- [Hidden files](#hidden-files)
Several behavioral differences exist between Artifact actions `v3` and below vs `v4`. This document outlines common scenarios in `v3`, and how they would be handled in `v4`.
@@ -189,21 +190,59 @@ jobs:
- name: Create a File
run: echo "hello from ${{ matrix.runs-on }}" > file-${{ matrix.runs-on }}.txt
- name: Upload Artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
- name: all-my-files
+ name: my-artifact-${{ matrix.runs-on }}
path: file-${{ matrix.runs-on }}.txt
+ merge:
+ runs-on: ubuntu-latest
+ needs: upload
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v4
+ with:
+ name: all-my-files
+ pattern: my-artifact-*
+ merge:
+ runs-on: ubuntu-latest
+ needs: upload
+ steps:
+ - name: Merge Artifacts
+ uses: actions/upload-artifact/merge@v4
+ with:
+ name: all-my-files
+ pattern: my-artifact-*
```
Note that this will download all artifacts to a temporary directory and reupload them as a single artifact. For more information on inputs and other use cases for `actions/upload-artifact/merge@v4`, see [the action documentation](../merge/README.md).
## Hidden Files
By default, hidden files are ignored by this action to avoid unintentionally uploading sensitive
information.
In versions of this action before v4.4.0, these hidden files were included by default.
If you need to upload hidden files, you can use the `include-hidden-files` input.
```yaml
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Create a Hidden File
run: echo "hello from a hidden file" > .hidden-file.txt
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
path: .hidden-file.txt
```
```diff
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Create a Hidden File
run: echo "hello from a hidden file" > .hidden-file.txt
- name: Upload Artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
path: .hidden-file.txt
+ include-hidden-files: true
```

52
eslint.config.mjs Normal file
View File

@@ -0,0 +1,52 @@
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/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'
}
}
]

View File

@@ -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
View 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
}

View File

@@ -65,6 +65,7 @@ For most cases, this may not be the most efficient solution. See [the migration
| - | - | - |
| `artifact-id` | GitHub ID of an Artifact, can be used by the REST API | `1234` |
| `artifact-url` | URL to download an Artifact. Can be used in many scenarios such as linking to artifacts in issues or pull requests. Users must be logged-in in order for this URL to work. This URL is valid as long as the artifact has not expired or the artifact, run or repository have not been deleted | `https://github.com/example-org/example-repo/actions/runs/1/artifacts/1234` |
| `artifact-digest` | SHA-256 digest of an Artifact | 0fde654d4c6e659b45783a725dc92f1bfb0baa6c2de64b34e814dc206ff4aaaf |
## Examples

View File

@@ -36,6 +36,11 @@ inputs:
If true, the artifacts that were merged will be deleted.
If false, the artifacts will still exist.
default: 'false'
include-hidden-files:
description: >
If true, hidden files will be included in the merged artifact.
If false, hidden files will be excluded from the merged artifact.
default: 'false'
outputs:
artifact-id:
@@ -52,6 +57,9 @@ outputs:
This URL will be valid for as long as the artifact exists and the workflow run and repository exists. Once an artifact has expired this URL will no longer work.
Common uses cases for such a download URL can be adding download links to artifacts in descriptions or comments on pull requests or issues.
artifact-digest:
description: >
SHA-256 digest for the artifact that was just uploaded. Empty if the artifact upload failed.
runs:
using: 'node20'
using: 'node24'
main: '../dist/merge/index.js'

15139
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
{
"name": "upload-artifact",
"version": "4.3.4",
"version": "7.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",
@@ -28,28 +29,35 @@
"url": "https://github.com/actions/upload-artifact/issues"
},
"homepage": "https://github.com/actions/upload-artifact#readme",
"engines": {
"node": ">=24"
},
"dependencies": {
"@actions/artifact": "^2.1.8",
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"@actions/glob": "^0.3.0",
"@actions/io": "^1.1.2",
"minimatch": "^9.0.3"
"@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": "^10.1.1"
},
"devDependencies": {
"@types/jest": "^29.2.5",
"@types/node": "^18.11.18",
"@typescript-eslint/parser": "^5.48.0",
"@vercel/ncc": "^0.36.0",
"concurrently": "^7.6.0",
"eslint": "^8.31.0",
"eslint-plugin-github": "^4.6.0",
"eslint-plugin-jest": "^27.2.0",
"glob": "^8.0.3",
"jest": "^29.3.1",
"jest-circus": "^29.3.1",
"prettier": "^2.8.1",
"ts-jest": "^29.0.3",
"typescript": "^4.9.4"
"@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"
}
}

View File

@@ -5,5 +5,6 @@ export enum Inputs {
SeparateDirectories = 'separate-directories',
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
DeleteMerged = 'delete-merged'
DeleteMerged = 'delete-merged',
IncludeHiddenFiles = 'include-hidden-files'
}

View File

@@ -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)

View File

@@ -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
@@ -10,6 +10,7 @@ export function getInputs(): MergeInputs {
const pattern = core.getInput(Inputs.Pattern, {required: true})
const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories)
const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged)
const includeHiddenFiles = core.getBooleanInput(Inputs.IncludeHiddenFiles)
const inputs = {
name,
@@ -17,7 +18,8 @@ export function getInputs(): MergeInputs {
separateDirectories,
deleteMerged,
retentionDays: 0,
compressionLevel: 6
compressionLevel: 6,
includeHiddenFiles
} as MergeInputs
const retentionDaysStr = core.getInput(Inputs.RetentionDays)

View File

@@ -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
@@ -62,7 +62,10 @@ export async function run(): Promise<void> {
options.compressionLevel = inputs.compressionLevel
}
const searchResult = await findFilesToUpload(tmpDir)
const searchResult = await findFilesToUpload(
tmpDir,
inputs.includeHiddenFiles
)
await uploadArtifact(
inputs.name,

View File

@@ -30,4 +30,9 @@ export interface MergeInputs {
* If false, the artifacts will be merged into the root of the destination.
*/
separateDirectories: boolean
/**
* Whether or not to include hidden files in the artifact
*/
includeHiddenFiles: boolean
}

View File

@@ -11,11 +11,12 @@ export interface SearchResult {
rootDirectory: string
}
function getDefaultGlobOptions(): glob.GlobOptions {
function getDefaultGlobOptions(includeHiddenFiles: boolean): glob.GlobOptions {
return {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true
omitBrokenSymbolicLinks: true,
excludeHiddenFiles: !includeHiddenFiles
}
}
@@ -80,12 +81,12 @@ function getMultiPathLCA(searchPaths: string[]): string {
export async function findFilesToUpload(
searchPath: string,
globOptions?: glob.GlobOptions
includeHiddenFiles?: boolean
): Promise<SearchResult> {
const searchResults: string[] = []
const globber = await glob.create(
searchPath,
globOptions || getDefaultGlobOptions()
getDefaultGlobOptions(includeHiddenFiles || false)
)
const rawSearchResults: string[] = await globber.glob()

View File

@@ -19,6 +19,7 @@ export async function uploadArtifact(
`Artifact ${artifactName} has been successfully uploaded! Final size is ${uploadResponse.size} bytes. Artifact ID is ${uploadResponse.id}`
)
core.setOutput('artifact-id', uploadResponse.id)
core.setOutput('artifact-digest', uploadResponse.digest)
const repository = github.context.repo
const artifactURL = `${github.context.serverUrl}/${repository.owner}/${repository.repo}/actions/runs/${github.context.runId}/artifacts/${uploadResponse.id}`

View File

@@ -5,7 +5,8 @@ export enum Inputs {
IfNoFilesFound = 'if-no-files-found',
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
Overwrite = 'overwrite'
Overwrite = 'overwrite',
IncludeHiddenFiles = 'include-hidden-files'
}
export enum NoFileOptions {

View File

@@ -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)

View File

@@ -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
@@ -9,6 +9,7 @@ export function getInputs(): UploadInputs {
const name = core.getInput(Inputs.Name)
const path = core.getInput(Inputs.Path, {required: true})
const overwrite = core.getBooleanInput(Inputs.Overwrite)
const includeHiddenFiles = core.getBooleanInput(Inputs.IncludeHiddenFiles)
const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
@@ -27,7 +28,8 @@ export function getInputs(): UploadInputs {
artifactName: name,
searchPath: path,
ifNoFilesFound: noFileBehavior,
overwrite: overwrite
overwrite: overwrite,
includeHiddenFiles: includeHiddenFiles
} as UploadInputs
const retentionDaysStr = core.getInput(Inputs.RetentionDays)

View File

@@ -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 {
@@ -24,7 +24,10 @@ async function deleteArtifactIfExists(artifactName: string): Promise<void> {
export async function run(): Promise<void> {
const inputs = getInputs()
const searchResult = await findFilesToUpload(inputs.searchPath)
const searchResult = await findFilesToUpload(
inputs.searchPath,
inputs.includeHiddenFiles
)
if (searchResult.filesToUpload.length === 0) {
// No files were found, different use cases warrant different types of behavior if nothing is found
switch (inputs.ifNoFilesFound) {

View File

@@ -1,4 +1,4 @@
import {NoFileOptions} from './constants'
import {NoFileOptions} from './constants.js'
export interface UploadInputs {
/**
@@ -30,4 +30,9 @@ export interface UploadInputs {
* Whether or not to replace an existing artifact with the same name
*/
overwrite: boolean
/**
* Whether or not to include hidden files in the artifact
*/
includeHiddenFiles: boolean
}

8
tsconfig.eslint.json Normal file
View File

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

View File

@@ -1,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__"]
}