Compare commits

...

200 Commits

Author SHA1 Message Date
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
Rob Herley 0b2256b8c0
Merge pull request #584 from actions/robherley/bump-pkgs
Update @actions/artifact version, bump dependencies
2024-07-05 11:11:13 -04:00
Rob Herley 488dcefb9b
licensed cache 2024-07-05 14:39:15 +00:00
Rob Herley 04c51f5766
ncc 2024-07-05 13:55:51 +00:00
Rob Herley 32a9e276a8
bump @actions/artifact and npm audit 2024-07-05 13:51:02 +00:00
Jonathan Tamsut 552bf3722c new version 2024-04-23 11:22:42 -07:00
eggyhead 79616d2ded
Merge pull request #565 from actions/eggyhead/use-artifact-v2.1.6
updating toolkit artifact dependency to 2.1.6
2024-04-22 09:19:03 -07:00
Your Name 65462800fd updating package version 2024-04-22 08:10:47 -07:00
eggyhead c004fb4bf6
Merge branch 'main' into eggyhead/use-artifact-v2.1.6 2024-04-22 08:08:40 -07:00
Your Name 90aba496fc updating toolkit artifact dependency to 2.1.6 2024-04-22 08:06:58 -07:00
eggyhead b06cde36fc
Merge pull request #563 from actions/eggyhead/release-4.3.2
updating to release 4.3.2
2024-04-18 08:13:48 -07:00
eggyhead 1746f4ab65 Revert "updating to release 4.3.2"
This reverts commit 31685d04a0.

updating to release 4.3.2
2024-04-18 15:05:07 +00:00
eggyhead 31685d04a0 updating to release 4.3.2 2024-04-18 14:56:48 +00:00
eggyhead 18bf333cd2
Merge pull request #562 from actions/eggyhead/update-artifact-v215
updating `@actions/artifact` dependency to v2.1.5 and `@actions/core` to v1.0.1
2024-04-17 15:05:29 -07:00
eggyhead dac413befa update package lock version 2024-04-17 21:24:58 +00:00
eggyhead bb3b4a3cdb updating package version 2024-04-17 20:38:47 +00:00
eggyhead 3e3da837d2 updating artifact and core dependencies 2024-04-17 17:00:25 +00:00
Rob Herley e35774f165
Merge pull request #561 from actions/robherley/deprecation-notice
Update readme with v3/v2/v1 deprecation notice
2024-04-16 17:12:57 -04:00
Rob Herley e63ea677fb
Update readme with v3/v2/v1 deprecation notice
- https://github.blog/changelog/2024-02-13-deprecation-notice-v1-and-v2-of-the-artifact-actions/
- https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/
2024-04-16 16:48:37 -04:00
Rob Herley ef09cdac3e
Merge pull request #523 from andrewakim/andrewakim/migration-readme-fix
Minor fix to the migration readme
2024-02-15 14:06:33 -05:00
Andrew Kim 00e36f94d8
Minor fix to the migration readme 2024-02-15 11:01:40 -08:00
Konrad Pabjan 4c0ff1c489
Update release-new-action-version.yml (#516) 2024-02-05 17:19:59 -05:00
eggyhead 5d5d22a312
Merge pull request #515 from actions/eggyhead/update-artifact-v2.1.1
updating artifact dependency to version 2.1.1
2024-02-05 13:19:54 -08:00
eggyhead f1e993d966 update artifact license 2024-02-05 17:52:45 +00:00
eggyhead 4881bfd3f2 updating dist: 2024-02-05 17:39:17 +00:00
eggyhead a30777e265 @eggyhead
updating artifact dependency to version 2.1.1
2024-02-05 17:31:38 +00:00
Rob Herley 3a8048248f
Merge pull request #511 from actions/robherley/migration-docs-typo
Fix typo with v3 artifact downloads in migration guide
2024-01-31 12:36:26 -05:00
Rob Herley 9d63e3f2f8
Merge branch 'main' into robherley/migration-docs-typo 2024-01-31 12:34:24 -05:00
Rob Herley dfa1ab292d
fix typo with v3 artifact downloads in migration guide 2024-01-31 12:27:48 -05:00
Rob Herley d00351bf69
Merge pull request #509 from markmssd/patch-1
Update limitation of `10` artifacts upload to `500`
2024-01-29 09:38:17 -05:00
Mark Massoud 707f5a7b71
Update limitation of `10` artifacts upload to `500`
According to https://github.com/actions/upload-artifact/issues/470#issuecomment-1885881535, the limitation has been raised from `10` to `500`. This PR simply updates the docs to reflect the change.
2024-01-27 16:21:08 -05:00
Rob Herley 26f96dfa69
Merge pull request #505 from actions/robherley/merge-artifacts
Add sub-action to merge artifacts
2024-01-23 12:39:33 -05:00
Rob Herley 530ed2c9b8
use correct artifact name in test 2024-01-23 11:26:59 -05:00
Rob Herley 53ef6987b3
add additional integration test for merge all behavior 2024-01-23 11:15:52 -05:00
Rob Herley 90b0f8eed8
add workflow to test merging 2024-01-22 22:50:11 -05:00
Rob Herley 199a58f54f
more docs 2024-01-22 22:24:56 -05:00
Rob Herley 3f353f9d9e
ncc 2024-01-22 21:46:33 -05:00
Rob Herley 997fffa355
add merge artifact sub-action 2024-01-22 21:46:12 -05:00
Rob Herley 52899c8c02
Merge pull request #504 from actions/robherley/reorganize
Reorganize upload code in prep for merge logic & add more tests
2024-01-22 16:56:11 -05:00
Rob Herley da58a3f7b2
ncc 2024-01-22 15:58:20 -05:00
Rob Herley 1f64adb853
fix import 2024-01-22 15:52:35 -05:00
Rob Herley 8d531b15a6
reorganize upload code in prep for merge logic, add more tests 2024-01-22 15:49:34 -05:00
Rob Herley 694cdabd8b
Merge pull request #501 from actions/robherley/overwrite-artifact
Ability to overwrite an Artifact
2024-01-18 15:11:31 -05:00
Rob Herley 05d4fe6702
run licensed against version that matches ci 2024-01-18 13:48:50 -05:00
Rob Herley 40b3052821
update readme 2024-01-18 13:44:36 -05:00
Rob Herley 49552fcb82
add overwrite tests to workflow 2024-01-18 13:33:14 -05:00
Rob Herley 79615904cc
licensed cache 2024-01-18 13:32:10 -05:00
Rob Herley 11ff42c7b1
add new overwrite input & docs 2024-01-18 13:31:03 -05:00
Rob Herley 1eb3cb2b3e
Merge pull request #497 from actions/robherley/update-readme-limit
Update readme to reflect new artifact per job limit
2024-01-10 15:47:13 -05:00
Rob Herley 8688a86492
Update readme to reflect new artifact/job limit 2024-01-10 15:23:13 -05:00
Konrad Pabjan 73d8b66ede
Support artifact-url output (#496)
* Support artifact-url output

* update dist/index.js

* Update license

* Simplify output

* Update README
2024-01-10 12:31:08 -05:00
shmuel c320f57948
Update README.md (#492)
Use correct id of step
2024-01-10 10:00:06 -05:00
Rob Herley cf8714cfea
Merge pull request #482 from actions/robherley/add-migration-docs
Add migrations docs
2023-12-18 18:18:28 -05:00
Rob Herley 7f16e37e88
add migrations docs 2023-12-18 18:10:52 -05:00
Rob Herley 353073034f
Merge pull request #468 from actions/robherley/misc-updates
Some cleanup
2023-12-14 15:14:19 -05:00
Rob Herley 6c139afa6f
update imports and old v4-beta references 2023-12-14 15:08:24 -05:00
Rob Herley c7d193f32e
Merge pull request #466 from actions/v4-beta
upload-artifact@v4
2023-12-14 10:04:00 -05:00
Rob Herley 13131bb095
licensed cache 2023-12-13 16:02:47 -05:00
Rob Herley 4a6c273b98
Merge branch 'main' into v4-beta 2023-12-13 15:10:05 -05:00
Rob Herley f391bb91a3
Merge pull request #465 from actions/robherley/v4-documentation
Update documentation for v4
2023-12-13 13:00:16 -05:00
Rob Herley 9653d03c4b
Apply suggestions from code review
Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2023-12-13 12:24:16 -05:00
Rob Herley 875b630764
add limitations section 2023-12-13 11:55:56 -05:00
Rob Herley ecb21463e9
add compression example 2023-12-13 10:17:03 -05:00
Rob Herley 5e7604f84a
trim some repeated info 2023-12-12 23:27:17 -05:00
Rob Herley d6437d0758
naming 2023-12-12 23:24:58 -05:00
Rob Herley 1b56155703
s/v4-beta/v4/g 2023-12-12 23:21:37 -05:00
Rob Herley 6a5077f99b
update docs and bump @actions/artifact 2023-12-12 23:19:52 -05:00
Vallie Joseph 2a6c27a029
Merge pull request #464 from actions/vmjoseph/v4-beta-more-descriptive-annotations
Consume latest @actions/toolkit
2023-12-11 14:32:50 -05:00
Vallie Joseph 9a179cdb8d adding error message improvement 2023-12-11 16:41:05 +00:00
Rob Herley eba2a242da
consume latest @actions/toolkit (#461) 2023-12-07 11:53:07 -08:00
Rob Herley 500e175644
Merge pull request #460 from actions/robherley/consume-latest-toolkit
Consume latest @actions/artifact
2023-12-04 15:47:37 -05:00
Rob Herley 83b889d09d
consume latest @actions/artifact 2023-12-04 15:40:37 -05:00
Rob Herley eeb333dcd2
Merge pull request #453 from actions/robherley/more-v4-improvements
More v4 Improvements: adjustable compression level and tweak concurrency
2023-11-21 10:58:16 -05:00
Rob Herley fa39c94ed2
consume latest @actions/artifact from toolkit 2023-11-20 20:56:30 -05:00
Rob Herley 59ef34ab24
bump for more logs 2023-11-20 11:28:41 -05:00
Rob Herley 20bcd3b8cb
add compression level input 2023-11-20 11:08:50 -05:00
Konrad Pabjan aa5cae10db
Add artifact-id output to v4-beta (#448)
* Add artifact-id to outputs

* ncc

---------

Co-authored-by: Rob Herley <robherley@github.com>
2023-10-26 12:05:31 -04:00
Rob Herley f87a4089c5
Merge pull request #446 from robherley/v4-beta
[v4-beta] Bump linked artifact toolkit pkg
2023-10-16 14:56:45 -04:00
Rob Herley 50ad273a1a
bump linked artifact toolkit pkg 2023-10-16 14:30:52 -04:00
Bethany a8a3f3ad30
Merge pull request #436 from bethanyj28/main
Bump @actions/artifact version
2023-09-06 15:03:53 -04:00
Bethany 7b48769c03 update dependency cache 2023-09-06 11:46:07 -07:00
Bethany 66630398df update dist/index.js 2023-09-06 11:42:11 -07:00
Bethany 55e76b779d bump @actions/artifact version 2023-09-06 11:35:17 -07:00
Bethany 0ff37d0dc6
Merge pull request #430 from bethanyj28/main
Create v4-beta
2023-08-24 15:06:41 -04:00
Bethany 0597a567d0 update dist 2023-08-24 12:03:24 -07:00
Bethany 0f573694ad bump @actions/artifact to 2.0.0 2023-08-24 11:21:18 -07:00
Bethany 99700c1456 Update upload-artifact for @actions/artifact@v2 2023-08-24 11:01:45 -07:00
Luís Ferreira 65d862660a
chore(github): remove trailing whitespaces (#313)
Signed-off-by: Luís Ferreira <contact@lsferreira.net>

Signed-off-by: Luís Ferreira <contact@lsferreira.net>
Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2023-01-06 09:46:02 -05:00
Luís Ferreira 0b7f8abb15
ci(github): update action/download-artifact from v1 to v3 (#312)
Signed-off-by: Luís Ferreira <contact@lsferreira.net>

Signed-off-by: Luís Ferreira <contact@lsferreira.net>
Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2023-01-05 18:07:55 -05:00
Konrad Pabjan 013d2b89ba
Create devcontainer for codespaces + update all dev dependencies (#375)
* Create devcontainer for codespaces

* Use node 16 for devcontainer and CI

* Fix node version incompatibility error

* Remove caching

* Add npm build to check-dist

* Remove new caching

* use npm install

* Test node 14

* Update check dist with node 16

* Use node 18.x for check-dist

* Npm install vs CI

* Update package.json scripts

* test

* Remove caching

* npm run test

* testing

* test

* test

* One more test

* Test

* test

* Update everything

* use vercel

* Test

* Update index.js

* Test

* Remove codeql warnings

* Use NPM CI vs install

* Small tweaks to existing workflows
2023-01-05 16:27:11 -05:00
Konrad Pabjan 055b8b3f04
Bump Actions NPM dependencies (#374) 2023-01-04 13:55:10 -05:00
Luís Ferreira 7a5d4831f7
ci(github): update action/checkout from v2 to v3 (#315)
Signed-off-by: Luís Ferreira <contact@lsferreira.net>

Signed-off-by: Luís Ferreira <contact@lsferreira.net>
Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2023-01-04 13:44:29 -05:00
George Melikov e0057a5b76
README: Bump actions/checkout to v3 (#352)
`Node.js 12 actions are deprecated. For more information see: https://github.blog/changelog/2022-09-22-github-actions-all-actions-will-begin-running-on-node16-instead-of-node12/. Please update the following actions to use Node.js 16: actions/checkout, actions/checkout`

It's great to have non-deprecated examples.
2023-01-04 13:34:12 -05:00
James M. Greene 7fe6c13ac8
Update to latest `actions/publish-action` (#363)
To avoid Actions core deprecation messages.

https://github.com/actions/publish-action/releases/tag/v0.2.1
2023-01-04 13:21:58 -05:00
Konrad Pabjan 83fd05a356
Bump actions-core to v1.10.0 (#356) 2022-10-21 15:17:17 -04:00
Rob Herley 3cea537223
Merge pull request #327 from actions/robherley/artifact-1.1.0
Bump @actions/artifact to 1.1.0
2022-05-20 10:28:12 -04:00
Rob Herley 849aa7758a
nvm use 12 & npm run release 2022-05-19 21:17:44 +00:00
Rob Herley 4d3986961d
recompile with correct ncc version 2022-05-19 20:46:08 +00:00
Rob Herley 2e0d362ec5
bump @actions/artifact to 1.1.0 2022-05-19 20:33:04 +00:00
Yang Cao 09a5d6a283
Merge pull request #320 from actions/dependabot/npm_and_yarn/ansi-regex-4.1.1
Bump ansi-regex from 4.1.0 to 4.1.1
2022-04-25 09:59:36 -04:00
dependabot[bot] 189315d910
Bump ansi-regex from 4.1.0 to 4.1.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 13:56:12 +00:00
Yang Cao d159c2d80b
Merge pull request #297 from actions/dependabot/npm_and_yarn/ajv-6.12.6
Bump ajv from 6.11.0 to 6.12.6
2022-04-25 09:55:27 -04:00
dependabot[bot] c26a7ba4b5
Bump ajv from 6.11.0 to 6.12.6
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.11.0 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.11.0...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 13:50:51 +00:00
Yang Cao 6ed6c72922
Merge pull request #303 from actions/dependabot/npm_and_yarn/yargs-parser-13.1.2
Bump yargs-parser from 13.1.1 to 13.1.2
2022-04-25 09:50:11 -04:00
dependabot[bot] 2aeee267b2
Bump yargs-parser from 13.1.1 to 13.1.2
Bumps [yargs-parser](https://github.com/yargs/yargs-parser) from 13.1.1 to 13.1.2.
- [Release notes](https://github.com/yargs/yargs-parser/releases)
- [Changelog](https://github.com/yargs/yargs-parser/blob/main/docs/CHANGELOG-full.md)
- [Commits](https://github.com/yargs/yargs-parser/commits)

---
updated-dependencies:
- dependency-name: yargs-parser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-25 13:47:10 +00:00
Yang Cao 0e2226cf92
Merge pull request #311 from actions/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-04-25 09:46:22 -04:00
dependabot[bot] f29a5d04a6
Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-26 14:50:39 +00:00
Jonathan Tamsut 6673cd052c
Update `lockfileVersion` in `package-lock.json` (#302)
* update lockfileVersion

* regenerate package lock

* update actions/artifact package

* upgrade to v3

* bump license number

* bump upload-artifact package version

* regenerate index.js

* add newline

* generate release

* revert index.js

* use correct typescript version

* revert artifact version

* regenerate lock file

* revert to newer packages

* regenerate index.js

* use new version in README
2022-03-03 08:44:35 -08:00
Thomas Boop 2244c82003
Update to node16 (#293)
Node 12 has an end of life on April 30, 2022.

This PR updates the default runtime to [node16](https://github.blog/changelog/2021-12-10-github-actions-github-hosted-runners-now-run-node-js-16-by-default/), rather then node12. 

This is supported on all Actions Runners v2.285.0 or later.
2022-02-07 21:18:54 +01:00
Konrad Pabjan 87348cee5f
Add 503 warning when uploading to the same artifact 2022-01-20 15:26:28 +01:00
Konrad Pabjan 82c141cc51
Bump @actions/artifact to version 0.6.1 (#286) 2021-12-15 09:47:54 -05:00
Konrad Pabjan da838ae959
Bump @actions/artifact to version 0.6.0 (#280) 2021-12-07 11:45:04 -05:00
Jozef Izso f4ac36d205
Improve readme (#278) 2021-12-07 09:07:03 -05:00
Konrad Pabjan 5f375cca4b
Document how to correctly use environment variables for path input (#274)
* Update README.md

* Update README.md
2021-12-06 16:35:35 -05:00
Konrad Pabjan a009a66585
Create release-new-action-version.yml (#277) 2021-12-02 10:29:09 -05:00
dependabot[bot] b9bb65708e
Bump tmpl from 1.0.4 to 1.0.5 (#250)
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2021-12-01 17:23:57 -05:00
Peter Mescalchin 0b3de3e43b
Fix `README.md` links and some formatting updates (#273)
* Some `README.md` whitespace and tweaks

* Fix link and title to GitHub documentation page on `README.md`

* Update link to `actions/toolkit` documentation

* De-quote some YAML blocks/examples in `README.md`
2021-12-01 08:48:09 -05:00
Rob Herley 54f3f7f081
Merge pull request #271 from actions/robherley/dupe-issue-template
rm dupe security vulnerability from issue template
2021-11-23 14:49:07 -05:00
Rob Herley f061e61d75
rm dupe security vulnerability from issue template 2021-11-23 14:47:33 -05:00
Rob Herley ee82d6a40e
Merge pull request #269 from actions/robherley/update-issue-templates
Add issue templates
2021-11-22 17:09:19 -05:00
Rob Herley ecd66f1683
add issue templates 2021-11-22 10:51:21 -05:00
Alberto Gimeno 37da8e8d08
Merge pull request #241 from actions/dependabot/npm_and_yarn/path-parse-1.0.7
Bump path-parse from 1.0.6 to 1.0.7
2021-11-10 11:56:12 +01:00
dependabot[bot] cd62eed952
Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-15 14:49:55 +00:00
Alberto Gimeno 6fc84b101e
Merge pull request #257 from actions/upgrade-dependencies
Upgrade dependencies
2021-10-15 16:48:51 +02:00
Alberto Gimeno 551bc9bdf8 Upgrade jest, jest-circus and ts-jest 2021-10-15 10:24:17 +02:00
Brian Cristante 11e311c8b5
Remove path filters (#229) 2021-06-25 13:39:56 -04:00
Brian Cristante 317b58f0d1
Fix YAML types in licensed.yml (#228)
* fix YAML types in licensed.yml

* Update .github/workflows/licensed.yml

Co-authored-by: Josh Gross <joshmgross@github.com>

* formatting

Co-authored-by: Josh Gross <joshmgross@github.com>
2021-06-24 11:52:33 -04:00
Brian Cristante 8d06e6c094
Create check-dist.yml (#227)
* Create check-dist.yml

* steps.if still needs ${{ }} when the expr has operators

* single quotes?

* npm install

* mess with index.js

* add if failure()

* Copy the correct version back in from the artifact

* Update .github/workflows/check-dist.yml

Co-authored-by: Konrad Pabjan <konradpabjan@github.com>

* formatting & language

* npm ci

Co-authored-by: Konrad Pabjan <konradpabjan@github.com>
2021-06-24 11:38:43 -04:00
Brian Cristante 27121b0bdf
Ingest v0.5.2 of @actions/artifact (#224)
* npm install --update @actions/artifact

* update .license file

* npm run release
2021-06-16 11:09:14 -04:00
dependabot[bot] 4537e112f4
Bump ws from 7.2.3 to 7.5.0 (#221)
Bumps [ws](https://github.com/websockets/ws) from 7.2.3 to 7.5.0.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.2.3...7.5.0)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com>
2021-06-16 10:34:57 -04:00
Brian Cristante 2368feccd5
Avoid triggering push for Dependabot branches (again) (#223) 2021-06-16 10:31:46 -04:00
dependabot[bot] 52a3c6b975
Bump glob-parent from 5.1.1 to 5.1.2 (#219)
Bumps [glob-parent](https://github.com/gulpjs/glob-parent) from 5.1.1 to 5.1.2.
- [Release notes](https://github.com/gulpjs/glob-parent/releases)
- [Changelog](https://github.com/gulpjs/glob-parent/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gulpjs/glob-parent/compare/v5.1.1...v5.1.2)

---
updated-dependencies:
- dependency-name: glob-parent
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com>
2021-06-16 10:19:08 -04:00
dependabot[bot] 76f4433885
Bump hosted-git-info from 2.8.5 to 2.8.9 (#203)
* Bump hosted-git-info from 2.8.5 to 2.8.9

Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.5 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.5...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

* Empty commit

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com>
2021-06-16 10:15:16 -04:00
Brian Cristante 46426468d3
Merge pull request #222 from actions/brcrista/dependabot-push
Avoid triggering push for Dependabot branches
2021-06-16 10:08:56 -04:00
Brian Cristante 8507687653
avoid triggering push for Dependabot branches 2021-06-16 10:05:05 -04:00
Brian Cristante 0482dbbe7a
Merge pull request #202 from actions/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21
2021-06-16 10:01:31 -04:00
Brian Cristante 58518184d2
Merge pull request #211 from JasonGross/patch-1
Fix the grammar in a warning message
2021-06-16 09:59:04 -04:00
Jason Gross 4db8255e70
Fix the grammar in a warning message 2021-05-23 08:33:18 -04:00
Robert Cannon 9243a41f97
Re-order example descriptions (#209)
Given the way that the two descriptions were written it was confusing about which example was being discussed (see specifically "In the above example in the previous version).
2021-05-21 19:28:43 +02:00
Remy Kabel 9dc0ee81a2
Fix spelling error (#204)
effect -> affect
2021-05-14 15:13:55 +02:00
dependabot[bot] 2e3d6da508
Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 12:10:04 +00:00
Konrad Pabjan ee69f02b3d
Bump @actions/artifact to version 0.5.1 (#189)
* Bump @actions/artifact to version 0.5.1

* Update license versions
2021-04-06 16:47:26 -04:00
dependabot[bot] bcd44ad93d
Bump y18n from 4.0.0 to 4.0.1 (#186)
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-06 14:57:20 -04:00
Yang Cao ea3d524381
Merge pull request #173 from yacaovsnc/main
Display a warning if user tries to upload over 10000 files
2021-02-08 15:48:10 -05:00
Yang Cao e95d1b9c8d update index.js 2021-02-08 15:41:35 -05:00
Yang Cao 0fbc4b771a Display a warning if user tries to upload over 10000 files 2021-02-08 15:09:20 -05:00
Robin Neatherway a1af2e8f43
Add on: pull_request trigger to CodeQL workflow (#165)
From February 2021, in order to provide feedback on pull requests, Code Scanning workflows must be configured with both `push` and `pull_request` triggers. This is because Code Scanning compares the results from a pull request against the results for the base branch to tell you only what has changed between the two.

Early in the beta period we supported displaying results on pull requests for workflows with only `push` triggers, but have discontinued support as this proved to be less robust.

See https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#scanning-pull-requests for more information on how best to configure your Code Scanning workflows.
2021-01-15 12:19:21 +01:00
Konrad Pabjan a4f98af3fa
Update README.md 2021-01-07 18:56:21 +01:00
Konrad Pabjan 16b8b2b2e7
Update where does the upload go section (#162) 2021-01-05 16:00:51 +01:00
Konrad Pabjan e448a9b857
Add retries to all HTTP calls + resolve dependabot alerts (#160)
* Bump @actions/artifact to version 0.5.0

* Resolve dependabot alert for node-notifier

* Resolve dependabot alert for node-fetch

* Bump artifact.dep.yml

* Update http-client.dep.yml
2021-01-04 15:48:10 +01:00
Hugo van Kemenade e6bd6b7749
Replace "file(s)" with "file" or "files" (#159) 2021-01-04 11:24:12 +01:00
Josh Gross 1fd4c858f9
Merge pull request #152 from actions/joshmgross/fix-codeowners
Fix CODEOWNERS team name
2020-12-07 14:24:45 -05:00
Josh Gross a2af908e3a
Fix CODEOWNERS team name 2020-12-07 13:36:14 -05:00
Brian Cristante 928d1a16d9
Create CODEOWNERS (#149) 2020-11-25 15:24:41 -05:00
Konrad Pabjan e4a7ffadfc
Update README with more retention documentation 2020-11-17 12:57:17 -05:00
Brian Westphal 726a6dcd01
Adding example of retention-days option. (#131) 2020-11-13 12:25:13 -05:00
Josh Gross 3db166e2ea
Merge pull request #145 from actions/joshmgross/update-actions-core
Update @actions/core to 1.2.6
2020-11-13 10:52:34 -05:00
Josh Gross d86048c66c
Update @actions/core license 2020-11-12 16:46:49 -05:00
Josh Gross 328d69042a
Update @actions/core to 1.2.6 2020-11-12 16:32:13 -05:00
Yang Cao 27bce4eee7
Merge pull request #112 from thboop/main
Add `Licensed` To Help Verify Prod Licenses
2020-09-23 17:18:48 -04:00
Thomas Boop f8b42f7ab4 update licensed files 2020-09-23 17:09:42 -04:00
Thomas Boop 2106e8cf10 update contributing.md 2020-09-23 17:06:57 -04:00
Thomas Boop db66798ebc Ignore Generated Files in Git PR's 2020-09-23 17:06:22 -04:00
Thomas Boop d359fd0772 Manual Verification of licenses 2020-09-23 17:06:22 -04:00
Thomas Boop 350822c32f Add Licensed Workflow and config 2020-09-23 17:06:22 -04:00
Justin Weissig abecf4abf4
Updated README.md (#118)
Fixed minor spelling and grammar issues.
2020-09-18 17:36:35 -04:00
Yang Cao 604e071d21
Merge pull request #126 from yacaovsnc/main
Add an option to specify retention period for artifacts
2020-09-18 16:57:13 -04:00
Yang Cao 4560c23b39 Check for invalid retention-days input 2020-09-18 16:04:35 -04:00
Yang Cao 59018c2f85 Add an option to specify retention period 2020-09-18 15:01:18 -04:00
Konrad Pabjan 58740802ef
Update README.md 2020-08-11 18:20:19 +02:00
Thomas Boop f109393e79
Add Third Party License Information (#111) 2020-08-10 17:24:27 +02:00
Konrad Pabjan 268d754764
Retry on 413 response codes (#108) 2020-08-04 17:36:48 +02:00
Konrad Pabjan c8879bf5ae
Detect case insensitive uploads + Bump @actions/artifact to version 0.3.3 (#106)
* Detect case insensitive uploads

* PR feedback
2020-07-31 19:22:08 +02:00
Konrad Pabjan 5ba29a7d5b
Add new option to specify behavior if no files found (#104)
* Add new option to specify behavior if no files found
2020-07-31 17:27:37 +02:00
Konrad Pabjan 5f948bc1f0
Correctly check symlinks (#103) 2020-07-27 15:41:16 +02:00
dependabot[bot] 589ca5fbdd
Bump lodash from 4.17.15 to 4.17.19 (#99)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-07-22 12:45:55 +02:00
Konrad Pabjan 8ec57c93cb
Update README.md 2020-07-14 12:25:21 +02:00
Konrad Pabjan ee5fe7718d
Update test.yml 2020-07-14 12:20:40 +02:00
Konrad Pabjan 0c366cb4fc
Create CONTRIBUTING.md 2020-07-10 14:17:19 +02:00
Konrad Pabjan 63d6076e6f
Create CODE_OF_CONDUCT.md 2020-07-10 13:49:39 +02:00
Konrad Pabjan ebad382c09
Fix outdated dependencies (#96)
* Update outdated dependencies

* Update more outdated dependencies
2020-07-09 22:26:29 +02:00
Konrad Pabjan 90f03bd03e
Update outdated dependencies (#95) 2020-07-09 21:44:51 +02:00
Konrad Pabjan f265ac5693
Multi Path Artifact Upload + Exclude Character Support (#94)
* Support for multi path upload

* Update README

* Fix tests

* Actually fix tests

* PR feedback

* Fix

* Apply suggestions from code review

Co-authored-by: Alberto Gimeno <gimenete@users.noreply.github.com>

* Fix more tests

Co-authored-by: Alberto Gimeno <gimenete@users.noreply.github.com>
2020-07-09 20:53:45 +02:00
Konrad Pabjan 4347a0d55a
Update README.md 2020-06-29 11:07:02 +02:00
Konrad Pabjan 97b7dace6c
Fix connection errors if a retry is encountered (#82)
* Use @actions/artifact 0.3.2

* setFailed if certain items don't upload

* Update verion #
2020-05-18 15:43:29 +02:00
Nick Schonning 9bcc1e21d0
chore: Remove CodeQL strategy (#79)
The job isn't using a matrix, so this value isn't allowed
2020-05-13 11:17:32 +02:00
Josh Gross f729db2261
Merge pull request #78 from actions/joshmgross/add-code-scanning
Create codeql-analysis.yml
2020-05-11 10:48:04 -04:00
Josh Gross d6636db5bd
Add paths-ignore for md files 2020-05-11 09:48:08 -04:00
Josh Gross 1c797a4e6c
Create codeql-analysis.yml 2020-05-11 09:27:19 -04:00
dependabot[bot] 4a30538ab4
Bump @actions/http-client from 1.0.7 to 1.0.8 (#73)
Bumps [@actions/http-client](https://github.com/actions/http-client) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/actions/http-client/releases)
- [Changelog](https://github.com/actions/http-client/blob/master/RELEASES.md)
- [Commits](https://github.com/actions/http-client/commits)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-05-11 12:02:41 +02:00
Konrad Pabjan e59920f5cf Update issue templates 2020-05-08 21:08:51 +02:00
Konrad Pabjan e7eefc4917
V2 Upload Artifact (#70)
* V2 Preview (#54)

* V2 Upload Artifact

* Improve logs

* Update release

* Update test.yml

* Update test.yml

* Update test.yml

* @actions/artifact v0.2.0 package

* Add extra YAML test

* Extra Documentation

* Update README.md

* Update README.md

* Update NPM packages

* Update to @actions/artifact 0.3.1

* Update readme from v2 preview to v2

* Add .gitattributes

* Misc Updates

* macOS to macos

* Update YAML so only push events trigger on the master branch
2020-04-28 17:18:53 +02:00
Manolo Edge 1283ca12b6
grammar: changed "trash can" to "trashcan" (#57) 2020-03-19 11:44:16 +01:00
Konrad Pabjan 8eb149d680
Update README.md 2020-01-16 14:38:17 -05:00
Konrad Pabjan ceafd8bc4a
Update README.md 2020-01-16 14:36:06 -05:00
52 changed files with 289546 additions and 24 deletions

View File

@ -0,0 +1,6 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "@actions/upload-artifact",
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-16"
}

3
.eslintignore Normal file
View File

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

16
.eslintrc.json Normal file
View File

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

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* text=auto eol=lf
.licenses/** -diff linguist-generated=true

1
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1 @@
* @actions/artifacts-actions

63
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,63 @@
name: "🐛 Bug report"
description: Let us know about a bug!
labels: ['bug']
title: '[bug]'
body:
- type: textarea
id: problem
attributes:
label: What happened?
description: |
Please provide a clear and concise description of what the bug is. If applicable, add screenshots to help explain your problem.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect to happen?
validations:
required: true
- type: textarea
id: repro
attributes:
label: How can we reproduce it?
description: |
Please be minimal and precise as possible. If your repo/run is public, please include a URL so it is easier for us to investigate.
validations:
required: true
- type: textarea
id: additional
attributes:
label: Anything else we need to know?
- type: input
id: version
attributes:
label: What version of the action are you using?
placeholder: vX.Y.Z
description: |
Please check the documentation first since different major versions can have different behaviors.
validations:
required: true
- type: dropdown
id: environment
attributes:
label: What are your runner environments?
multiple: true
options:
- self-hosted
- linux
- window
- macos
validations:
required: true
- type: input
id: ghes
attributes:
label: Are you on GitHub Enterprise Server? If so, what version?
placeholder: vX.Y

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: 🙋 Ask a question
url: https://github.community/c/code-to-cloud/52
about: Please ask and answer questions on GitHub Support Community.

View File

@ -0,0 +1,28 @@
name: "📚 Documentation issues"
description: Make a suggestion to improve the documentation!
labels: ['documentation']
title: '[docs]'
body:
- type: markdown
attributes:
value: |
❗ This is only for documentation updates for files in this repo, ie: `README.md`.
If you want to suggest changes for the [GitHub Docs](https://docs.github.com/), please [open an issue there](https://github.com/github/docs/issues/new/choose).
- type: textarea
id: affected
attributes:
label: What files would you like to change?
description: |
Please provide permalinks to the specified files and line numbers.
validations:
required: true
- type: textarea
id: suggested
attributes:
label: What are your suggested changes?
description: |
Give as much detail as you can to help us understand the changes you want to see.
validations:
required: true

View File

@ -0,0 +1,20 @@
name: "🎁 Feature request"
description: Suggest a new feature/enhancement!
labels: ['enhancement']
title: '[feat req]'
body:
- type: textarea
id: feature
attributes:
label: What would you like to be added?
description: |
Please check existing issues to avoid making duplicates. Any duplicate issue will be closed immediately.
validations:
required: true
- type: textarea
id: reasoning
attributes:
label: Why is this needed?
validations:
required: true

53
.github/workflows/check-dist.yml vendored Normal file
View File

@ -0,0 +1,53 @@
# `dist/index.js` is a special file in Actions.
# When you reference an action with `uses:` in a workflow,
# `index.js` is the code that will run.
# For our project, we generate this file through a build process
# from other source files.
# We need to make sure the checked-in `index.js` actually matches what we expect it to be.
name: Check dist/
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
workflow_dispatch:
jobs:
check-dist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node 20
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Rebuild the dist/ directory
run: npm run release
- name: Compare the expected and actual dist/ directories
run: |
if [ "$(git diff --ignore-space-at-eol dist/ | wc -l)" -gt "0" ]; then
echo "Detected uncommitted changes after build. See status below:"
git diff
exit 1
fi
id: diff
# If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
path: dist/

46
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@ -0,0 +1,46 @@
name: "Code scanning - action"
on:
push:
branches-ignore: "dependabot/**"
pull_request:
paths-ignore:
- '**.md'
schedule:
- cron: '0 6 * * 3'
jobs:
CodeQL-Build:
# CodeQL runs on ubuntu-latest and windows-latest
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
# 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
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

24
.github/workflows/licensed.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: Licensed
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
name: Check licenses
steps:
- uses: actions/checkout@v4
- run: npm ci
- name: Install licensed
run: |
cd $RUNNER_TEMP
curl -Lfs -o licensed.tar.gz https://github.com/github/licensed/releases/download/2.12.2/licensed-2.12.2-linux-x64.tar.gz
sudo tar -xzf licensed.tar.gz
sudo mv licensed /usr/local/bin/licensed
- run: licensed status

View File

@ -0,0 +1,28 @@
name: Release new action version
on:
release:
types: [released]
workflow_dispatch:
inputs:
TAG_NAME:
description: 'Tag name that the major tag will point to'
required: true
env:
TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
permissions:
contents: write
jobs:
update_tag:
name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes
environment:
name: releaseNewActionVersion
runs-on: ubuntu-latest
steps:
- name: Update the ${{ env.TAG_NAME }} tag
id: update-major-tag
uses: actions/publish-action@v0.3.0
with:
source-tag: ${{ env.TAG_NAME }}
slack-webhook: ${{ secrets.SLACK_WEBHOOK }}

251
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,251 @@
name: Test
on:
push:
branches:
- main
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
jobs:
build:
name: Build
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.runs-on }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node 20
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Compile
run: npm run build
- name: Lint
run: npm run lint
- name: Format
run: npm run format-check
- name: Test
run: npm run test
# 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
# Upload a single file artifact
- name: 'Upload artifact #1'
uses: ./
with:
name: 'Artifact-A-${{ matrix.runs-on }}'
path: path/to/dir-1/file1.txt
# Upload using a wildcard pattern
- name: 'Upload artifact #2'
uses: ./
with:
name: 'Artifact-Wildcard-${{ matrix.runs-on }}'
path: path/**/dir*/
# Upload a multi-path artifact
- name: 'Upload artifact #3'
uses: ./
with:
name: 'Multi-Path-Artifact-${{ matrix.runs-on }}'
path: |
path/to/dir-1/*
path/to/dir-[23]/*
!path/to/dir-3/*.txt
# Download Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1'
uses: actions/download-artifact@v4
with:
name: 'Artifact-A-${{ matrix.runs-on }}'
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@v4
with:
name: 'Artifact-Wildcard-${{ matrix.runs-on }}'
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 #4 and verify the correctness of the content
- name: 'Download artifact #4'
uses: actions/download-artifact@v4
with:
name: 'Multi-Path-Artifact-${{ matrix.runs-on }}'
path: multi/artifact
- name: 'Verify Artifact #4'
run: |
$file1 = "multi/artifact/dir-1/file1.txt"
$file2 = "multi/artifact/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
- name: 'Alter file 1 content'
run: |
echo "This file has changed" > path/to/dir-1/file1.txt
# Replace the contents of Artifact #1
- name: 'Overwrite artifact #1'
uses: ./
with:
name: 'Artifact-A-${{ matrix.runs-on }}'
path: path/to/dir-1/file1.txt
overwrite: true
# Download replaced Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1 again'
uses: actions/download-artifact@v4
with:
name: 'Artifact-A-${{ matrix.runs-on }}'
path: overwrite/some/new/path
- name: 'Verify Artifact #1 again'
run: |
$file = "overwrite/some/new/path/file1.txt"
if(!(Test-Path -path $file))
{
Write-Error "Expected file does not exist"
}
if(!((Get-Content $file) -ceq "This file has changed"))
{
Write-Error "File contents of downloaded artifact are incorrect"
}
shell: pwsh
merge:
name: Merge
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
# Merge all artifacts from previous jobs
- name: Merge all artifacts in run
uses: ./merge/
with:
# our matrix produces artifacts with the same file, this prevents "stomping" on each other, also makes it
# easier to identify each of the merged artifacts
separate-directories: true
- name: 'Download merged artifacts'
uses: actions/download-artifact@v4
with:
name: merged-artifacts
path: all-merged-artifacts
- name: 'Check merged artifact has directories for each artifact'
run: |
$artifacts = @(
"Artifact-A-ubuntu-latest",
"Artifact-A-macos-latest",
"Artifact-A-windows-latest",
"Artifact-Wildcard-ubuntu-latest",
"Artifact-Wildcard-macos-latest",
"Artifact-Wildcard-windows-latest",
"Multi-Path-Artifact-ubuntu-latest",
"Multi-Path-Artifact-macos-latest",
"Multi-Path-Artifact-windows-latest"
)
foreach ($artifact in $artifacts) {
$path = "all-merged-artifacts/$artifact"
if (!(Test-Path $path)) {
Write-Error "$path does not exist."
}
}
shell: pwsh
# Merge Artifact-A-* from previous jobs
- name: Merge all Artifact-A
uses: ./merge/
with:
name: Merged-Artifact-As
pattern: 'Artifact-A-*'
separate-directories: true
# Download merged artifacts and verify the correctness of the content
- name: 'Download merged artifacts'
uses: actions/download-artifact@v4
with:
name: Merged-Artifact-As
path: merged-artifact-a
- name: 'Verify merged artifacts'
run: |
$files = @(
"merged-artifact-a/Artifact-A-ubuntu-latest/file1.txt",
"merged-artifact-a/Artifact-A-macos-latest/file1.txt",
"merged-artifact-a/Artifact-A-windows-latest/file1.txt"
)
foreach ($file in $files) {
if (!(Test-Path $file)) {
Write-Error "$file does not exist."
}
if (!((Get-Content $file) -ceq "This file has changed")) {
Write-Error "$file has incorrect content."
}
}
shell: pwsh

3
.gitignore vendored Normal file
View File

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

15
.licensed.yml Normal file
View File

@ -0,0 +1,15 @@
sources:
npm: true
allowed:
- apache-2.0
- bsd-2-clause
- bsd-3-clause
- isc
- mit
- cc0-1.0
- unlicense
reviewed:
npm:
- fs.realpath

20
.licenses/npm/@actions/artifact.dep.yml generated Normal file
View File

@ -0,0 +1,20 @@
---
name: "@actions/artifact"
version: 2.1.8
type: npm
summary:
homepage:
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

20
.licenses/npm/@actions/core.dep.yml generated Normal file
View File

@ -0,0 +1,20 @@
---
name: "@actions/core"
version: 1.10.1
type: npm
summary:
homepage:
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

20
.licenses/npm/@actions/github.dep.yml generated Normal file
View File

@ -0,0 +1,20 @@
---
name: "@actions/github"
version: 6.0.0
type: npm
summary:
homepage:
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

20
.licenses/npm/@actions/glob.dep.yml generated Normal file
View File

@ -0,0 +1,20 @@
---
name: "@actions/glob"
version: 0.3.0
type: npm
summary:
homepage:
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

20
.licenses/npm/@actions/io.dep.yml generated Normal file
View File

@ -0,0 +1,20 @@
---
name: "@actions/io"
version: 1.1.2
type: npm
summary:
homepage:
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []

26
.licenses/npm/minimatch.dep.yml generated Normal file
View File

@ -0,0 +1,26 @@
---
name: minimatch
version: 9.0.3
type: npm
summary:
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) 2011-2023 Isaac Z. Schlueter and Contributors
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.
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.
notices: []

3
.prettierignore Normal file
View File

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

11
.prettierrc.json Normal file
View File

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

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces, and it also applies when
an individual is representing the project or its community in public spaces.
Examples of representing a project or community include using an official
project e-mail address, posting via an official social media account, or acting
as an appointed representative at an online or offline event. Representation of
a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at opensource@github.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

56
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,56 @@
## Contributing
[fork]: https://github.com/actions/upload-artifact/fork
[pr]: https://github.com/actions/upload-artifact/compare
[style]: https://github.com/styleguide/js
[code-of-conduct]: CODE_OF_CONDUCT.md
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE).
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
## Found a bug?
- **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/actions/upload-artifact/issues).
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/actions/upload-artifact/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or a **reproducable test case** demonstrating the expected behavior that is not occurring.
- If possible, use the relevant bug report templates to create the issue.
## What should I know before submitting a pull request or issue
The code related to `upload-artifact` is split between this repository and [actions/toolkit](https://github.com/actions/toolkit) where the `@actions/artifact` npm package is housed. The npm package contains the core functionality to interact with artifacts. Any extra functionality on top of interacting with the apis such as search is inside this repository.
Artifact related issues will be tracked in this repository so please do not open duplicate issues in `actions/toolkit`.
## Submitting a pull request
1. [Fork][fork] and clone the repository
2. Configure and install the dependencies: `npm install`
3. Make sure the tests pass on your machine: `npm run test`
4. Create a new branch: `git checkout -b my-branch-name`
5. Make your change, add tests, and make sure the tests still pass
6. Make sure your code is correctly formatted: `npm run format`
7. Make sure your code passes linting: `npm run lint`
8. Update `dist/index.js` using `npm run release`. This creates a single javascript file that is used as an entry-point for the action
7. Push to your fork and [submit a pull request][pr]
8. Pat your self on the back and wait for your pull request to be reviewed and merged.
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
- Write tests.
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
## Licensed
This repository uses a tool called [Licensed](https://github.com/github/licensed) to verify third party dependencies. You may need to locally install licensed and run `licensed cache` to update the dependency cache if you install or update a production dependency. If licensed cache is unable to determine the dependency, you may need to modify the cache file yourself to put the correct license. You should still verify the dependency, licensed is a tool to help, but is not a substitute for human review of dependencies.
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
Thanks! :heart: :heart: :heart:
GitHub Actions Team :octocat:

444
README.md
View File

@ -1,44 +1,450 @@
# upload-artifact
# `@actions/upload-artifact`
This uploads artifacts from your workflow.
> [!WARNING]
> actions/upload-artifact@v3 is scheduled for deprecation on **November 30, 2024**. [Learn more.](https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/)
> Similarly, v1/v2 are scheduled for deprecation on **June 30, 2024**.
> Please update your workflow to use v4 of the artifact actions.
> This deprecation will not impact any existing versions of GitHub Enterprise Server being used by customers.
Upload [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) from your Workflow Runs. Internally powered by [@actions/artifact](https://github.com/actions/toolkit/tree/main/packages/artifact) package.
See also [download-artifact](https://github.com/actions/download-artifact).
# Usage
- [`@actions/upload-artifact`](#actionsupload-artifact)
- [v4 - What's new](#v4---whats-new)
- [Improvements](#improvements)
- [Breaking Changes](#breaking-changes)
- [Usage](#usage)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [Examples](#examples)
- [Upload an Individual File](#upload-an-individual-file)
- [Upload an Entire Directory](#upload-an-entire-directory)
- [Upload using a Wildcard Pattern](#upload-using-a-wildcard-pattern)
- [Upload using Multiple Paths and Exclusions](#upload-using-multiple-paths-and-exclusions)
- [Altering compressions level (speed v. size)](#altering-compressions-level-speed-v-size)
- [Customization if no files are found](#customization-if-no-files-are-found)
- [(Not) Uploading to the same artifact](#not-uploading-to-the-same-artifact)
- [Environment Variables and Tilde Expansion](#environment-variables-and-tilde-expansion)
- [Retention Period](#retention-period)
- [Using Outputs](#using-outputs)
- [Example output between steps](#example-output-between-steps)
- [Example output between jobs](#example-output-between-jobs)
- [Overwriting an Artifact](#overwriting-an-artifact)
- [Limitations](#limitations)
- [Number of Artifacts](#number-of-artifacts)
- [Zip archives](#zip-archives)
- [Permission Loss](#permission-loss)
- [Where does the upload go?](#where-does-the-upload-go)
See [action.yml](action.yml)
Basic:
## 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).
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.
For more information, see the [`@actions/artifact`](https://github.com/actions/toolkit/tree/main/packages/artifact) documentation.
There is also a new sub-action, `actions/upload-artifact/merge`. For more info, check out that action's [README](./merge/README.md).
### Improvements
1. Uploads are significantly faster, upwards of 90% improvement in worst case scenarios.
2. Once uploaded, an Artifact ID is returned and Artifacts are immediately available in the UI and [REST API](https://docs.github.com/en/rest/actions/artifacts). Previously, you would have to wait for the run to be completed before an ID was available or any APIs could be utilized.
3. The contents of an Artifact are uploaded together into an _immutable_ archive. They cannot be altered by subsequent jobs unless the Artifacts are deleted and recreated (where they will have a new ID). Both of these factors help reduce the possibility of accidentally corrupting Artifact files.
4. The compression level of an Artifact can be manually tweaked for speed or size reduction.
### Breaking Changes
1. On self hosted runners, additional [firewall rules](https://github.com/actions/toolkit/tree/main/packages/artifact#breaking-changes) may be required.
2. Uploading to the same named Artifact multiple times.
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.
For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
## Usage
### Inputs
```yaml
- uses: actions/upload-artifact@v4
with:
# Name of the artifact to upload.
# Optional. Default is 'artifact'
name:
# A file, directory or wildcard pattern that describes what to upload
# Required.
path:
# The desired behavior if no files are found using the provided path.
# Available Options:
# warn: Output a warning but do not fail the action
# error: Fail the action with an error message
# ignore: Do not output any warnings or errors, the action does not fail
# Optional. Default is 'warn'
if-no-files-found:
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days:
# The level of compression for Zlib to be applied to the artifact archive.
# The value can range from 0 to 9.
# For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
# Optional. Default is '6'
compression-level:
# If true, an artifact with a matching name will be deleted before a new one is uploaded.
# If false, the action will fail if an artifact for the given name already exists.
# Does not fail if the artifact does not exist.
# Optional. Default is 'false'
overwrite:
```
### Outputs
| Name | Description | Example |
| - | - | - |
| `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` |
## Examples
### Upload an Individual File
```yaml
steps:
- uses: actions/checkout@v1
- run: mkdir -p path/to/artifact
- run: echo hello > path/to/artifact/world.txt
- uses: actions/upload-artifact@v1
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: path/to/artifact
path: path/to/artifact/world.txt
```
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):
### Upload an Entire Directory
```yaml
- uses: actions/upload-artifact@v1
if: failure()
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: path/to/artifact
path: path/to/artifact/ # or path/to/artifact
```
### Upload using a Wildcard Pattern
```yaml
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: path/**/[abc]rtifac?/*
```
### Upload using Multiple Paths and Exclusions
```yaml
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: |
path/output/bin/
path/output/test-results
!path/**/*.tmp
```
For supported wildcards along with behavior and documentation, see [@actions/glob](https://github.com/actions/toolkit/tree/main/packages/glob) which is used internally to search for files.
If a wildcard pattern is used, the path hierarchy will be preserved after the first wildcard pattern:
```
path/to/*/directory/foo?.txt =>
∟ path/to/some/directory/foo1.txt
∟ path/to/some/directory/foo2.txt
∟ path/to/other/directory/foo1.txt
would be flattened and uploaded as =>
∟ some/directory/foo1.txt
∟ some/directory/foo2.txt
∟ other/directory/foo1.txt
```
If multiple paths are provided as input, the least common ancestor of all the search paths will be used as the root directory of the artifact. Exclude paths do not affect the directory structure.
Relative and absolute file paths are both allowed. Relative paths are rooted against the current working directory. Paths that begin with a wildcard character should be quoted to avoid being interpreted as YAML aliases.
### Altering compressions level (speed v. size)
If you are uploading large or easily compressable data to your artifact, you may benefit from tweaking the compression level. By default, the compression level is `6`, the same as GNU Gzip.
The value can range from 0 to 9:
- 0: No compression
- 1: Best speed
- 6: Default compression (same as GNU Gzip)
- 9: Best compression
Higher levels will result in better compression, but will take longer to complete.
For large files that are not easily compressed, a value of `0` is recommended for significantly faster uploads.
For instance, if you are uploading random binary data, you can save a lot of time by opting out of compression completely, since it won't benefit:
```yaml
- name: Make a 1GB random binary file
run: |
dd if=/dev/urandom of=my-1gb-file bs=1M count=1000
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: my-1gb-file
compression-level: 0 # no compression
```
But, if you are uploading data that is easily compressed (like plaintext, code, etc) you can save space and cost by having a higher compression level. But this will be heavier on the CPU therefore slower to upload:
```yaml
- name: Make a file with a lot of repeated text
run: |
for i in {1..100000}; do echo -n 'foobar' >> foobar.txt; done
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: foobar.txt
compression-level: 9 # maximum compression
```
### Customization if no files are found
If a path (or paths), result in no files being found for the artifact, the action will succeed but print out a warning. In certain scenarios it may be desirable to fail the action or suppress the warning. The `if-no-files-found` option allows you to customize the behavior of the action if no files are found:
```yaml
- uses: actions/upload-artifact@v4
with:
name: my-artifact
path: path/to/artifact/
if-no-files-found: error # 'warn' or 'ignore' are also available, defaults to `warn`
```
### (Not) Uploading to the same artifact
Unlike earlier versions of `upload-artifact`, uploading to the same artifact via multiple jobs is _not_ supported with `v4`.
```yaml
- run: echo hi > world.txt
- uses: actions/upload-artifact@v4
with:
# implicitly named as 'artifact'
path: world.txt
- run: echo howdy > extra-file.txt
- uses: actions/upload-artifact@v4
with:
# also implicitly named as 'artifact', will fail here!
path: extra-file.txt
```
Artifact names must be unique since each created artifact is idempotent so multiple jobs cannot modify the same artifact.
In matrix scenarios, be careful to not accidentally upload to the same artifact, or else you will encounter conflict errors. It would be best to name the artifact _with_ a prefix or suffix from the matrix:
```yaml
jobs:
upload:
name: Generate Build Artifacts
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
version: [a, b, c]
runs-on: ${{ matrix.os }}
steps:
- name: Build
run: ./some-script --version=${{ matrix.version }} > my-binary
- name: Upload
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}-${{ matrix.version }}
path: my-binary
```
This will result in artifacts like: `binary-ubuntu-latest-a`, `binary-windows-latest-b`, and so on.
Previously the behavior _allowed_ for the artifact names to be the same which resulted in unexpected mutations and accidental corruption. Artifacts created by upload-artifact@v4 are immutable.
### Environment Variables and Tilde Expansion
You can use `~` in the path input as a substitute for `$HOME`. Basic tilde expansion is supported:
```yaml
- run: |
mkdir -p ~/new/artifact
echo hello > ~/new/artifact/world.txt
- uses: actions/upload-artifact@v4
with:
name: my-artifacts
path: ~/new/**/*
```
Environment variables along with context expressions can also be used for input. For documentation see [context and expression syntax](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions):
```yaml
env:
name: my-artifact
steps:
- run: |
mkdir -p ${{ github.workspace }}/artifact
echo hello > ${{ github.workspace }}/artifact/world.txt
- uses: actions/upload-artifact@v4
with:
name: ${{ env.name }}-name
path: ${{ github.workspace }}/artifact/**/*
```
For environment variables created in other steps, make sure to use the `env` expression syntax
```yaml
steps:
- run: |
mkdir testing
echo "This is a file to upload" > testing/file.txt
echo "artifactPath=testing/file.txt" >> $GITHUB_ENV
- uses: actions/upload-artifact@v4
with:
name: artifact
path: ${{ env.artifactPath }} # this will resolve to testing/file.txt at runtime
```
### Retention Period
Artifacts are retained for 90 days by default. You can specify a shorter retention period using the `retention-days` input:
```yaml
- name: Create a file
run: echo "I won't live long" > my_file.txt
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: my-artifact
path: my_file.txt
retention-days: 5
```
The retention period must be between 1 and 90 inclusive. For more information see [artifact and log retention policies](https://docs.github.com/en/free-pro-team@latest/actions/reference/usage-limits-billing-and-administration#artifact-and-log-retention-policy).
### Using Outputs
If an artifact upload is successful then an `artifact-id` output is available. This ID is a unique identifier that can be used with [Artifact REST APIs](https://docs.github.com/en/rest/actions/artifacts).
#### Example output between steps
```yml
- uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: my-artifact
path: path/to/artifact/content/
- name: Output artifact ID
run: echo 'Artifact ID is ${{ steps.artifact-upload-step.outputs.artifact-id }}'
```
#### Example output between jobs
```yml
jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.artifact-upload-step.outputs.artifact-id }}
steps:
- uses: actions/upload-artifact@v4
id: artifact-upload-step
with:
name: my-artifact
path: path/to/artifact/content/
job2:
runs-on: ubuntu-latest
needs: job1
steps:
- env:
OUTPUT1: ${{needs.job1.outputs.output1}}
run: echo "Artifact ID from previous job is $OUTPUT1"
```
### Overwriting an Artifact
Although it's not possible to mutate an Artifact, can completely overwrite one. But do note that this will give the Artifact a new ID, the previous one will no longer exist:
```yaml
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Create a file
run: echo "hello world" > my-file.txt
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: my-artifact # NOTE: same artifact name
path: my-file.txt
upload-again:
needs: upload
runs-on: ubuntu-latest
steps:
- name: Create a different file
run: echo "goodbye world" > my-file.txt
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: my-artifact # NOTE: same artifact name
path: my-file.txt
overwrite: true
```
## Limitations
### Number of Artifacts
Within an individual job, there is a limit of 500 artifacts that can be created for that job.
You may also be limited by Artifacts if you have exceeded your shared storage quota. Storage is calculated every 6-12 hours. See [the documentation](https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions#calculating-minute-and-storage-spending) for more info.
### Zip archives
When an Artifact is uploaded, all the files are assembled into an immutable Zip archive. There is currently no way to download artifacts in a format other than a Zip or to download individual artifact contents.
### Permission Loss
File permissions are not maintained during artifact upload. All directories will have `755` and all files will have `644`. For example, if you make a file executable using `chmod` and then upload that file, post-download the file is no longer guaranteed to be set as an executable.
If you must preserve permissions, you can `tar` all of your files together before artifact upload. Post download, the `tar` file will maintain file permissions and case sensitivity.
```yaml
- name: 'Tar files'
run: tar -cvf my_files.tar /path/to/my/directory
- name: 'Upload Artifact'
uses: actions/upload-artifact@v4
with:
name: my-artifact
path: my_files.tar
```
## 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/>
![Artifacts Screenshot](https://user-images.githubusercontent.com/3685876/62906968-1b4aff80-bd3f-11e9-8815-9058eb05692a.png)
At the bottom of the workflow summary page, there is a dedicated section for artifacts. Here's a screenshot of something you might see:
# License
<img src="https://user-images.githubusercontent.com/16109154/103645952-223c6880-4f59-11eb-8268-8dca6937b5f9.png" width="700" height="300">
The scripts and documentation in this project are released under the [MIT License](LICENSE)
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.

175
__tests__/merge.test.ts Normal file
View File

@ -0,0 +1,175 @@
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'
const fixtures = {
artifactName: 'my-merged-artifact',
tmpDirectory: '/tmp/merge-artifact',
filesToUpload: [
'/some/artifact/path/file-a.txt',
'/some/artifact/path/file-b.txt',
'/some/artifact/path/file-c.txt'
],
artifacts: [
{
name: 'my-artifact-a',
id: 1,
size: 100,
createdAt: new Date('2024-01-01T00:00:00Z')
},
{
name: 'my-artifact-b',
id: 2,
size: 100,
createdAt: new Date('2024-01-01T00:00:00Z')
},
{
name: 'my-artifact-c',
id: 3,
size: 100,
createdAt: new Date('2024-01-01T00:00:00Z')
}
]
}
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 = {
[Inputs.Name]: 'my-merged-artifact',
[Inputs.Pattern]: '*',
[Inputs.SeparateDirectories]: false,
[Inputs.RetentionDays]: 0,
[Inputs.CompressionLevel]: 6,
[Inputs.DeleteMerged]: false,
...overrides
}
;(core.getInput as jest.Mock).mockImplementation((name: string) => {
return inputs[name]
})
;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
return inputs[name]
})
return inputs
}
describe('merge', () => {
beforeEach(async () => {
mockInputs()
jest
.spyOn(artifact, 'listArtifacts')
.mockResolvedValue({artifacts: fixtures.artifacts})
jest.spyOn(artifact, 'downloadArtifact').mockResolvedValue({
downloadPath: fixtures.tmpDirectory
})
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
filesToUpload: fixtures.filesToUpload,
rootDirectory: fixtures.tmpDirectory
})
jest.spyOn(artifact, '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}
})
})
it('merges artifacts', async () => {
await run()
for (const a of fixtures.artifacts) {
expect(artifact.downloadArtifact).toHaveBeenCalledWith(a.id, {
path: fixtures.tmpDirectory
})
}
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.tmpDirectory,
{compressionLevel: 6}
)
})
it('fails if no artifacts found', async () => {
mockInputs({[Inputs.Pattern]: 'this-does-not-match'})
expect(run()).rejects.toThrow()
expect(artifact.uploadArtifact).not.toBeCalled()
expect(artifact.downloadArtifact).not.toBeCalled()
})
it('supports custom compression level', async () => {
mockInputs({
[Inputs.CompressionLevel]: 2
})
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.tmpDirectory,
{compressionLevel: 2}
)
})
it('supports custom retention days', async () => {
mockInputs({
[Inputs.RetentionDays]: 7
})
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.tmpDirectory,
{retentionDays: 7, compressionLevel: 6}
)
})
it('supports deleting artifacts after merge', async () => {
mockInputs({
[Inputs.DeleteMerged]: true
})
await run()
for (const a of fixtures.artifacts) {
expect(artifact.deleteArtifact).toHaveBeenCalledWith(a.name)
}
})
})

355
__tests__/search.test.ts Normal file
View File

@ -0,0 +1,355 @@
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/shared/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(searchItem3Path)).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(searchItem3Path)).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('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 searchResult = await findFilesToUpload(searchPaths)
expect(searchResult.rootDirectory).toEqual(root)
expect(searchResult.filesToUpload.length).toEqual(7)
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem3Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem4Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem1Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraSearchItem2Path)).toEqual(
true
)
expect(searchResult.filesToUpload.includes(extraFileInFolderCPath)).toEqual(
true
)
})
it('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 searchResult = await findFilesToUpload(searchPaths)
expect(searchResult.rootDirectory).toEqual(root)
expect(searchResult.filesToUpload.length).toEqual(5)
expect(searchResult.filesToUpload.includes(searchItem1Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem2Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem3Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(searchItem4Path)).toEqual(true)
expect(searchResult.filesToUpload.includes(extraSearchItem2Path)).toEqual(
true
)
})
it('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
const searchPaths = [searchPath1, searchPath2, searchPath3].join('\n')
const searchResult = await findFilesToUpload(searchPaths)
expect(searchResult.rootDirectory).toEqual(path.join(root, 'folder-h'))
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)
})
})

231
__tests__/upload.test.ts Normal file
View File

@ -0,0 +1,231 @@
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'
const fixtures = {
artifactName: 'artifact-name',
rootDirectory: '/some/artifact/path',
filesToUpload: [
'/some/artifact/path/file1.txt',
'/some/artifact/path/file2.txt'
]
}
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 = {
[Inputs.Name]: 'artifact-name',
[Inputs.Path]: '/some/artifact/path',
[Inputs.IfNoFilesFound]: 'warn',
[Inputs.RetentionDays]: 0,
[Inputs.CompressionLevel]: 6,
[Inputs.Overwrite]: false,
...overrides
}
;(core.getInput as jest.Mock).mockImplementation((name: string) => {
return inputs[name]
})
;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
return inputs[name]
})
return inputs
}
describe('upload', () => {
beforeEach(async () => {
mockInputs()
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
filesToUpload: fixtures.filesToUpload,
rootDirectory: fixtures.rootDirectory
})
jest.spyOn(artifact, 'uploadArtifact').mockResolvedValue({
size: 123,
id: 1337
})
})
it('uploads a single file', async () => {
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
filesToUpload: [fixtures.filesToUpload[0]],
rootDirectory: fixtures.rootDirectory
})
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
[fixtures.filesToUpload[0]],
fixtures.rootDirectory,
{compressionLevel: 6}
)
})
it('uploads multiple files', async () => {
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.rootDirectory,
{compressionLevel: 6}
)
})
it('sets outputs', async () => {
await run()
expect(core.setOutput).toHaveBeenCalledWith('artifact-id', 1337)
expect(core.setOutput).toHaveBeenCalledWith(
'artifact-url',
`${github.context.serverUrl}/${github.context.repo.owner}/${
github.context.repo.repo
}/actions/runs/${github.context.runId}/artifacts/${1337}`
)
})
it('supports custom compression level', async () => {
mockInputs({
[Inputs.CompressionLevel]: 2
})
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.rootDirectory,
{compressionLevel: 2}
)
})
it('supports custom retention days', async () => {
mockInputs({
[Inputs.RetentionDays]: 7
})
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.rootDirectory,
{retentionDays: 7, compressionLevel: 6}
)
})
it('supports warn if-no-files-found', async () => {
mockInputs({
[Inputs.IfNoFilesFound]: 'warn'
})
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
filesToUpload: [],
rootDirectory: fixtures.rootDirectory
})
await run()
expect(core.warning).toHaveBeenCalledWith(
`No files were found with the provided path: ${fixtures.rootDirectory}. No artifacts will be uploaded.`
)
})
it('supports error if-no-files-found', async () => {
mockInputs({
[Inputs.IfNoFilesFound]: 'error'
})
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
filesToUpload: [],
rootDirectory: fixtures.rootDirectory
})
await run()
expect(core.setFailed).toHaveBeenCalledWith(
`No files were found with the provided path: ${fixtures.rootDirectory}. No artifacts will be uploaded.`
)
})
it('supports ignore if-no-files-found', async () => {
mockInputs({
[Inputs.IfNoFilesFound]: 'ignore'
})
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
filesToUpload: [],
rootDirectory: fixtures.rootDirectory
})
await run()
expect(core.info).toHaveBeenCalledWith(
`No files were found with the provided path: ${fixtures.rootDirectory}. No artifacts will be uploaded.`
)
})
it('supports overwrite', async () => {
mockInputs({
[Inputs.Overwrite]: true
})
jest.spyOn(artifact, 'deleteArtifact').mockResolvedValue({
id: 1337
})
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.rootDirectory,
{compressionLevel: 6}
)
expect(artifact.deleteArtifact).toHaveBeenCalledWith(fixtures.artifactName)
})
it('supports overwrite and continues if not found', async () => {
mockInputs({
[Inputs.Overwrite]: true
})
jest
.spyOn(artifact, 'deleteArtifact')
.mockRejectedValue(new ArtifactNotFoundError('not found'))
await run()
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
fixtures.filesToUpload,
fixtures.rootDirectory,
{compressionLevel: 6}
)
expect(artifact.deleteArtifact).toHaveBeenCalledWith(fixtures.artifactName)
expect(core.debug).toHaveBeenCalledWith(
`Skipping deletion of '${fixtures.artifactName}', it does not exist`
)
})
})

View File

@ -1,13 +1,61 @@
name: 'Upload a Build Artifact'
description: 'Upload a build artifact that can be used by subsequent workflow steps'
author: 'GitHub'
inputs:
inputs:
name:
description: 'Artifact name'
required: true
default: 'artifact'
path:
description: 'Directory containing files to upload'
description: 'A file, directory or wildcard pattern that describes what to upload'
required: true
if-no-files-found:
description: >
The desired behavior if no files are found using the provided path.
Available Options:
warn: Output a warning but do not fail the action
error: Fail the action with an error message
ignore: Do not output any warnings or errors, the action does not fail
default: 'warn'
retention-days:
description: >
Duration after which artifact will expire in days. 0 means using default retention.
Minimum 1 day.
Maximum 90 days unless changed from the repository settings page.
compression-level:
description: >
The level of compression for Zlib to be applied to the artifact archive.
The value can range from 0 to 9:
- 0: No compression
- 1: Best speed
- 6: Default compression (same as GNU Gzip)
- 9: Best compression
Higher levels will result in better compression, but will take longer to complete.
For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
default: '6'
overwrite:
description: >
If true, an artifact with a matching name will be deleted before a new one is uploaded.
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'
outputs:
artifact-id:
description: >
A unique identifier for the artifact that was just uploaded. Empty if the artifact upload failed.
This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts
artifact-url:
description: >
A download URL for the artifact that was just uploaded. Empty if the artifact upload failed.
This download URL only works for requests Authenticated with GitHub. Anonymous downloads will be prompted to first login.
If an anonymous download URL is needed than a short time restricted URL can be generated using the download artifact API: https://docs.github.com/en/rest/actions/artifacts#download-an-artifact
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.
runs:
# Plugins live on the runner and are only available to a certain set of first party actions.
plugin: 'publish'
using: 'node20'
main: 'dist/upload/index.js'

136233
dist/merge/index.js vendored Normal file

File diff suppressed because one or more lines are too long

136243
dist/upload/index.js vendored Normal file

File diff suppressed because one or more lines are too long

209
docs/MIGRATION.md Normal file
View File

@ -0,0 +1,209 @@
# Migration
- [Migration](#migration)
- [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)
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`.
## Multiple uploads to the same named Artifact
In `v3`, Artifacts are _mutable_ so it's possible to write workflow scenarios where multiple jobs upload to the same Artifact like so:
```yaml
jobs:
upload:
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.runs-on }}
steps:
- name: Create a File
run: echo "hello from ${{ matrix.runs-on }}" > file-${{ matrix.runs-on }}.txt
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact # NOTE: same artifact name
path: file-${{ matrix.runs-on }}.txt
download:
needs: upload
runs-on: ubuntu-latest
steps:
- name: Download All Artifacts
uses: actions/download-artifact@v3
with:
name: my-artifact
path: my-artifact
- run: ls -R my-artifact
```
This results in a directory like so:
```
my-artifact/
file-macos-latest.txt
file-ubuntu-latest.txt
file-windows-latest.txt
```
In v4, Artifacts are immutable (unless deleted). So you must change each of the uploaded Artifacts to have a different name and filter the downloads by name to achieve the same effect:
```diff
jobs:
upload:
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.runs-on }}
steps:
- 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
with:
- name: my-artifact
+ name: my-artifact-${{ matrix.runs-on }}
path: file-${{ matrix.runs-on }}.txt
download:
needs: upload
runs-on: ubuntu-latest
steps:
- name: Download All Artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
- name: my-artifact
path: my-artifact
+ pattern: my-artifact-*
+ merge-multiple: true
- run: ls -R my-artifact
```
In `v4`, the new `pattern:` input will filter the downloaded Artifacts to match the name specified. The new `merge-multiple:` input will support downloading multiple Artifacts to the same directory. If the files within the Artifacts have the same name, the last writer wins.
## Overwriting an Artifact
In `v3`, the contents of an Artifact were mutable so something like the following was possible:
```yaml
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Create a file
run: echo "hello world" > my-file.txt
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact # NOTE: same artifact name
path: my-file.txt
upload-again:
needs: upload
runs-on: ubuntu-latest
steps:
- name: Create a different file
run: echo "goodbye world" > my-file.txt
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact # NOTE: same artifact name
path: my-file.txt
```
The resulting `my-file.txt` in `my-artifact` will have "goodbye world" as the content.
In `v4`, Artifacts are immutable unless deleted. To achieve this same behavior, you can use `overwrite: true` to delete the Artifact before a new one is created:
```diff
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Create a file
run: echo "hello world" > my-file.txt
- name: Upload Artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: my-artifact # NOTE: same artifact name
path: my-file.txt
upload-again:
needs: upload
runs-on: ubuntu-latest
steps:
- name: Create a different file
run: echo "goodbye world" > my-file.txt
- name: Upload Artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: my-artifact # NOTE: same artifact name
path: my-file.txt
+ overwrite: true
```
Note that this will create an _entirely_ new Artifact, with a different ID from the previous.
## Merging multiple artifacts
In `v3`, multiple uploads from multiple jobs could be done to the same Artifact. This would result in a single archive, which could be useful for sending to upstream systems outside of Actions via API or UI downloads.
```yaml
jobs:
upload:
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.runs-on }}
steps:
- name: Create a File
run: echo "hello from ${{ matrix.runs-on }}" > file-${{ matrix.runs-on }}.txt
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: all-my-files # NOTE: same artifact name
path: file-${{ matrix.runs-on }}.txt
```
The single `all-my-files` artifact would contain the following:
```
.
∟ file-ubuntu-latest.txt
∟ file-macos-latest.txt
∟ file-windows-latest.txt
```
To achieve the same in `v4` you can change it like so:
```diff
jobs:
upload:
strategy:
matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.runs-on }}
steps:
- 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
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-*
```
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).

12
jest.config.js Normal file
View File

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

200
merge/README.md Normal file
View File

@ -0,0 +1,200 @@
# `@actions/upload-artifact/merge`
Merge multiple [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) in Workflow Runs. Internally powered by [@actions/artifact](https://github.com/actions/toolkit/tree/main/packages/artifact) package.
- [`@actions/upload-artifact/merge`](#actionsupload-artifactmerge)
- [Usage](#usage)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [Examples](#examples)
- [Combining all artifacts in a workflow run](#combining-all-artifacts-in-a-workflow-run)
- [Prefix directories in merged artifact](#prefix-directories-in-merged-artifact)
- [Deleting artifacts after merge](#deleting-artifacts-after-merge)
- [Retention and Compression Level](#retention-and-compression-level)
## Usage
> [!IMPORTANT]
> upload-artifact/merge@v4+ is not currently supported on GHES.
Note: this actions can only merge artifacts created with actions/upload-artifact@v4+
This sub-action is a helper to merge multiple artifacts after they are created. To do so, it will download multiple artifacts to a temporary directory and reupload them as a single artifact.
For most cases, this may not be the most efficient solution. See [the migration docs](../docs/MIGRATION.md#multiple-uploads-to-the-same-named-artifact) on how to download multiple artifacts to the same directory on a runner. This action should only be necessary for cases where multiple artifacts will need to be downloaded outside the runner environment, like downloads via the UI or REST API.
### Inputs
```yaml
- uses: actions/upload-artifact/merge@v4
with:
# The name of the artifact that the artifacts will be merged into
# Optional. Default is 'merged-artifacts'
name:
# A glob pattern matching the artifacts that should be merged.
# Optional. Default is '*'
pattern:
# If true, the artifacts will be merged into separate directories.
# If false, the artifacts will be merged into the root of the destination.
# Optional. Default is 'false'
separate-directories:
# If true, the artifacts that were merged will be deleted.
# If false, the artifacts will still exist.
# Optional. Default is 'false'
delete-merged:
# Duration after which artifact will expire in days. 0 means using default retention.
# Minimum 1 day.
# Maximum 90 days unless changed from the repository settings page.
# Optional. Defaults to repository settings.
retention-days:
# The level of compression for Zlib to be applied to the artifact archive.
# The value can range from 0 to 9.
# For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
# Optional. Default is '6'
compression-level:
```
### Outputs
| Name | Description | Example |
| - | - | - |
| `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` |
## Examples
For each of these examples, assume we have a prior job matrix that generates three artifacts: `my-artifact-a`, `my-artifact-b` and `my-artifact-c`.
e.g.
```yaml
jobs:
upload:
runs-on: ubuntu-latest
strategy:
matrix:
foo: [a, b, c]
steps:
- name: Run a one-line script
run: echo "hello from job ${{ matrix.foo }}" > file-${{ matrix.foo }}.txt
- name: Upload
uses: actions/upload-artifact@v4
with:
name: my-artifact-${{ matrix.foo }}
path: file-${{ matrix.foo }}.txt
```
Each of the following examples will use the `needs: upload` as a prerequesite before any merging operations.
### Combining all artifacts in a workflow run
By default (with no inputs), calling this action will take all the artifacts in the workflow run and combined them into a single artifact called `merged-artifacts`:
```yaml
jobs:
# ... <upload job> ...
merge:
runs-on: ubuntu-latest
needs: upload
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
```
This will result in an artifact called `merged-artifacts` with the following content:
```
.
∟ file-a.txt
∟ file-b.txt
∟ file-c.txt
```
To change the name of the artifact and filter on what artifacts are added, you can use the `name` and `pattern` inputs:
```yaml
jobs:
# ... <upload job> ...
merge:
runs-on: ubuntu-latest
needs: upload
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
name: my-amazing-merged-artifact
pattern: my-artifact-*
```
### Prefix directories in merged artifact
To prevent overwriting files in artifacts that may have the same name, you can use the `separate-directories` to prefix the extracted files with directories (named after the original artifact):
```yaml
jobs:
# ... <upload job> ...
merge:
runs-on: ubuntu-latest
needs: upload
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
separate-directories: true
```
This will result in the following artifact structure:
```
.
∟ my-artifact-a
∟ file-a.txt
∟ my-artifact-b
∟ file-b.txt
∟ my-artifact-c
∟ file-c.txt
```
### Deleting artifacts after merge
After merge, the old artifacts may no longer be required. To automatically delete them after they are merged into a new artifact, you can use `delete-merged` like so:
```yaml
jobs:
# ... <upload job> ...
merge:
runs-on: ubuntu-latest
needs: upload
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
delete-merged: true
```
After this runs, the matching artifact (`my-artifact-a`, `my-artifact-b` and `my-artifact-c`) will be merged.
### Retention and Compression Level
Similar to actions/upload-artifact, both [`retention-days`](../README.md#retention-period) and [`compression-level`](../README.md#altering-compressions-level-speed-v-size) are supported:
```yaml
jobs:
# ... <upload job> ...
merge:
runs-on: ubuntu-latest
needs: upload
steps:
- name: Merge Artifacts
uses: actions/upload-artifact/merge@v4
with:
retention-days: 1
compression-level: 9
```

57
merge/action.yml Normal file
View File

@ -0,0 +1,57 @@
name: 'Merge Build Artifacts'
description: 'Merge one or more build Artifacts'
author: 'GitHub'
inputs:
name:
description: 'The name of the artifact that the artifacts will be merged into.'
required: true
default: 'merged-artifacts'
pattern:
description: 'A glob pattern matching the artifact names that should be merged.'
default: '*'
separate-directories:
description: 'When multiple artifacts are matched, this changes the behavior of how they are merged in the archive.
If true, the matched artifacts will be extracted into individual named directories within the specified path.
If false, the matched artifacts will combined in the same directory.'
default: 'false'
retention-days:
description: >
Duration after which artifact will expire in days. 0 means using default retention.
Minimum 1 day.
Maximum 90 days unless changed from the repository settings page.
compression-level:
description: >
The level of compression for Zlib to be applied to the artifact archive.
The value can range from 0 to 9:
- 0: No compression
- 1: Best speed
- 6: Default compression (same as GNU Gzip)
- 9: Best compression
Higher levels will result in better compression, but will take longer to complete.
For large files that are not easily compressed, a value of 0 is recommended for significantly faster uploads.
default: '6'
delete-merged:
description: >
If true, the artifacts that were merged will be deleted.
If false, the artifacts will still exist.
default: 'false'
outputs:
artifact-id:
description: >
A unique identifier for the artifact that was just uploaded. Empty if the artifact upload failed.
This ID can be used as input to other APIs to download, delete or get more information about an artifact: https://docs.github.com/en/rest/actions/artifacts
artifact-url:
description: >
A download URL for the artifact that was just uploaded. Empty if the artifact upload failed.
This download URL only works for requests Authenticated with GitHub. Anonymous downloads will be prompted to first login.
If an anonymous download URL is needed than a short time restricted URL can be generated using the download artifact API: https://docs.github.com/en/rest/actions/artifacts#download-an-artifact
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.
runs:
using: 'node20'
main: '../dist/merge/index.js'

13879
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

55
package.json Normal file
View File

@ -0,0 +1,55 @@
{
"name": "upload-artifact",
"version": "4.3.6",
"description": "Upload an Actions Artifact in a workflow run",
"main": "dist/upload/index.js",
"scripts": {
"build": "tsc",
"release": "ncc build src/upload/index.ts -o dist/upload && ncc build src/merge/index.ts -o dist/merge",
"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",
"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"
},
"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"
}
}

9
src/merge/constants.ts Normal file
View File

@ -0,0 +1,9 @@
/* eslint-disable no-unused-vars */
export enum Inputs {
Name = 'name',
Pattern = 'pattern',
SeparateDirectories = 'separate-directories',
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
DeleteMerged = 'delete-merged'
}

6
src/merge/index.ts Normal file
View File

@ -0,0 +1,6 @@
import * as core from '@actions/core'
import {run} from './merge-artifacts'
run().catch(error => {
core.setFailed((error as Error).message)
})

44
src/merge/input-helper.ts Normal file
View File

@ -0,0 +1,44 @@
import * as core from '@actions/core'
import {Inputs} from './constants'
import {MergeInputs} from './merge-inputs'
/**
* Helper to get all the inputs for the action
*/
export function getInputs(): MergeInputs {
const name = core.getInput(Inputs.Name, {required: true})
const pattern = core.getInput(Inputs.Pattern, {required: true})
const separateDirectories = core.getBooleanInput(Inputs.SeparateDirectories)
const deleteMerged = core.getBooleanInput(Inputs.DeleteMerged)
const inputs = {
name,
pattern,
separateDirectories,
deleteMerged,
retentionDays: 0,
compressionLevel: 6
} as MergeInputs
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
if (retentionDaysStr) {
inputs.retentionDays = parseInt(retentionDaysStr)
if (isNaN(inputs.retentionDays)) {
core.setFailed('Invalid retention-days')
}
}
const compressionLevelStr = core.getInput(Inputs.CompressionLevel)
if (compressionLevelStr) {
inputs.compressionLevel = parseInt(compressionLevelStr)
if (isNaN(inputs.compressionLevel)) {
core.setFailed('Invalid compression-level')
}
if (inputs.compressionLevel < 0 || inputs.compressionLevel > 9) {
core.setFailed('Invalid compression-level. Valid values are 0-9')
}
}
return inputs
}

View File

@ -0,0 +1,93 @@
import * as path from 'path'
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'
const PARALLEL_DOWNLOADS = 5
export const chunk = <T>(arr: T[], n: number): T[][] =>
arr.reduce((acc, cur, i) => {
const index = Math.floor(i / n)
acc[index] = [...(acc[index] || []), cur]
return acc
}, [] as T[][])
export async function run(): Promise<void> {
const inputs = getInputs()
const tmpDir = await mkdtemp('merge-artifact')
const listArtifactResponse = await artifactClient.listArtifacts({
latest: true
})
const matcher = new Minimatch(inputs.pattern)
const artifacts = listArtifactResponse.artifacts.filter(artifact =>
matcher.match(artifact.name)
)
core.debug(
`Filtered from ${listArtifactResponse.artifacts.length} to ${artifacts.length} artifacts`
)
if (artifacts.length === 0) {
throw new Error(`No artifacts found matching pattern '${inputs.pattern}'`)
}
core.info(`Preparing to download the following artifacts:`)
artifacts.forEach(artifact => {
core.info(`- ${artifact.name} (ID: ${artifact.id}, Size: ${artifact.size})`)
})
const downloadPromises = artifacts.map(artifact =>
artifactClient.downloadArtifact(artifact.id, {
path: inputs.separateDirectories
? path.join(tmpDir, artifact.name)
: tmpDir
})
)
const chunkedPromises = chunk(downloadPromises, PARALLEL_DOWNLOADS)
for (const chunk of chunkedPromises) {
await Promise.all(chunk)
}
const options: UploadArtifactOptions = {}
if (inputs.retentionDays) {
options.retentionDays = inputs.retentionDays
}
if (typeof inputs.compressionLevel !== 'undefined') {
options.compressionLevel = inputs.compressionLevel
}
const searchResult = await findFilesToUpload(tmpDir)
await uploadArtifact(
inputs.name,
searchResult.filesToUpload,
searchResult.rootDirectory,
options
)
core.info(
`The ${artifacts.length} artifact(s) have been successfully merged!`
)
if (inputs.deleteMerged) {
const deletePromises = artifacts.map(artifact =>
artifactClient.deleteArtifact(artifact.name)
)
await Promise.all(deletePromises)
core.info(`The ${artifacts.length} artifact(s) have been deleted`)
}
try {
await rm(tmpDir, {recursive: true})
} catch (error) {
core.warning(
`Unable to remove temporary directory: ${(error as Error).message}`
)
}
}

33
src/merge/merge-inputs.ts Normal file
View File

@ -0,0 +1,33 @@
export interface MergeInputs {
/**
* The name of the artifact that the artifacts will be merged into
*/
name: string
/**
* A glob pattern matching the artifacts that should be merged.
*/
pattern: string
/**
* Duration after which artifact will expire in days
*/
retentionDays: number
/**
* The level of compression for Zlib to be applied to the artifact archive.
*/
compressionLevel?: number
/**
* If true, the artifacts that were merged will be deleted.
* If false, the artifacts will still exist.
*/
deleteMerged: boolean
/**
* If true, the artifacts will be merged into separate directories.
* If false, the artifacts will be merged into the root of the destination.
*/
separateDirectories: boolean
}

157
src/shared/search.ts Normal file
View File

@ -0,0 +1,157 @@
import * as glob from '@actions/glob'
import * as path from 'path'
import {debug, info} from '@actions/core'
import {stat} from 'fs'
import {dirname} from 'path'
import {promisify} from 'util'
const stats = promisify(stat)
export interface SearchResult {
filesToUpload: string[]
rootDirectory: string
}
function getDefaultGlobOptions(): glob.GlobOptions {
return {
followSymbolicLinks: true,
implicitDescendants: true,
omitBrokenSymbolicLinks: true
}
}
/**
* If multiple paths are specific, the least common ancestor (LCA) of the search paths is used as
* the delimiter to control the directory structure for the artifact. This function returns the LCA
* when given an array of search paths
*
* Example 1: The patterns `/foo/` and `/bar/` returns `/`
*
* Example 2: The patterns `~/foo/bar/*` and `~/foo/voo/two/*` and `~/foo/mo/` returns `~/foo`
*/
function getMultiPathLCA(searchPaths: string[]): string {
if (searchPaths.length < 2) {
throw new Error('At least two search paths must be provided')
}
const commonPaths = new Array<string>()
const splitPaths = new Array<string[]>()
let smallestPathLength = Number.MAX_SAFE_INTEGER
// split each of the search paths using the platform specific separator
for (const searchPath of searchPaths) {
debug(`Using search path ${searchPath}`)
const splitSearchPath = path.normalize(searchPath).split(path.sep)
// keep track of the smallest path length so that we don't accidentally later go out of bounds
smallestPathLength = Math.min(smallestPathLength, splitSearchPath.length)
splitPaths.push(splitSearchPath)
}
// on Unix-like file systems, the file separator exists at the beginning of the file path, make sure to preserve it
if (searchPaths[0].startsWith(path.sep)) {
commonPaths.push(path.sep)
}
let splitIndex = 0
// function to check if the paths are the same at a specific index
function isPathTheSame(): boolean {
const compare = splitPaths[0][splitIndex]
for (let i = 1; i < splitPaths.length; i++) {
if (compare !== splitPaths[i][splitIndex]) {
// a non-common index has been reached
return false
}
}
return true
}
// loop over all the search paths until there is a non-common ancestor or we go out of bounds
while (splitIndex < smallestPathLength) {
if (!isPathTheSame()) {
break
}
// if all are the same, add to the end result & increment the index
commonPaths.push(splitPaths[0][splitIndex])
splitIndex++
}
return path.join(...commonPaths)
}
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()
/*
Files are saved with case insensitivity. Uploading both a.txt and A.txt will files to be overwritten
Detect any files that could be overwritten for user awareness
*/
const set = new Set<string>()
/*
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) {
const fileStats = await stats(searchResult)
// isDirectory() returns false for symlinks if using fs.lstat(), make sure to use fs.stat() instead
if (!fileStats.isDirectory()) {
debug(`File:${searchResult} was found using the provided searchPath`)
searchResults.push(searchResult)
// detect any files that would be overwritten because of case insensitivity
if (set.has(searchResult.toLowerCase())) {
info(
`Uploads are case insensitive: ${searchResult} was detected that it will be overwritten by another file with the same path`
)
} else {
set.add(searchResult.toLowerCase())
}
} else {
debug(
`Removing ${searchResult} from rawSearchResults because it is a directory`
)
}
}
// Calculate the root directory for the artifact using the search paths that were utilized
const searchPaths: string[] = globber.getSearchPaths()
if (searchPaths.length > 1) {
info(
`Multiple search paths detected. Calculating the least common ancestor of all paths`
)
const lcaSearchPath = getMultiPathLCA(searchPaths)
info(
`The least common ancestor is ${lcaSearchPath}. This will be the root directory of the artifact`
)
return {
filesToUpload: searchResults,
rootDirectory: lcaSearchPath
}
}
/*
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]
}
}

View File

@ -0,0 +1,28 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import artifact, {UploadArtifactOptions} from '@actions/artifact'
export async function uploadArtifact(
artifactName: string,
filesToUpload: string[],
rootDirectory: string,
options: UploadArtifactOptions
) {
const uploadResponse = await artifact.uploadArtifact(
artifactName,
filesToUpload,
rootDirectory,
options
)
core.info(
`Artifact ${artifactName} has been successfully uploaded! Final size is ${uploadResponse.size} bytes. Artifact ID is ${uploadResponse.id}`
)
core.setOutput('artifact-id', uploadResponse.id)
const repository = github.context.repo
const artifactURL = `${github.context.serverUrl}/${repository.owner}/${repository.repo}/actions/runs/${github.context.runId}/artifacts/${uploadResponse.id}`
core.info(`Artifact download URL: ${artifactURL}`)
core.setOutput('artifact-url', artifactURL)
}

26
src/upload/constants.ts Normal file
View File

@ -0,0 +1,26 @@
/* eslint-disable no-unused-vars */
export enum Inputs {
Name = 'name',
Path = 'path',
IfNoFilesFound = 'if-no-files-found',
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
Overwrite = 'overwrite'
}
export enum NoFileOptions {
/**
* Default. Output a warning but do not fail the action
*/
warn = 'warn',
/**
* Fail the action with an error message
*/
error = 'error',
/**
* Do not output any warnings or errors, the action does not fail
*/
ignore = 'ignore'
}

6
src/upload/index.ts Normal file
View File

@ -0,0 +1,6 @@
import * as core from '@actions/core'
import {run} from './upload-artifact'
run().catch(error => {
core.setFailed((error as Error).message)
})

View File

@ -0,0 +1,54 @@
import * as core from '@actions/core'
import {Inputs, NoFileOptions} from './constants'
import {UploadInputs} from './upload-inputs'
/**
* Helper to get all the inputs for the action
*/
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 ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
if (!noFileBehavior) {
core.setFailed(
`Unrecognized ${
Inputs.IfNoFilesFound
} input. Provided: ${ifNoFilesFound}. Available options: ${Object.keys(
NoFileOptions
)}`
)
}
const inputs = {
artifactName: name,
searchPath: path,
ifNoFilesFound: noFileBehavior,
overwrite: overwrite
} as UploadInputs
const retentionDaysStr = core.getInput(Inputs.RetentionDays)
if (retentionDaysStr) {
inputs.retentionDays = parseInt(retentionDaysStr)
if (isNaN(inputs.retentionDays)) {
core.setFailed('Invalid retention-days')
}
}
const compressionLevelStr = core.getInput(Inputs.CompressionLevel)
if (compressionLevelStr) {
inputs.compressionLevel = parseInt(compressionLevelStr)
if (isNaN(inputs.compressionLevel)) {
core.setFailed('Invalid compression-level')
}
if (inputs.compressionLevel < 0 || inputs.compressionLevel > 9) {
core.setFailed('Invalid compression-level. Valid values are 0-9')
}
}
return inputs
}

View File

@ -0,0 +1,77 @@
import * as core from '@actions/core'
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'
async function deleteArtifactIfExists(artifactName: string): Promise<void> {
try {
await artifact.deleteArtifact(artifactName)
} catch (error) {
if (error instanceof ArtifactNotFoundError) {
core.debug(`Skipping deletion of '${artifactName}', it does not exist`)
return
}
// Best effort, we don't want to fail the action if this fails
core.debug(`Unable to delete artifact: ${(error as Error).message}`)
}
}
export async function run(): Promise<void> {
const inputs = getInputs()
const searchResult = await findFilesToUpload(inputs.searchPath)
if (searchResult.filesToUpload.length === 0) {
// No files were found, different use cases warrant different types of behavior if nothing is found
switch (inputs.ifNoFilesFound) {
case NoFileOptions.warn: {
core.warning(
`No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.`
)
break
}
case NoFileOptions.error: {
core.setFailed(
`No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.`
)
break
}
case NoFileOptions.ignore: {
core.info(
`No files were found with the provided path: ${inputs.searchPath}. No artifacts will be uploaded.`
)
break
}
}
} else {
const s = searchResult.filesToUpload.length === 1 ? '' : 's'
core.info(
`With the provided path, there will be ${searchResult.filesToUpload.length} file${s} uploaded`
)
core.debug(`Root artifact directory is ${searchResult.rootDirectory}`)
if (inputs.overwrite) {
await deleteArtifactIfExists(inputs.artifactName)
}
const options: UploadArtifactOptions = {}
if (inputs.retentionDays) {
options.retentionDays = inputs.retentionDays
}
if (typeof inputs.compressionLevel !== 'undefined') {
options.compressionLevel = inputs.compressionLevel
}
await uploadArtifact(
inputs.artifactName,
searchResult.filesToUpload,
searchResult.rootDirectory,
options
)
}
}

View File

@ -0,0 +1,33 @@
import {NoFileOptions} from './constants'
export interface UploadInputs {
/**
* The name of the artifact that will be uploaded
*/
artifactName: string
/**
* The search path used to describe what to upload as part of the artifact
*/
searchPath: string
/**
* The desired behavior if no files are found with the provided search path
*/
ifNoFilesFound: NoFileOptions
/**
* Duration after which artifact will expire in days
*/
retentionDays: number
/**
* The level of compression for Zlib to be applied to the artifact archive.
*/
compressionLevel?: number
/**
* Whether or not to replace an existing artifact with the same name
*/
overwrite: boolean
}

17
tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./lib",
"rootDir": "./src",
"strict": true,
"noImplicitAny": false,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"declaration": false,
"sourceMap": true,
"lib": ["es6"]
},
"exclude": ["node_modules", "**/*.test.ts"]
}