Artifact upload: support uploading single un-zipped files#2256
Artifact upload: support uploading single un-zipped files#2256danwkennedy merged 15 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request adds support for uploading single files without archiving them into a zip file. The feature introduces a new skipArchive option to UploadArtifactOptions that, when enabled, uploads a single file directly with its original MIME type instead of creating a zip archive.
Changes:
- Added
skipArchiveoption to allow uploading single uncompressed files - Introduced MIME type detection and transmission to the artifact service
- Refactored stream handling code into a shared module for reusability
- Updated artifact API version from 4 to 7 to support the new MIME type field
Reviewed changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/artifact/src/internal/upload/types.ts | New file providing MIME type mapping for various file extensions |
| packages/artifact/src/internal/upload/stream.ts | New file containing extracted and new stream utilities, including WaterMarkedUploadStream class and createRawFileUploadStream function |
| packages/artifact/src/internal/upload/zip.ts | Refactored to use WaterMarkedUploadStream from the new stream module |
| packages/artifact/src/internal/upload/blob-upload.ts | Renamed uploadZipToBlobStorage to uploadToBlobStorage and added contentType parameter |
| packages/artifact/src/internal/upload/upload-artifact.ts | Main logic for skipArchive feature including validation, file handling, and artifact name adjustment |
| packages/artifact/src/internal/shared/interfaces.ts | Added skipArchive option to UploadArtifactOptions interface with documentation |
| packages/artifact/src/generated/results/api/v1/artifact.ts | Generated protobuf code with mimeType field added to CreateArtifactRequest, removed migration-related types, and updated RepeatType |
| packages/artifact/src/generated/results/api/v1/artifact.twirp-client.ts | Minor formatting change (trailing newline) |
| packages/artifact/tests/upload-artifact.test.ts | Comprehensive test coverage for skipArchive feature including multiple file validation, raw file upload, MIME type handling, and artifact naming |
Comments suppressed due to low confidence (1)
packages/artifact/src/internal/upload/upload-artifact.ts:35
- The validation for skipArchive doesn't check if the files array is empty. If files.length is 0, the error won't be thrown, but then files[0] will be undefined on line 41, leading to unexpected behavior. Add a check for files.length === 0 alongside the files.length > 1 check.
if (files.length > 1){
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks for this! The ability to upload single un-zipped files with the appropriate MIME type will unlock real workflows where ZIP packaging is actively harmful. I previously opened #1874, which aimed at improving download behavior when artifacts are not zipped. That PR was ultimately closed, but it surfaced a real pain point: today the toolkit and While here it focuses on the upload side, it aligns with that same broader goal: support first-class non-zip artifacts. Together, these capabilities would cover both ends of the artifact lifecycle for non-zipped files and improve compatibility with existing external producers/consumers of artifacts. For example, workflows using gzip blobs (e.g., |
| */ | ||
| export function getMimeType(filePath: string): string { | ||
| const ext = path.extname(filePath).toLowerCase() | ||
| return mimeTypes[ext] || 'application/octet-stream' |
There was a problem hiding this comment.
Would it be possible to have an extra opt to enforce the desired mime type? In our case we are uploading a .dockerbuild file that is application/gzip type.
There was a problem hiding this comment.
Something similar to crazy-max@00e8704 so we can call uploadArtifact directly in crazy-max/docker-actions-toolkit@8027d57#diff-a248ff5ec76368168c745273fee0601b243545fdca37aa3bec76ef9e9365fdb7R172 as with ESM only we can't use internal helpers anymore: docker/actions-toolkit#968 (comment)
There was a problem hiding this comment.
Or maybe it is fine to have application/octet-stream if this is just use as content type in azure blob storage?
There was a problem hiding this comment.
Heh, I was literally going to ask that? I think it's actually ok to have an option for specifying though. I hadn't thought of files with no extension. I'm trying to come up with another example though, .dockerbuild would probably work fine since I'd expect us to default to a basic binary stream 🙂 . The types mostly help with specifying how the browser should handle the download in the UI: a PDF or PNG will be opened in a tab in the browser instead of downloading, etc.
There was a problem hiding this comment.
I think it's actually ok to have an option for specifying though.
Would be great!
I hadn't thought of files with no extension.
Actually .dockerbuild is the file extension 😅
For example one of the artifact in this run: https://github.com/docker/build-push-action/actions/runs/21586122185
The types mostly help with specifying how the browser should handle the download in the UI: a PDF or PNG will be opened in a tab in the browser instead of downloading, etc.
Ah ok that makes sense! So maybe a contentType opt is not necessary then.
|
@danwkennedy Is there any sense of timing for when this might move forward? This PR would unblock docker/actions-toolkit#972 (comment). Thanks 🙏 |
|
@crazy-max sorry for the wait. We're going to pick up the rollout next week at the earliest. |
Awesome thx! |
15fbe0f to
d584ea9
Compare
philip-gai
left a comment
There was a problem hiding this comment.
Some comments, lmk if you have any questions! 🚀
|
@danwkennedy Related to #2256 (review) I made some tests on this repo https://github.com/crazy-max/upload-artifact-skiparchive/actions/runs/22435286802 using latest changes from main of https://github.com/actions/upload-artifact. jobs:
json:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: github-repo-1c96c578b3.json
path: github-repo-1c96c578b3.json
archive: false
dockerbuild:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v6
-
name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
with:
name: docker~actions-toolkit~LF571I.dockerbuild
path: docker~actions-toolkit~LF571I.dockerbuild
archive: falseIt seems to work fine for the |
|
@crazy-max Could you rerun the whole pipeline? I've enabled the feature on that repo explicitly (I know the |
@danwkennedy Thanks! Looks good after re-run: https://github.com/crazy-max/upload-artifact-skiparchive/actions/runs/22435286802 |
|
@crazy-max cool, I haven't fully rolled out the final flag (it's a percentage right now so I don't know which repos have it and which don't). The request/response look correct when I tested it in the browser:
|

Description
Sometimes we want to upload un-zipped files:
tarfiles to handle file permissions. Zipping those files is unhelpful since it requires double decompressing.pdf,png/jpg,json,html, etc.). To support something like that in the UI, we can't have them zippedThis PR adds support for those scenarios.
The new CI run for this PR demonstrates the browser behavior for this. Note that this is enabled for this repo only, please wait until the feature is fully rolled out before attempting elsewhere.
Screen.Recording.2026-02-24.at.14.27.32.mov
Changes:
Notes
skipArchiveistrue, thenameparameter is replaced with the file name.skipArchiveisfalse. Notably, this'll mean the file type is not present in the artifact name. The mime type passed will still beapplication/zip, though.