Skip to content

Commit 9d63e3f

Browse files
authored
Merge branch 'main' into robherley/migration-docs-typo
2 parents dfa1ab2 + d00351b commit 9d63e3f

16 files changed

+169987
-32669
lines changed

.github/workflows/test.yml

+86-5
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,16 @@ jobs:
141141
}
142142
shell: pwsh
143143

144+
- name: 'Alter file 1 content'
145+
run: |
146+
echo "This file has changed" > path/to/dir-1/file1.txt
147+
144148
# Replace the contents of Artifact #1
145-
- name: 'Overwrite artifact #1 again'
149+
- name: 'Overwrite artifact #1'
146150
uses: ./
147151
with:
148152
name: 'Artifact-A-${{ matrix.runs-on }}'
149-
path: path/to/dir-2/file2.txt
153+
path: path/to/dir-1/file1.txt
150154
overwrite: true
151155

152156
# Download replaced Artifact #1 and verify the correctness of the content
@@ -158,13 +162,90 @@ jobs:
158162

159163
- name: 'Verify Artifact #1 again'
160164
run: |
161-
$file = "overwrite/some/new/path/file2.txt"
165+
$file = "overwrite/some/new/path/file1.txt"
162166
if(!(Test-Path -path $file))
163167
{
164168
Write-Error "Expected file does not exist"
165169
}
166-
if(!((Get-Content $file) -ceq "Hello world from file #2"))
170+
if(!((Get-Content $file) -ceq "This file has changed"))
167171
{
168-
Write-Error "File contents of downloaded artifacts are incorrect"
172+
Write-Error "File contents of downloaded artifact are incorrect"
173+
}
174+
shell: pwsh
175+
merge:
176+
name: Merge
177+
needs: build
178+
runs-on: ubuntu-latest
179+
180+
steps:
181+
- name: Checkout
182+
uses: actions/checkout@v4
183+
184+
# Merge all artifacts from previous jobs
185+
- name: Merge all artifacts in run
186+
uses: ./merge/
187+
with:
188+
# our matrix produces artifacts with the same file, this prevents "stomping" on each other, also makes it
189+
# easier to identify each of the merged artifacts
190+
separate-directories: true
191+
- name: 'Download merged artifacts'
192+
uses: actions/download-artifact@v4
193+
with:
194+
name: merged-artifacts
195+
path: all-merged-artifacts
196+
- name: 'Check merged artifact has directories for each artifact'
197+
run: |
198+
$artifacts = @(
199+
"Artifact-A-ubuntu-latest",
200+
"Artifact-A-macos-latest",
201+
"Artifact-A-windows-latest",
202+
"Artifact-Wildcard-ubuntu-latest",
203+
"Artifact-Wildcard-macos-latest",
204+
"Artifact-Wildcard-windows-latest",
205+
"Multi-Path-Artifact-ubuntu-latest",
206+
"Multi-Path-Artifact-macos-latest",
207+
"Multi-Path-Artifact-windows-latest"
208+
)
209+
210+
foreach ($artifact in $artifacts) {
211+
$path = "all-merged-artifacts/$artifact"
212+
if (!(Test-Path $path)) {
213+
Write-Error "$path does not exist."
214+
}
169215
}
170216
shell: pwsh
217+
218+
# Merge Artifact-A-* from previous jobs
219+
- name: Merge all Artifact-A
220+
uses: ./merge/
221+
with:
222+
name: Merged-Artifact-As
223+
pattern: 'Artifact-A-*'
224+
separate-directories: true
225+
226+
# Download merged artifacts and verify the correctness of the content
227+
- name: 'Download merged artifacts'
228+
uses: actions/download-artifact@v4
229+
with:
230+
name: Merged-Artifact-As
231+
path: merged-artifact-a
232+
233+
- name: 'Verify merged artifacts'
234+
run: |
235+
$files = @(
236+
"merged-artifact-a/Artifact-A-ubuntu-latest/file1.txt",
237+
"merged-artifact-a/Artifact-A-macos-latest/file1.txt",
238+
"merged-artifact-a/Artifact-A-windows-latest/file1.txt"
239+
)
240+
241+
foreach ($file in $files) {
242+
if (!(Test-Path $file)) {
243+
Write-Error "$file does not exist."
244+
}
245+
246+
if (!((Get-Content $file) -ceq "This file has changed")) {
247+
Write-Error "$file has incorrect content."
248+
}
249+
}
250+
shell: pwsh
251+

.licenses/npm/minimatch.dep.yml

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ The release of upload-artifact@v4 and download-artifact@v4 are major changes to
4141

4242
For more information, see the [`@actions/artifact`](https://github.com/actions/toolkit/tree/main/packages/artifact) documentation.
4343

44+
There is also a new sub-action, `actions/upload-artifact/merge`. For more info, check out that action's [README](./merge/README.md).
45+
4446
### Improvements
4547

4648
1. Uploads are significantly faster, upwards of 90% improvement in worst case scenarios.
@@ -406,7 +408,7 @@ jobs:
406408

407409
### Number of Artifacts
408410

409-
Within an individual job, there is a limit of 10 artifacts that can be created for that job.
411+
Within an individual job, there is a limit of 500 artifacts that can be created for that job.
410412

411413
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.
412414

__tests__/merge.test.ts

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import * as core from '@actions/core'
2+
import artifact from '@actions/artifact'
3+
import {run} from '../src/merge/merge-artifacts'
4+
import {Inputs} from '../src/merge/constants'
5+
import * as search from '../src/shared/search'
6+
7+
const fixtures = {
8+
artifactName: 'my-merged-artifact',
9+
tmpDirectory: '/tmp/merge-artifact',
10+
filesToUpload: [
11+
'/some/artifact/path/file-a.txt',
12+
'/some/artifact/path/file-b.txt',
13+
'/some/artifact/path/file-c.txt'
14+
],
15+
artifacts: [
16+
{
17+
name: 'my-artifact-a',
18+
id: 1,
19+
size: 100,
20+
createdAt: new Date('2024-01-01T00:00:00Z')
21+
},
22+
{
23+
name: 'my-artifact-b',
24+
id: 2,
25+
size: 100,
26+
createdAt: new Date('2024-01-01T00:00:00Z')
27+
},
28+
{
29+
name: 'my-artifact-c',
30+
id: 3,
31+
size: 100,
32+
createdAt: new Date('2024-01-01T00:00:00Z')
33+
}
34+
]
35+
}
36+
37+
jest.mock('@actions/github', () => ({
38+
context: {
39+
repo: {
40+
owner: 'actions',
41+
repo: 'toolkit'
42+
},
43+
runId: 123,
44+
serverUrl: 'https://github.com'
45+
}
46+
}))
47+
48+
jest.mock('@actions/core')
49+
50+
jest.mock('fs/promises', () => ({
51+
mkdtemp: jest.fn().mockResolvedValue('/tmp/merge-artifact'),
52+
rm: jest.fn().mockResolvedValue(undefined)
53+
}))
54+
55+
/* eslint-disable no-unused-vars */
56+
const mockInputs = (overrides?: Partial<{[K in Inputs]?: any}>) => {
57+
const inputs = {
58+
[Inputs.Name]: 'my-merged-artifact',
59+
[Inputs.Pattern]: '*',
60+
[Inputs.SeparateDirectories]: false,
61+
[Inputs.RetentionDays]: 0,
62+
[Inputs.CompressionLevel]: 6,
63+
[Inputs.DeleteMerged]: false,
64+
...overrides
65+
}
66+
67+
;(core.getInput as jest.Mock).mockImplementation((name: string) => {
68+
return inputs[name]
69+
})
70+
;(core.getBooleanInput as jest.Mock).mockImplementation((name: string) => {
71+
return inputs[name]
72+
})
73+
74+
return inputs
75+
}
76+
77+
describe('merge', () => {
78+
beforeEach(async () => {
79+
mockInputs()
80+
81+
jest
82+
.spyOn(artifact, 'listArtifacts')
83+
.mockResolvedValue({artifacts: fixtures.artifacts})
84+
85+
jest.spyOn(artifact, 'downloadArtifact').mockResolvedValue({
86+
downloadPath: fixtures.tmpDirectory
87+
})
88+
89+
jest.spyOn(search, 'findFilesToUpload').mockResolvedValue({
90+
filesToUpload: fixtures.filesToUpload,
91+
rootDirectory: fixtures.tmpDirectory
92+
})
93+
94+
jest.spyOn(artifact, 'uploadArtifact').mockResolvedValue({
95+
size: 123,
96+
id: 1337
97+
})
98+
99+
jest
100+
.spyOn(artifact, 'deleteArtifact')
101+
.mockImplementation(async artifactName => {
102+
const artifact = fixtures.artifacts.find(a => a.name === artifactName)
103+
if (!artifact) throw new Error(`Artifact ${artifactName} not found`)
104+
return {id: artifact.id}
105+
})
106+
})
107+
108+
it('merges artifacts', async () => {
109+
await run()
110+
111+
for (const a of fixtures.artifacts) {
112+
expect(artifact.downloadArtifact).toHaveBeenCalledWith(a.id, {
113+
path: fixtures.tmpDirectory
114+
})
115+
}
116+
117+
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
118+
fixtures.artifactName,
119+
fixtures.filesToUpload,
120+
fixtures.tmpDirectory,
121+
{compressionLevel: 6}
122+
)
123+
})
124+
125+
it('fails if no artifacts found', async () => {
126+
mockInputs({[Inputs.Pattern]: 'this-does-not-match'})
127+
128+
expect(run()).rejects.toThrow()
129+
130+
expect(artifact.uploadArtifact).not.toBeCalled()
131+
expect(artifact.downloadArtifact).not.toBeCalled()
132+
})
133+
134+
it('supports custom compression level', async () => {
135+
mockInputs({
136+
[Inputs.CompressionLevel]: 2
137+
})
138+
139+
await run()
140+
141+
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
142+
fixtures.artifactName,
143+
fixtures.filesToUpload,
144+
fixtures.tmpDirectory,
145+
{compressionLevel: 2}
146+
)
147+
})
148+
149+
it('supports custom retention days', async () => {
150+
mockInputs({
151+
[Inputs.RetentionDays]: 7
152+
})
153+
154+
await run()
155+
156+
expect(artifact.uploadArtifact).toHaveBeenCalledWith(
157+
fixtures.artifactName,
158+
fixtures.filesToUpload,
159+
fixtures.tmpDirectory,
160+
{retentionDays: 7, compressionLevel: 6}
161+
)
162+
})
163+
164+
it('supports deleting artifacts after merge', async () => {
165+
mockInputs({
166+
[Inputs.DeleteMerged]: true
167+
})
168+
169+
await run()
170+
171+
for (const a of fixtures.artifacts) {
172+
expect(artifact.deleteArtifact).toHaveBeenCalledWith(a.name)
173+
}
174+
})
175+
})

0 commit comments

Comments
 (0)