Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcfe547070 | ||
|
|
707331a88d |
@@ -51,6 +51,7 @@ This action will create a GitHub release and optionally upload an artifact to it
|
|||||||
| upload_url | The URL for [uploading assets](https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset) to the release. |
|
| upload_url | The URL for [uploading assets](https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28#upload-a-release-asset) to the release. |
|
||||||
| tarball_url | The URL for downloading the release as a tarball (.tar.gz). |
|
| tarball_url | The URL for downloading the release as a tarball (.tar.gz). |
|
||||||
| zipball_url | The URL for downloading the release as a zipball (.zip). |
|
| zipball_url | The URL for downloading the release as a zipball (.zip). |
|
||||||
|
| assets | JSON string containing a map of asset names to download URLs for uploaded assets. |
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
This example will create a release when a tag is pushed:
|
This example will create a release when a tag is pushed:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const TEST_URLS = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
const applyReleaseDataMock = jest.fn()
|
const applyReleaseDataMock = jest.fn()
|
||||||
|
const applyAssetUrlsMock = jest.fn()
|
||||||
const artifactDestroyMock = jest.fn()
|
const artifactDestroyMock = jest.fn()
|
||||||
const createMock = jest.fn()
|
const createMock = jest.fn()
|
||||||
const deleteMock = jest.fn()
|
const deleteMock = jest.fn()
|
||||||
@@ -51,6 +52,8 @@ const generatedReleaseBody = "test release notes"
|
|||||||
|
|
||||||
describe("Action", () => {
|
describe("Action", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
applyReleaseDataMock.mockClear()
|
||||||
|
applyAssetUrlsMock.mockClear()
|
||||||
createMock.mockClear()
|
createMock.mockClear()
|
||||||
getMock.mockClear()
|
getMock.mockClear()
|
||||||
listMock.mockClear()
|
listMock.mockClear()
|
||||||
@@ -77,6 +80,7 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).not.toHaveBeenCalled()
|
expect(uploadMock).not.toHaveBeenCalled()
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates release if no release exists to update", async () => {
|
it("creates release if no release exists to update", async () => {
|
||||||
@@ -99,6 +103,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates release if no draft releases", async () => {
|
it("creates release if no draft releases", async () => {
|
||||||
@@ -124,6 +132,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("creates release then uploads artifact", async () => {
|
it("creates release then uploads artifact", async () => {
|
||||||
@@ -144,6 +156,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("removes all artifacts when artifact destroyer is enabled", async () => {
|
it("removes all artifacts when artifact destroyer is enabled", async () => {
|
||||||
@@ -153,6 +169,10 @@ describe("Action", () => {
|
|||||||
|
|
||||||
expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
|
expect(artifactDestroyMock).toHaveBeenCalledWith(releaseId)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("removes no artifacts when artifact destroyer is disabled", async () => {
|
it("removes no artifacts when artifact destroyer is disabled", async () => {
|
||||||
@@ -162,6 +182,10 @@ describe("Action", () => {
|
|||||||
|
|
||||||
expect(artifactDestroyMock).not.toHaveBeenCalled()
|
expect(artifactDestroyMock).not.toHaveBeenCalled()
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("skips action", async () => {
|
it("skips action", async () => {
|
||||||
@@ -326,6 +350,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates draft release with static body", async () => {
|
it("updates draft release with static body", async () => {
|
||||||
@@ -354,6 +382,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates release but does not upload if no artifact", async () => {
|
it("updates release but does not upload if no artifact", async () => {
|
||||||
@@ -374,6 +406,7 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).not.toHaveBeenCalled()
|
expect(uploadMock).not.toHaveBeenCalled()
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates release then uploads artifact", async () => {
|
it("updates release then uploads artifact", async () => {
|
||||||
@@ -394,6 +427,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it("updates release with static body when generateReleaseNotes is true but omitBodyDuringUpdate is true", async () => {
|
it("updates release with static body when generateReleaseNotes is true but omitBodyDuringUpdate is true", async () => {
|
||||||
@@ -422,6 +459,10 @@ describe("Action", () => {
|
|||||||
)
|
)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
expect(uploadMock).toHaveBeenCalledWith(artifacts, releaseId, url)
|
||||||
assertOutputApplied()
|
assertOutputApplied()
|
||||||
|
assertAssetUrlsApplied({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function assertOutputApplied() {
|
function assertOutputApplied() {
|
||||||
@@ -434,6 +475,10 @@ describe("Action", () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assertAssetUrlsApplied(expectedUrls: Record<string, string>) {
|
||||||
|
expect(applyAssetUrlsMock).toHaveBeenCalledWith(expectedUrls)
|
||||||
|
}
|
||||||
|
|
||||||
function createAction(
|
function createAction(
|
||||||
allowUpdates: boolean,
|
allowUpdates: boolean,
|
||||||
hasArtifact: boolean,
|
hasArtifact: boolean,
|
||||||
@@ -495,7 +540,10 @@ describe("Action", () => {
|
|||||||
zipball_url: TEST_URLS.ZIPBALL_URL,
|
zipball_url: TEST_URLS.ZIPBALL_URL,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
uploadMock.mockResolvedValue({})
|
uploadMock.mockResolvedValue({
|
||||||
|
"art1": "https://github.com/owner/repo/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/owner/repo/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
|
|
||||||
const MockInputs = jest.fn<Inputs, any>(() => {
|
const MockInputs = jest.fn<Inputs, any>(() => {
|
||||||
return {
|
return {
|
||||||
@@ -528,6 +576,7 @@ describe("Action", () => {
|
|||||||
const MockOutputs = jest.fn<Outputs, any>(() => {
|
const MockOutputs = jest.fn<Outputs, any>(() => {
|
||||||
return {
|
return {
|
||||||
applyReleaseData: applyReleaseDataMock,
|
applyReleaseData: applyReleaseDataMock,
|
||||||
|
applyAssetUrls: applyAssetUrlsMock,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
|
const MockUploader = jest.fn<ArtifactUploader, any>(() => {
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ const deleteMock = jest.fn()
|
|||||||
const listArtifactsMock = jest.fn()
|
const listArtifactsMock = jest.fn()
|
||||||
const uploadMock = jest.fn()
|
const uploadMock = jest.fn()
|
||||||
|
|
||||||
|
// Mock response with browser_download_url
|
||||||
|
const mockUploadResponse = (name: string) => ({
|
||||||
|
data: {
|
||||||
|
browser_download_url: `https://github.com/octocat/Hello-World/releases/download/v1.0.0/${name}`,
|
||||||
|
name: name,
|
||||||
|
id: 1,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
jest.mock("fs", () => {
|
jest.mock("fs", () => {
|
||||||
const originalFs = jest.requireActual("fs")
|
const originalFs = jest.requireActual("fs")
|
||||||
return {
|
return {
|
||||||
@@ -32,13 +41,48 @@ describe("ArtifactUploader", () => {
|
|||||||
uploadMock.mockClear()
|
uploadMock.mockClear()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("returns asset URLs when upload succeeds", async () => {
|
||||||
|
mockListWithoutAssets()
|
||||||
|
mockUploadSuccess()
|
||||||
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
|
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns empty object when no artifacts are uploaded", async () => {
|
||||||
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
|
const result = await uploader.uploadArtifacts([], releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({})
|
||||||
|
expect(uploadMock).toHaveBeenCalledTimes(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("excludes failed uploads from returned URLs", async () => {
|
||||||
|
mockListWithoutAssets()
|
||||||
|
mockUploadArtifact(401, 2)
|
||||||
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({})
|
||||||
|
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
it("abort when upload failed with non-5xx response", async () => {
|
it("abort when upload failed with non-5xx response", async () => {
|
||||||
mockListWithoutAssets()
|
mockListWithoutAssets()
|
||||||
mockUploadArtifact(401, 2)
|
mockUploadArtifact(401, 2)
|
||||||
const uploader = createUploader(true)
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({})
|
||||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||||
@@ -51,8 +95,9 @@ describe("ArtifactUploader", () => {
|
|||||||
mockUploadArtifact(500, 4)
|
mockUploadArtifact(500, 4)
|
||||||
const uploader = createUploader(true)
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({})
|
||||||
expect(uploadMock).toHaveBeenCalledTimes(5)
|
expect(uploadMock).toHaveBeenCalledTimes(5)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
@@ -66,11 +111,15 @@ describe("ArtifactUploader", () => {
|
|||||||
it("replaces all artifacts", async () => {
|
it("replaces all artifacts", async () => {
|
||||||
mockDeleteSuccess()
|
mockDeleteSuccess()
|
||||||
mockListWithAssets()
|
mockListWithAssets()
|
||||||
mockUploadArtifact()
|
mockUploadSuccess()
|
||||||
const uploader = createUploader(true)
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||||
@@ -83,11 +132,15 @@ describe("ArtifactUploader", () => {
|
|||||||
it("replaces no artifacts when previous asset list empty", async () => {
|
it("replaces no artifacts when previous asset list empty", async () => {
|
||||||
mockDeleteSuccess()
|
mockDeleteSuccess()
|
||||||
mockListWithoutAssets()
|
mockListWithoutAssets()
|
||||||
mockUploadArtifact()
|
mockUploadSuccess()
|
||||||
const uploader = createUploader(true)
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||||
@@ -100,8 +153,9 @@ describe("ArtifactUploader", () => {
|
|||||||
mockUploadArtifact(500, 2)
|
mockUploadArtifact(500, 2)
|
||||||
const uploader = createUploader(true)
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({})
|
||||||
expect(uploadMock).toHaveBeenCalledTimes(4)
|
expect(uploadMock).toHaveBeenCalledTimes(4)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
@@ -127,7 +181,7 @@ describe("ArtifactUploader", () => {
|
|||||||
it("throws error from replace", async () => {
|
it("throws error from replace", async () => {
|
||||||
mockDeleteError()
|
mockDeleteError()
|
||||||
mockListWithAssets()
|
mockListWithAssets()
|
||||||
mockUploadArtifact()
|
mockUploadSuccess()
|
||||||
const uploader = createUploader(true)
|
const uploader = createUploader(true)
|
||||||
|
|
||||||
expect.hasAssertions()
|
expect.hasAssertions()
|
||||||
@@ -141,11 +195,15 @@ describe("ArtifactUploader", () => {
|
|||||||
it("updates all artifacts, delete none", async () => {
|
it("updates all artifacts, delete none", async () => {
|
||||||
mockDeleteError()
|
mockDeleteError()
|
||||||
mockListWithAssets()
|
mockListWithAssets()
|
||||||
mockUploadArtifact()
|
mockUploadSuccess()
|
||||||
const uploader = createUploader(false)
|
const uploader = createUploader(false)
|
||||||
|
|
||||||
await uploader.uploadArtifacts(artifacts, releaseId, url)
|
const result = await uploader.uploadArtifacts(artifacts, releaseId, url)
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
"art1": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art1",
|
||||||
|
"art2": "https://github.com/octocat/Hello-World/releases/download/v1.0.0/art2",
|
||||||
|
})
|
||||||
expect(uploadMock).toHaveBeenCalledTimes(2)
|
expect(uploadMock).toHaveBeenCalledTimes(2)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art1", releaseId)
|
||||||
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
expect(uploadMock).toHaveBeenCalledWith(url, contentLength, "raw", fakeReadStream, "art2", releaseId)
|
||||||
@@ -194,6 +252,10 @@ describe("ArtifactUploader", () => {
|
|||||||
listArtifactsMock.mockResolvedValue([])
|
listArtifactsMock.mockResolvedValue([])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mockUploadSuccess() {
|
||||||
|
uploadMock.mockImplementation((_, __, ___, ____, name) => Promise.resolve(mockUploadResponse(name)))
|
||||||
|
}
|
||||||
|
|
||||||
function mockUploadArtifact(status = 200, failures = 0) {
|
function mockUploadArtifact(status = 200, failures = 0) {
|
||||||
const error = new RequestError(`HTTP ${status}`, status, {
|
const error = new RequestError(`HTTP ${status}`, status, {
|
||||||
headers: {},
|
headers: {},
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ describe.skip("Integration Test", () => {
|
|||||||
applyReleaseData(releaseData: ReleaseData) {
|
applyReleaseData(releaseData: ReleaseData) {
|
||||||
console.log(`Release Data: ${releaseData}`)
|
console.log(`Release Data: ${releaseData}`)
|
||||||
},
|
},
|
||||||
|
applyAssetUrls(assetUrls: Record<string, string>) {
|
||||||
|
console.log(`Asset URLs: ${JSON.stringify(assetUrls)}`)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return new MockOutputs()
|
return new MockOutputs()
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
const mockSetOutput = jest.fn()
|
import { CoreOutputs, type Outputs } from "../src/Outputs"
|
||||||
|
import type { ReleaseData } from "../src/Releases"
|
||||||
|
|
||||||
import { CoreOutputs, Outputs } from "../src/Outputs"
|
jest.mock("@actions/core")
|
||||||
import { ReleaseData } from "../src/Releases"
|
const { setOutput: mockSetOutput } = jest.mocked(require("@actions/core"))
|
||||||
|
|
||||||
jest.mock("@actions/core", () => {
|
|
||||||
return { setOutput: mockSetOutput }
|
|
||||||
})
|
|
||||||
|
|
||||||
const TEST_URLS = {
|
const TEST_URLS = {
|
||||||
HTML_URL: "https://api.example.com/assets",
|
HTML_URL: "https://api.example.com/assets",
|
||||||
@@ -48,4 +45,19 @@ describe("Outputs", () => {
|
|||||||
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", "")
|
expect(mockSetOutput).toHaveBeenCalledWith("tarball_url", "")
|
||||||
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", "")
|
expect(mockSetOutput).toHaveBeenCalledWith("zipball_url", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("Applies asset URLs to the action output", () => {
|
||||||
|
const assetUrls = {
|
||||||
|
"example.zip": "https://github.com/owner/repo/releases/download/v1.0.0/example.zip",
|
||||||
|
"example.tar.gz": "https://github.com/owner/repo/releases/download/v1.0.0/example.tar.gz",
|
||||||
|
}
|
||||||
|
outputs.applyAssetUrls(assetUrls)
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith("assets", JSON.stringify(assetUrls))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Applies empty asset URLs to the action output", () => {
|
||||||
|
const assetUrls = {}
|
||||||
|
outputs.applyAssetUrls(assetUrls)
|
||||||
|
expect(mockSetOutput).toHaveBeenCalledWith("assets", JSON.stringify(assetUrls))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ outputs:
|
|||||||
description: 'The HTML URL of the release.'
|
description: 'The HTML URL of the release.'
|
||||||
upload_url:
|
upload_url:
|
||||||
description: 'The URL for uploading assets to the release.'
|
description: 'The URL for uploading assets to the release.'
|
||||||
|
tarball_url:
|
||||||
|
description: 'The URL for downloading the release as a tarball (.tar.gz).'
|
||||||
|
zipball_url:
|
||||||
|
description: 'The URL for downloading the release as a zipball (.zip).'
|
||||||
|
assets:
|
||||||
|
description: 'JSON string containing a map of asset names to download URLs for uploaded assets.'
|
||||||
runs:
|
runs:
|
||||||
using: 'node20'
|
using: 'node20'
|
||||||
main: 'dist/index.js'
|
main: 'dist/index.js'
|
||||||
|
|||||||
21
dist/index.js
vendored
21
dist/index.js
vendored
@@ -64,10 +64,12 @@ class Action {
|
|||||||
await this.artifactDestroyer.destroyArtifacts(releaseId);
|
await this.artifactDestroyer.destroyArtifacts(releaseId);
|
||||||
}
|
}
|
||||||
const artifacts = this.inputs.artifacts;
|
const artifacts = this.inputs.artifacts;
|
||||||
|
let assetUrls = {};
|
||||||
if (artifacts.length > 0) {
|
if (artifacts.length > 0) {
|
||||||
await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl);
|
assetUrls = await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl);
|
||||||
}
|
}
|
||||||
this.outputs.applyReleaseData(releaseData);
|
this.outputs.applyReleaseData(releaseData);
|
||||||
|
this.outputs.applyAssetUrls(assetUrls);
|
||||||
}
|
}
|
||||||
async createOrUpdateRelease() {
|
async createOrUpdateRelease() {
|
||||||
if (this.inputs.allowUpdates) {
|
if (this.inputs.allowUpdates) {
|
||||||
@@ -454,19 +456,25 @@ class GithubArtifactUploader {
|
|||||||
if (this.replacesExistingArtifacts) {
|
if (this.replacesExistingArtifacts) {
|
||||||
await this.deleteUpdatedArtifacts(artifacts, releaseId);
|
await this.deleteUpdatedArtifacts(artifacts, releaseId);
|
||||||
}
|
}
|
||||||
|
const assetUrls = {};
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
await this.uploadArtifact(artifact, releaseId, uploadUrl);
|
const assetUrl = await this.uploadArtifact(artifact, releaseId, uploadUrl);
|
||||||
|
if (assetUrl !== null) {
|
||||||
|
assetUrls[artifact.name] = assetUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return assetUrls;
|
||||||
}
|
}
|
||||||
async uploadArtifact(artifact, releaseId, uploadUrl, retry = 3) {
|
async uploadArtifact(artifact, releaseId, uploadUrl, retry = 3) {
|
||||||
try {
|
try {
|
||||||
core.debug(`Uploading artifact ${artifact.name}...`);
|
core.debug(`Uploading artifact ${artifact.name}...`);
|
||||||
await this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name, releaseId);
|
const assetResponse = await this.releases.uploadArtifact(uploadUrl, artifact.contentLength, artifact.contentType, artifact.readFile(), artifact.name, releaseId);
|
||||||
|
return assetResponse.data.browser_download_url;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (error.status >= 500 && retry > 0) {
|
if (error.status >= 500 && retry > 0) {
|
||||||
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`);
|
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`);
|
||||||
await this.uploadArtifact(artifact, releaseId, uploadUrl, retry - 1);
|
return this.uploadArtifact(artifact, releaseId, uploadUrl, retry - 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this.throwsUploadErrors) {
|
if (this.throwsUploadErrors) {
|
||||||
@@ -474,6 +482,7 @@ class GithubArtifactUploader {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`);
|
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -892,6 +901,10 @@ class CoreOutputs {
|
|||||||
core.setOutput("tarball_url", releaseData.tarball_url || "");
|
core.setOutput("tarball_url", releaseData.tarball_url || "");
|
||||||
core.setOutput("zipball_url", releaseData.zipball_url || "");
|
core.setOutput("zipball_url", releaseData.zipball_url || "");
|
||||||
}
|
}
|
||||||
|
applyAssetUrls(assetUrls) {
|
||||||
|
const assetUrlsJson = JSON.stringify(assetUrls);
|
||||||
|
core.setOutput("assets", assetUrlsJson);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.CoreOutputs = CoreOutputs;
|
exports.CoreOutputs = CoreOutputs;
|
||||||
|
|
||||||
|
|||||||
2
dist/index.js.map
vendored
2
dist/index.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -57,11 +57,13 @@ export class Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const artifacts = this.inputs.artifacts
|
const artifacts = this.inputs.artifacts
|
||||||
|
let assetUrls: Record<string, string> = {}
|
||||||
if (artifacts.length > 0) {
|
if (artifacts.length > 0) {
|
||||||
await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl)
|
assetUrls = await this.uploader.uploadArtifacts(artifacts, releaseId, uploadUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.outputs.applyReleaseData(releaseData)
|
this.outputs.applyReleaseData(releaseData)
|
||||||
|
this.outputs.applyAssetUrls(assetUrls)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createOrUpdateRelease(): Promise<CreateOrUpdateReleaseResponse> {
|
private async createOrUpdateRelease(): Promise<CreateOrUpdateReleaseResponse> {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Artifact } from "./Artifact"
|
|||||||
import { Releases } from "./Releases"
|
import { Releases } from "./Releases"
|
||||||
|
|
||||||
export interface ArtifactUploader {
|
export interface ArtifactUploader {
|
||||||
uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void>
|
uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<Record<string, string>>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GithubArtifactUploader implements ArtifactUploader {
|
export class GithubArtifactUploader implements ArtifactUploader {
|
||||||
@@ -13,19 +13,24 @@ export class GithubArtifactUploader implements ArtifactUploader {
|
|||||||
private throwsUploadErrors: boolean = false
|
private throwsUploadErrors: boolean = false
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<void> {
|
async uploadArtifacts(artifacts: Artifact[], releaseId: number, uploadUrl: string): Promise<Record<string, string>> {
|
||||||
if (this.replacesExistingArtifacts) {
|
if (this.replacesExistingArtifacts) {
|
||||||
await this.deleteUpdatedArtifacts(artifacts, releaseId)
|
await this.deleteUpdatedArtifacts(artifacts, releaseId)
|
||||||
}
|
}
|
||||||
|
const assetUrls: Record<string, string> = {}
|
||||||
for (const artifact of artifacts) {
|
for (const artifact of artifacts) {
|
||||||
await this.uploadArtifact(artifact, releaseId, uploadUrl)
|
const assetUrl = await this.uploadArtifact(artifact, releaseId, uploadUrl)
|
||||||
|
if (assetUrl !== null) {
|
||||||
|
assetUrls[artifact.name] = assetUrl
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return assetUrls
|
||||||
}
|
}
|
||||||
|
|
||||||
private async uploadArtifact(artifact: Artifact, releaseId: number, uploadUrl: string, retry = 3) {
|
private async uploadArtifact(artifact: Artifact, releaseId: number, uploadUrl: string, retry = 3): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
core.debug(`Uploading artifact ${artifact.name}...`)
|
core.debug(`Uploading artifact ${artifact.name}...`)
|
||||||
await this.releases.uploadArtifact(
|
const assetResponse = await this.releases.uploadArtifact(
|
||||||
uploadUrl,
|
uploadUrl,
|
||||||
artifact.contentLength,
|
artifact.contentLength,
|
||||||
artifact.contentType,
|
artifact.contentType,
|
||||||
@@ -33,15 +38,17 @@ export class GithubArtifactUploader implements ArtifactUploader {
|
|||||||
artifact.name,
|
artifact.name,
|
||||||
releaseId
|
releaseId
|
||||||
)
|
)
|
||||||
|
return assetResponse.data.browser_download_url
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.status >= 500 && retry > 0) {
|
if (error.status >= 500 && retry > 0) {
|
||||||
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`)
|
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}. Retrying...`)
|
||||||
await this.uploadArtifact(artifact, releaseId, uploadUrl, retry - 1)
|
return this.uploadArtifact(artifact, releaseId, uploadUrl, retry - 1)
|
||||||
} else {
|
} else {
|
||||||
if (this.throwsUploadErrors) {
|
if (this.throwsUploadErrors) {
|
||||||
throw Error(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
|
throw Error(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
|
||||||
} else {
|
} else {
|
||||||
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
|
core.warning(`Failed to upload artifact ${artifact.name}. ${error.message}.`)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ReleaseData } from "./Releases"
|
|||||||
|
|
||||||
export interface Outputs {
|
export interface Outputs {
|
||||||
applyReleaseData(releaseData: ReleaseData): void
|
applyReleaseData(releaseData: ReleaseData): void
|
||||||
|
applyAssetUrls(assetUrls: Record<string, string>): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CoreOutputs implements Outputs {
|
export class CoreOutputs implements Outputs {
|
||||||
@@ -13,4 +14,9 @@ export class CoreOutputs implements Outputs {
|
|||||||
core.setOutput("tarball_url", releaseData.tarball_url || "")
|
core.setOutput("tarball_url", releaseData.tarball_url || "")
|
||||||
core.setOutput("zipball_url", releaseData.zipball_url || "")
|
core.setOutput("zipball_url", releaseData.zipball_url || "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyAssetUrls(assetUrls: Record<string, string>) {
|
||||||
|
const assetUrlsJson = JSON.stringify(assetUrls)
|
||||||
|
core.setOutput("assets", assetUrlsJson)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user