Skip to content

Commit 634f93c

Browse files
authored
Merge pull request #416 from actions/single-artifact-id-download-path
fix: inconsistent path behavior for single artifact downloads by ID
2 parents 448e3f8 + b19ff43 commit 634f93c

File tree

4 files changed

+61
-4
lines changed

4 files changed

+61
-4
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ See also [upload-artifact](https://github.com/actions/upload-artifact).
88
- [v4 - What's new](#v4---whats-new)
99
- [Improvements](#improvements)
1010
- [Breaking Changes](#breaking-changes)
11+
- [Note](#note)
1112
- [Usage](#usage)
1213
- [Inputs](#inputs)
1314
- [Outputs](#outputs)
@@ -89,6 +90,7 @@ You are welcome to still raise bugs in this repo.
8990
# When multiple artifacts are matched, this changes the behavior of the destination directories.
9091
# If true, the downloaded artifacts will be in the same directory specified by path.
9192
# If false, the downloaded artifacts will be extracted into individual named directories within the specified path.
93+
# Note: When downloading a single artifact (by name or ID), it will always be extracted directly to the specified path.
9294
# Optional. Default is 'false'
9395
merge-multiple:
9496

@@ -145,6 +147,8 @@ steps:
145147

146148
The `artifact-ids` input allows downloading artifacts using their unique ID rather than name. This is particularly useful when working with immutable artifacts from `actions/upload-artifact@v4` which assigns a unique ID to each artifact.
147149

150+
Download a single artifact by ID to the current working directory (`$GITHUB_WORKSPACE`):
151+
148152
```yaml
149153
steps:
150154
- uses: actions/download-artifact@v4
@@ -154,6 +158,20 @@ steps:
154158
run: ls -R
155159
```
156160

161+
Download a single artifact by ID to a specific directory:
162+
163+
```yaml
164+
steps:
165+
- uses: actions/download-artifact@v4
166+
with:
167+
artifact-ids: 12345
168+
path: your/destination/dir
169+
- name: Display structure of downloaded files
170+
run: ls -R your/destination/dir
171+
```
172+
173+
When downloading a single artifact by ID, the behavior is identical to downloading by name - the artifact contents are extracted directly to the specified path without creating a subdirectory.
174+
157175
Multiple artifacts can be downloaded by providing a comma-separated list of IDs:
158176

159177
```yaml
@@ -166,7 +184,7 @@ steps:
166184
run: ls -R path/to/artifacts
167185
```
168186

169-
This will download multiple artifacts to separate directories (similar to downloading multiple artifacts by name).
187+
When downloading multiple artifacts by ID, each artifact will be extracted into its own subdirectory named after the artifact (similar to downloading multiple artifacts by name).
170188

171189
### Download All Artifacts
172190

__tests__/download.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as core from '@actions/core'
2+
import * as path from 'path'
23
import artifact, {ArtifactNotFoundError} from '@actions/artifact'
34
import {run} from '../src/download-artifact'
45
import {Inputs} from '../src/constants'
@@ -371,4 +372,38 @@ describe('download', () => {
371372
"Inputs 'name' and 'artifact-ids' cannot be used together. Please specify only one."
372373
)
373374
})
375+
376+
test('downloads single artifact by ID to same path as by name', async () => {
377+
const mockArtifact = {
378+
id: 456,
379+
name: 'test-artifact',
380+
size: 1024,
381+
digest: 'def456'
382+
}
383+
384+
const testPath = '/test/path'
385+
mockInputs({
386+
[Inputs.Name]: '',
387+
[Inputs.Pattern]: '',
388+
[Inputs.ArtifactIds]: '456',
389+
[Inputs.Path]: testPath
390+
})
391+
392+
jest.spyOn(artifact, 'listArtifacts').mockImplementation(() =>
393+
Promise.resolve({
394+
artifacts: [mockArtifact]
395+
})
396+
)
397+
398+
await run()
399+
400+
// Verify it downloads directly to the specified path (not nested in artifact name subdirectory)
401+
expect(artifact.downloadArtifact).toHaveBeenCalledWith(
402+
456,
403+
expect.objectContaining({
404+
path: path.resolve(testPath), // Should be the resolved path directly, not nested
405+
expectedHash: mockArtifact.digest
406+
})
407+
)
408+
})
374409
})

dist/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118883,7 +118883,9 @@ function run() {
118883118883
}
118884118884
const downloadPromises = artifacts.map(artifact => ({
118885118885
name: artifact.name,
118886-
promise: artifact_1.default.downloadArtifact(artifact.id, Object.assign(Object.assign({}, options), { path: isSingleArtifactDownload || inputs.mergeMultiple
118886+
promise: artifact_1.default.downloadArtifact(artifact.id, Object.assign(Object.assign({}, options), { path: isSingleArtifactDownload ||
118887+
inputs.mergeMultiple ||
118888+
artifacts.length === 1
118887118889
? resolvedPath
118888118890
: path.join(resolvedPath, artifact.name), expectedHash: artifact.digest }))
118889118891
}));
@@ -128958,4 +128960,4 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"]
128958128960
/******/ module.exports = __webpack_exports__;
128959128961
/******/
128960128962
/******/ })()
128961-
;
128963+
;

src/download-artifact.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,9 @@ export async function run(): Promise<void> {
174174
promise: artifactClient.downloadArtifact(artifact.id, {
175175
...options,
176176
path:
177-
isSingleArtifactDownload || inputs.mergeMultiple
177+
isSingleArtifactDownload ||
178+
inputs.mergeMultiple ||
179+
artifacts.length === 1
178180
? resolvedPath
179181
: path.join(resolvedPath, artifact.name),
180182
expectedHash: artifact.digest

0 commit comments

Comments
 (0)