diff --git a/README.md b/README.md index 9f25219d..8accc13b 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ This action will create a GitHub release and optionally upload an artifact to it This will preserve the existing prerelease state during updates. - **owner**: Optionally specify the owner of the repo where the release should be generated. Defaults to current repo's owner. - **prerelease**: Optionally marks this release as prerelease. Set to true to enable. +- **removeArtifacts**: Indicates if existing release artifacts should be removed. Defaults to `false`. - **replacesArtifacts**: Indicates if existing release artifacts should be replaced. Defaults to `true`. - **repo**: Optionally specify the repo where the release should be generated. Defaults to current repo. - **tag**: An optional tag for the release. If this is omitted the git ref will be used (if it is a tag). diff --git a/__tests__/Action.test.ts b/__tests__/Action.test.ts index 0f21b31d..e778a36f 100644 --- a/__tests__/Action.test.ts +++ b/__tests__/Action.test.ts @@ -29,6 +29,7 @@ const createPrerelease = true const updatePrerelease = false const releaseId = 101 const replacesArtifacts = true +const removeArtifacts = false const tag = 'tag' const token = 'token' const updateBody = 'updateBody' @@ -298,6 +299,7 @@ describe("Action", () => { owner: "owner", createdPrerelease: createPrerelease, replacesArtifacts: replacesArtifacts, + removeArtifacts: removeArtifacts, repo: "repo", tag: tag, token: token, diff --git a/__tests__/ArtifactUploader.test.ts b/__tests__/ArtifactUploader.test.ts index 68d172ff..25006d29 100644 --- a/__tests__/ArtifactUploader.test.ts +++ b/__tests__/ArtifactUploader.test.ts @@ -89,6 +89,25 @@ describe('ArtifactUploader', () => { expect(deleteMock).toBeCalledWith(2) }) + it('removes all artifacts', async () => { + mockDeleteSuccess() + mockListWithAssets() + mockUploadArtifact() + const uploader = createUploader(false, true) + + await uploader.uploadArtifacts(artifacts, releaseId, url) + + expect(uploadMock).toBeCalledTimes(2) + expect(uploadMock) + .toBeCalledWith(url, contentLength, 'raw', fileContents, 'art1', releaseId) + expect(uploadMock) + .toBeCalledWith(url, contentLength, 'raw', fileContents, 'art2', releaseId) + + expect(deleteMock).toBeCalledTimes(2) + expect(deleteMock).toBeCalledWith(1) + expect(deleteMock).toBeCalledWith(2) + }) + it('replaces no artifacts when previous asset list empty', async () => { mockDeleteSuccess() mockListWithoutAssets() @@ -129,7 +148,7 @@ describe('ArtifactUploader', () => { it('throws upload error when replacesExistingArtifacts is true', async () => { mockListWithoutAssets() mockUploadError() - const uploader = createUploader(true, true) + const uploader = createUploader(true, false, true) expect.hasAssertions() try { @@ -170,7 +189,7 @@ describe('ArtifactUploader', () => { expect(deleteMock).toBeCalledTimes(0) }) - function createUploader(replaces: boolean, throws: boolean = false): GithubArtifactUploader { + function createUploader(replaces: boolean, removes: boolean = false, throws: boolean = false): GithubArtifactUploader { const MockReleases = jest.fn(() => { return { create: jest.fn(), @@ -182,7 +201,7 @@ describe('ArtifactUploader', () => { uploadArtifact: uploadMock } }) - return new GithubArtifactUploader(new MockReleases(), replaces, throws) + return new GithubArtifactUploader(new MockReleases(), replaces, removes, throws) } function mockDeleteError(): any { diff --git a/__tests__/Inputs.test.ts b/__tests__/Inputs.test.ts index 6f8d8aea..ad361e13 100644 --- a/__tests__/Inputs.test.ts +++ b/__tests__/Inputs.test.ts @@ -236,6 +236,17 @@ describe('Inputs', () => { }) }) + describe('removeArtifacts', () => { + it('returns false', () => { + expect(inputs.removeArtifacts).toBe(false) + }) + + it('returns true', () => { + mockGetInput.mockReturnValue('true') + expect(inputs.removeArtifacts).toBe(true) + }) + }) + describe('repo', () => { it('returns repo from context', function () { process.env.GITHUB_REPOSITORY = "owner/repo" diff --git a/__tests__/Integration.test.ts b/__tests__/Integration.test.ts index d35fad6a..ecef9fd1 100644 --- a/__tests__/Integration.test.ts +++ b/__tests__/Integration.test.ts @@ -23,6 +23,7 @@ describe.skip('Integration Test', () => { const uploader = new GithubArtifactUploader( releases, inputs.replacesArtifacts, + inputs.removeArtifacts, inputs.artifactErrorsFailBuild, ) action = new Action(inputs, outputs, releases, uploader) @@ -46,6 +47,7 @@ describe.skip('Integration Test', () => { owner: "ncipollo", createdPrerelease: false, replacesArtifacts: true, + removeArtifacts: false, repo: "actions-playground", tag: "release-action-test", token: getToken(), diff --git a/action.yml b/action.yml index 7b5932ed..fcec331c 100644 --- a/action.yml +++ b/action.yml @@ -75,6 +75,10 @@ inputs: description: "Optionally marks this release as prerelease. Set to true to enable." required: false default: '' + removeArtifacts: + description: 'Indicates if existing release artifacts should be removed, Defaults to false.' + required: false + default: 'false' replacesArtifacts: description: "Indicates if existing release artifacts should be replaced. Defaults to true." required: false diff --git a/lib/ArtifactUploader.js b/lib/ArtifactUploader.js index ad06c636..d284839a 100644 --- a/lib/ArtifactUploader.js +++ b/lib/ArtifactUploader.js @@ -31,9 +31,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.GithubArtifactUploader = void 0; const core = __importStar(require("@actions/core")); class GithubArtifactUploader { - constructor(releases, replacesExistingArtifacts = true, throwsUploadErrors = false) { + constructor(releases, replacesExistingArtifacts = true, removeArtifacts = false, throwsUploadErrors = false) { this.releases = releases; this.replacesExistingArtifacts = replacesExistingArtifacts; + this.removeArtifacts = removeArtifacts; this.throwsUploadErrors = throwsUploadErrors; } uploadArtifacts(artifacts, releaseId, uploadUrl) { @@ -41,6 +42,9 @@ class GithubArtifactUploader { if (this.replacesExistingArtifacts) { yield this.deleteUpdatedArtifacts(artifacts, releaseId); } + if (this.removeArtifacts) { + yield this.deleteUploadedArtifacts(releaseId); + } for (const artifact of artifacts) { yield this.uploadArtifact(artifact, releaseId, uploadUrl); } @@ -84,5 +88,15 @@ class GithubArtifactUploader { } }); } + deleteUploadedArtifacts(releaseId) { + return __awaiter(this, void 0, void 0, function* () { + const releaseAssets = yield this.releases.listArtifactsForRelease(releaseId); + for (const artifact of releaseAssets) { + const asset = artifact; + core.debug(`Deleting existing artifact ${artifact.name}...`); + yield this.releases.deleteArtifact(asset.id); + } + }); + } } exports.GithubArtifactUploader = GithubArtifactUploader; diff --git a/lib/Inputs.js b/lib/Inputs.js index 2c5543c8..56e642f4 100644 --- a/lib/Inputs.js +++ b/lib/Inputs.js @@ -117,6 +117,10 @@ class CoreInputs { return undefined; return this.createdPrerelease; } + get removeArtifacts() { + const removes = core.getInput('removeArtifacts'); + return removes == 'true'; + } get replacesArtifacts() { const replaces = core.getInput('replacesArtifacts'); return replaces == 'true'; diff --git a/lib/Main.js b/lib/Main.js index bdb8485a..acd220d1 100644 --- a/lib/Main.js +++ b/lib/Main.js @@ -57,7 +57,7 @@ function createAction() { const inputs = new Inputs_1.CoreInputs(globber, context); const outputs = new Outputs_1.CoreOutputs(); const releases = new Releases_1.GithubReleases(inputs, git); - const uploader = new ArtifactUploader_1.GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild); + const uploader = new ArtifactUploader_1.GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.removeArtifacts, inputs.artifactErrorsFailBuild); return new Action_1.Action(inputs, outputs, releases, uploader); } run(); diff --git a/src/ArtifactUploader.ts b/src/ArtifactUploader.ts index 2cf4b66a..41b59391 100644 --- a/src/ArtifactUploader.ts +++ b/src/ArtifactUploader.ts @@ -10,6 +10,7 @@ export class GithubArtifactUploader implements ArtifactUploader { constructor( private releases: Releases, private replacesExistingArtifacts: boolean = true, + private removeArtifacts: boolean = false, private throwsUploadErrors: boolean = false, ) { } @@ -20,6 +21,9 @@ export class GithubArtifactUploader implements ArtifactUploader { if (this.replacesExistingArtifacts) { await this.deleteUpdatedArtifacts(artifacts, releaseId) } + if (this.removeArtifacts) { + await this.deleteUploadedArtifacts(releaseId) + } for (const artifact of artifacts) { await this.uploadArtifact(artifact, releaseId, uploadUrl) } @@ -65,4 +69,13 @@ export class GithubArtifactUploader implements ArtifactUploader { } } } + + private async deleteUploadedArtifacts(releaseId: number): Promise { + const releaseAssets = await this.releases.listArtifactsForRelease(releaseId) + for (const artifact of releaseAssets) { + const asset = artifact + core.debug(`Deleting existing artifact ${artifact.name}...`) + await this.releases.deleteArtifact(asset.id) + } + } } diff --git a/src/Inputs.ts b/src/Inputs.ts index 5d22a3a5..ae1f2484 100644 --- a/src/Inputs.ts +++ b/src/Inputs.ts @@ -15,6 +15,7 @@ export interface Inputs { readonly draft: boolean readonly owner: string readonly createdPrerelease: boolean + readonly removeArtifacts: boolean readonly replacesArtifacts: boolean readonly repo: string readonly tag: string @@ -138,6 +139,10 @@ export class CoreInputs implements Inputs { if (CoreInputs.omitPrereleaseDuringUpdate) return undefined return this.createdPrerelease } + get removeArtifacts(): boolean { + const removes = core.getInput('removeArtifacts') + return removes == 'true' + } get replacesArtifacts(): boolean { const replaces = core.getInput('replacesArtifacts') return replaces == 'true' diff --git a/src/Main.ts b/src/Main.ts index d3f1df8c..84c813a0 100644 --- a/src/Main.ts +++ b/src/Main.ts @@ -27,7 +27,7 @@ function createAction(): Action { const inputs = new CoreInputs(globber, context) const outputs = new CoreOutputs() const releases = new GithubReleases(inputs, git) - const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.artifactErrorsFailBuild) + const uploader = new GithubArtifactUploader(releases, inputs.replacesArtifacts, inputs.removeArtifacts, inputs.artifactErrorsFailBuild) return new Action(inputs, outputs, releases, uploader) }