Compare commits

..

10 Commits

Author SHA1 Message Date
Daniel Kennedy
2aa2914a67 eslint: don't allow common-js imports 2026-02-25 13:49:28 -05:00
Daniel Kennedy
c36c5fbf26 More licensed fixes 2026-02-25 13:44:14 -05:00
Daniel Kennedy
8c63b6d07a Try fixing licenced issues 2026-02-25 13:30:34 -05:00
Daniel Kennedy
0508a90968 Bump minimatch to 10.1.1 2026-02-25 13:24:55 -05:00
Daniel Kennedy
a2008cd1d8 Cache licenses 2026-02-25 13:18:03 -05:00
Daniel Kennedy
7d7cf5541f Bump the package version 2026-02-25 13:13:12 -05:00
Daniel Kennedy
56b3ba9990 Fix linting issues 2026-02-25 13:12:50 -05:00
Daniel Kennedy
d025a8c768 Push build files 2026-02-25 13:08:22 -05:00
Daniel Kennedy
15be44e9f1 CI: bump node versions 2026-02-25 13:05:51 -05:00
Daniel Kennedy
f119f587c2 Upgrade the module to ESM and bump dependencies 2026-02-25 12:57:27 -05:00
11 changed files with 67 additions and 274 deletions

View File

@@ -10,10 +10,6 @@ on:
paths-ignore:
- '**.md'
permissions:
contents: read
actions: write
jobs:
build:
name: Build
@@ -98,7 +94,7 @@ jobs:
# Download Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: 'Artifact-A-${{ matrix.runs-on }}'
path: some/new/path
@@ -118,7 +114,7 @@ jobs:
# Download Artifact #2 and verify the correctness of the content
- name: 'Download artifact #2'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: 'Artifact-Wildcard-${{ matrix.runs-on }}'
path: some/other/path
@@ -139,7 +135,7 @@ jobs:
# Download Artifact #4 and verify the correctness of the content
- name: 'Download artifact #4'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: 'Multi-Path-Artifact-${{ matrix.runs-on }}'
path: multi/artifact
@@ -159,7 +155,7 @@ jobs:
shell: pwsh
- name: 'Download symlinked artifact'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: 'Symlinked-Artifact-${{ matrix.runs-on }}'
path: from/symlink
@@ -200,7 +196,7 @@ jobs:
# Download replaced Artifact #1 and verify the correctness of the content
- name: 'Download artifact #1 again'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: 'Artifact-A-${{ matrix.runs-on }}'
path: overwrite/some/new/path
@@ -217,101 +213,6 @@ jobs:
Write-Error "File contents of downloaded artifact are incorrect"
}
shell: pwsh
# Upload a single file without archiving (direct file upload)
- name: 'Create direct upload file'
run: echo -n 'direct file upload content' > direct-upload-${{ matrix.runs-on }}.txt
shell: bash
- name: 'Upload direct file artifact'
uses: ./
with:
name: 'Direct-File-${{ matrix.runs-on }}'
path: direct-upload-${{ matrix.runs-on }}.txt
archive: false
- name: 'Download direct file artifact'
uses: actions/download-artifact@main
with:
name: direct-upload-${{ matrix.runs-on }}.txt
path: direct-download
- name: 'Verify direct file artifact'
run: |
$file = "direct-download/direct-upload-${{ matrix.runs-on }}.txt"
if(!(Test-Path -path $file))
{
Write-Error "Expected file does not exist"
}
if(!((Get-Content $file -Raw).TrimEnd() -ceq "direct file upload content"))
{
Write-Error "File contents of downloaded artifact are incorrect"
}
shell: pwsh
upload-html-report:
name: Upload HTML Report
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node 24
uses: actions/setup-node@v4
with:
node-version: 24.x
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Compile
run: npm run build
- name: Create HTML report
run: |
cat > report.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Artifact Upload Test Report</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; max-width: 800px; margin: 40px auto; padding: 0 20px; color: #24292f; }
h1 { border-bottom: 1px solid #d0d7de; padding-bottom: 8px; }
.success { color: #1a7f37; }
.info { background: #ddf4ff; border: 1px solid #54aeff; border-radius: 6px; padding: 12px 16px; margin: 16px 0; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
th, td { border: 1px solid #d0d7de; padding: 8px 12px; text-align: left; }
th { background: #f6f8fa; }
</style>
</head>
<body>
<h1>Artifact Upload Test Report</h1>
<div class="info">
<strong>This HTML file was uploaded as a single un-zipped artifact.</strong>
If you can see this in the browser, the feature is working correctly!
</div>
<table>
<tr><th>Property</th><th>Value</th></tr>
<tr><td>Upload method</td><td><code>archive: false</code></td></tr>
<tr><td>Content-Type</td><td><code>text/html</code></td></tr>
<tr><td>File</td><td><code>report.html</code></td></tr>
</table>
<p class="success">&#10004; Single file upload is working!</p>
</body>
</html>
EOF
- name: Upload HTML report (no archive)
uses: ./
with:
name: 'test-report'
path: report.html
archive: false
merge:
name: Merge
needs: build
@@ -329,7 +230,7 @@ jobs:
# easier to identify each of the merged artifacts
separate-directories: true
- name: 'Download merged artifacts'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: merged-artifacts
path: all-merged-artifacts
@@ -365,7 +266,7 @@ jobs:
# Download merged artifacts and verify the correctness of the content
- name: 'Download merged artifacts'
uses: actions/download-artifact@main
uses: actions/download-artifact@v4
with:
name: Merged-Artifact-As
path: merged-artifact-a
@@ -389,40 +290,3 @@ jobs:
}
shell: pwsh
cleanup:
name: Cleanup Artifacts
needs: [build, merge]
runs-on: ubuntu-latest
steps:
- name: Delete test artifacts
uses: actions/github-script@v8
with:
script: |
const keep = ['report.html'];
const owner = context.repo.owner;
const repo = context.repo.repo;
const runId = context.runId;
const {data: {artifacts}} = await github.rest.actions.listWorkflowRunArtifacts({
owner,
repo,
run_id: runId
});
for (const a of artifacts) {
if (keep.includes(a.name)) {
console.log(`Keeping artifact '${a.name}'`);
continue;
}
try {
await github.rest.actions.deleteArtifact({
owner,
repo,
artifact_id: a.id
});
console.log(`Deleted artifact '${a.name}'`);
} catch (err) {
console.log(`Could not delete artifact '${a.name}': ${err.message}`);
}
}

View File

@@ -11,14 +11,15 @@ Upload [Actions Artifacts](https://docs.github.com/en/actions/using-workflows/st
See also [download-artifact](https://github.com/actions/download-artifact).
- [`@actions/upload-artifact`](#actionsupload-artifact)
- [What's new](#whats-new)
- [GHES Support](#ghes-support)
- [v6 - What's new](#v6---whats-new)
- [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 (Zipped)](#upload-an-individual-file-zipped)
- [Upload an Individual File (Unzipped)](#upload-an-individual-file-unzipped)
- [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)
@@ -33,16 +34,53 @@ See also [download-artifact](https://github.com/actions/download-artifact).
- [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)
## What's new
Check out the [releases page](https://github.com/actions/upload-artifact/releases) for details on what's new.
## v6 - What's new
> [!IMPORTANT]
> actions/upload-artifact@v6 now runs on Node.js 24 (`runs.using: node24`) and requires a minimum Actions Runner version of 2.327.1. If you are using self-hosted runners, ensure they are updated before upgrading.
### Node.js 24
This release updates the runtime to Node.js 24. v5 had preliminary support for Node.js 24, however this action was by default still running on Node.js 20. Now this action by default will run on Node.js 24.
## v4 - What's new
> [!IMPORTANT]
> upload-artifact@v4+ is not currently supported on GitHub Enterprise Server (GHES) yet. If you are on GHES, you must use [v3](https://github.com/actions/upload-artifact/releases/tag/v3) (Node 16) or [v3-node20](https://github.com/actions/upload-artifact/releases/tag/v3-node20) (Node 20).
The release of upload-artifact@v4 and download-artifact@v4 are major changes to the backend architecture of Artifacts. They have numerous performance and behavioral improvements.
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.
4. With `v4.4` and later, hidden files are excluded by default.
For assistance with breaking changes, see [MIGRATION.md](docs/MIGRATION.md).
## Note
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
Thank you for your interest in this GitHub repo, however, right now we are not taking contributions.
We continue to focus our resources on strategic areas that help our customers be successful while making developers' lives easier. While GitHub Actions remains a key part of this vision, we are allocating resources towards other areas of Actions and are not taking contributions to this repository at this time. The GitHub public roadmap is the best place to follow along for any updates on features were working on and what stage theyre in.
@@ -58,10 +96,6 @@ We will still provide security updates for this project and fix major breaking c
You are welcome to still raise bugs in this repo.
## GHES Support
`upload-artifact@v4+` is not currently supported on GitHub Enterprise Server (GHES). If you are on GHES, you must use [v3.2.2](https://github.com/actions/upload-artifact/releases/tag/v3.2.2) (Node 24) or [v3.2.2-node20](https://github.com/actions/upload-artifact/releases/tag/v3.2.2-node20) (Node 20).
## Usage
### Inputs
@@ -108,11 +142,6 @@ You are welcome to still raise bugs in this repo.
# enabled this to avoid uploading sensitive information.
# Optional. Default is 'false'
include-hidden-files:
# Whether to zip the artifact files before upload
# If 'false', only a single file can be uploaded. The name of the file will be used as the artifact name (the 'name' parameter is ignored)
# Optional. Default is 'true'
archive:
```
### Outputs
@@ -125,7 +154,7 @@ You are welcome to still raise bugs in this repo.
## Examples
### Upload an Individual File (Zipped)
### Upload an Individual File
```yaml
steps:
@@ -137,18 +166,6 @@ steps:
path: path/to/artifact/world.txt
```
### Upload an Individual File (Unzipped)
```yaml
steps:
- run: mkdir -p path/to/artifact
- run: echo hello > path/to/artifact/world.txt
- uses: actions/upload-artifact@v4
with:
path: path/to/artifact/world.txt
archive: false
```
### Upload an Entire Directory
```yaml
@@ -459,11 +476,15 @@ Within an individual job, there is a limit of 500 artifacts that can be created
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 zipped 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 with `archive: true`, post-download the file is no longer guaranteed to be set as an executable.
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 and upload that file directly with `archive: false`. Post download, the `tar` file will maintain file permissions and case sensitivity.
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'
@@ -472,8 +493,8 @@ If you must preserve permissions, you can `tar` all of your files together befor
- name: 'Upload Artifact'
uses: actions/upload-artifact@v4
with:
name: my-artifact
path: my_files.tar
archive: false
```
## Where does the upload go?

View File

@@ -72,7 +72,6 @@ const mockInputs = (
[Inputs.RetentionDays]: 0,
[Inputs.CompressionLevel]: 6,
[Inputs.Overwrite]: false,
[Inputs.Archive]: true,
...overrides
}
@@ -274,57 +273,4 @@ describe('upload', () => {
`Skipping deletion of '${fixtures.artifactName}', it does not exist`
)
})
test('passes skipArchive when archive is false', async () => {
mockInputs({
[Inputs.Archive]: false
})
mockFindFilesToUpload.mockResolvedValue({
filesToUpload: [fixtures.filesToUpload[0]],
rootDirectory: fixtures.rootDirectory
})
await run()
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
[fixtures.filesToUpload[0]],
fixtures.rootDirectory,
{compressionLevel: 6, skipArchive: true}
)
})
test('does not pass skipArchive when archive is true', async () => {
mockInputs({
[Inputs.Archive]: true
})
mockFindFilesToUpload.mockResolvedValue({
filesToUpload: [fixtures.filesToUpload[0]],
rootDirectory: fixtures.rootDirectory
})
await run()
expect(artifact.default.uploadArtifact).toHaveBeenCalledWith(
fixtures.artifactName,
[fixtures.filesToUpload[0]],
fixtures.rootDirectory,
{compressionLevel: 6}
)
})
test('fails when archive is false and multiple files are provided', async () => {
mockInputs({
[Inputs.Archive]: false
})
await run()
expect(core.setFailed).toHaveBeenCalledWith(
`When 'archive' is set to false, only a single file can be uploaded. Found ${fixtures.filesToUpload.length} files to upload.`
)
expect(artifact.default.uploadArtifact).not.toHaveBeenCalled()
})
})

View File

@@ -3,10 +3,10 @@ description: 'Upload a build artifact that can be used by subsequent workflow st
author: 'GitHub'
inputs:
name:
description: 'Artifact name. If the `archive` input is `false`, the name of the file uploaded will be the artifact name.'
description: 'Artifact name'
default: 'artifact'
path:
description: 'A file, directory or wildcard pattern that describes what to upload.'
description: 'A file, directory or wildcard pattern that describes what to upload'
required: true
if-no-files-found:
description: >
@@ -45,12 +45,6 @@ inputs:
If true, hidden files will be included in the artifact.
If false, hidden files will be excluded from the artifact.
default: 'false'
archive:
description: >
If true, the artifact will be archived (zipped) before uploading.
If false, the artifact will be uploaded as-is without archiving.
When `archive` is `false`, only a single file can be uploaded. The name of the file will be used as the artifact name (ignoring the `name` parameter).
default: 'true'
outputs:
artifact-id:

13
dist/upload/index.js vendored
View File

@@ -130457,7 +130457,6 @@ var Inputs;
Inputs["CompressionLevel"] = "compression-level";
Inputs["Overwrite"] = "overwrite";
Inputs["IncludeHiddenFiles"] = "include-hidden-files";
Inputs["Archive"] = "archive";
})(Inputs || (Inputs = {}));
var NoFileOptions;
(function (NoFileOptions) {
@@ -130486,7 +130485,6 @@ function getInputs() {
const path = getInput(Inputs.Path, { required: true });
const overwrite = getBooleanInput(Inputs.Overwrite);
const includeHiddenFiles = getBooleanInput(Inputs.IncludeHiddenFiles);
const archive = getBooleanInput(Inputs.Archive);
const ifNoFilesFound = getInput(Inputs.IfNoFilesFound);
const noFileBehavior = NoFileOptions[ifNoFilesFound];
if (!noFileBehavior) {
@@ -130497,8 +130495,7 @@ function getInputs() {
searchPath: path,
ifNoFilesFound: noFileBehavior,
overwrite: overwrite,
includeHiddenFiles: includeHiddenFiles,
archive: archive
includeHiddenFiles: includeHiddenFiles
};
const retentionDaysStr = getInput(Inputs.RetentionDays);
if (retentionDaysStr) {
@@ -130579,11 +130576,6 @@ async function run() {
const s = searchResult.filesToUpload.length === 1 ? '' : 's';
info(`With the provided path, there will be ${searchResult.filesToUpload.length} file${s} uploaded`);
core_debug(`Root artifact directory is ${searchResult.rootDirectory}`);
// Validate that only a single file is uploaded when archive is false
if (!inputs.archive && searchResult.filesToUpload.length > 1) {
setFailed(`When 'archive' is set to false, only a single file can be uploaded. Found ${searchResult.filesToUpload.length} files to upload.`);
return;
}
if (inputs.overwrite) {
await deleteArtifactIfExists(inputs.artifactName);
}
@@ -130594,9 +130586,6 @@ async function run() {
if (typeof inputs.compressionLevel !== 'undefined') {
options.compressionLevel = inputs.compressionLevel;
}
if (!inputs.archive) {
options.skipArchive = true;
}
await upload_artifact_uploadArtifact(inputs.artifactName, searchResult.filesToUpload, searchResult.rootDirectory, options);
}
}

2
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "7.0.0",
"license": "MIT",
"dependencies": {
"@actions/artifact": "^6.2.0",
"@actions/artifact": "^6.1.0",
"@actions/core": "^3.0.0",
"@actions/github": "^9.0.0",
"@actions/glob": "^0.6.1",

View File

@@ -33,7 +33,7 @@
"node": ">=24"
},
"dependencies": {
"@actions/artifact": "^6.2.0",
"@actions/artifact": "^6.1.0",
"@actions/core": "^3.0.0",
"@actions/github": "^9.0.0",
"@actions/glob": "^0.6.1",

View File

@@ -6,8 +6,7 @@ export enum Inputs {
RetentionDays = 'retention-days',
CompressionLevel = 'compression-level',
Overwrite = 'overwrite',
IncludeHiddenFiles = 'include-hidden-files',
Archive = 'archive'
IncludeHiddenFiles = 'include-hidden-files'
}
export enum NoFileOptions {

View File

@@ -10,7 +10,6 @@ export function getInputs(): UploadInputs {
const path = core.getInput(Inputs.Path, {required: true})
const overwrite = core.getBooleanInput(Inputs.Overwrite)
const includeHiddenFiles = core.getBooleanInput(Inputs.IncludeHiddenFiles)
const archive = core.getBooleanInput(Inputs.Archive)
const ifNoFilesFound = core.getInput(Inputs.IfNoFilesFound)
const noFileBehavior: NoFileOptions = NoFileOptions[ifNoFilesFound]
@@ -30,8 +29,7 @@ export function getInputs(): UploadInputs {
searchPath: path,
ifNoFilesFound: noFileBehavior,
overwrite: overwrite,
includeHiddenFiles: includeHiddenFiles,
archive: archive
includeHiddenFiles: includeHiddenFiles
} as UploadInputs
const retentionDaysStr = core.getInput(Inputs.RetentionDays)

View File

@@ -57,14 +57,6 @@ export async function run(): Promise<void> {
)
core.debug(`Root artifact directory is ${searchResult.rootDirectory}`)
// Validate that only a single file is uploaded when archive is false
if (!inputs.archive && searchResult.filesToUpload.length > 1) {
core.setFailed(
`When 'archive' is set to false, only a single file can be uploaded. Found ${searchResult.filesToUpload.length} files to upload.`
)
return
}
if (inputs.overwrite) {
await deleteArtifactIfExists(inputs.artifactName)
}
@@ -78,10 +70,6 @@ export async function run(): Promise<void> {
options.compressionLevel = inputs.compressionLevel
}
if (!inputs.archive) {
options.skipArchive = true
}
await uploadArtifact(
inputs.artifactName,
searchResult.filesToUpload,

View File

@@ -35,10 +35,4 @@ export interface UploadInputs {
* Whether or not to include hidden files in the artifact
*/
includeHiddenFiles: boolean
/**
* Whether or not to archive (zip) the artifact before uploading.
* When false, only a single file can be uploaded.
*/
archive: boolean
}