diff --git a/action.yml b/action.yml index 3b4ddb4..9580b55 100644 --- a/action.yml +++ b/action.yml @@ -4,7 +4,10 @@ author: 'GitHub' inputs: upload_url: description: 'The URL for uploading assets to the release' - required: true + required: false + release_tag: + description: 'The tag of release for uploading assets' + required: false asset_path: description: 'The path to the asset you want to upload' required: true diff --git a/dist/index.js b/dist/index.js index 6f0342d..788925c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4114,16 +4114,50 @@ function octokitDebug (octokit) { /***/ (function(module, __unusedexports, __webpack_require__) { const core = __webpack_require__(470); -const { GitHub } = __webpack_require__(469); +const { GitHub, context } = __webpack_require__(469); const fs = __webpack_require__(747); +async function getUploadUrlByReleaseTag(github, releaseTag) { + // Get owner and repo from context of payload that triggered the action + const { owner, repo } = context.repo; + + // Get the default tag name from the triggered action when not given + const tagName = releaseTag || context.ref; + + // This removes the 'refs/tags' portion of the string, i.e. from 'refs/tags/xxx' to 'xxx' + const tag = tagName.replace('refs/tags/', ''); + + // Get a release from the tag name + // API Documentation: https://developer.github.com/v3/repos/releases/#create-a-release + // Octokit Documentation: https://octokit.github.io/rest.js/#octokit-routes-repos-create-release + const response = await github.repos.getReleaseByTag({ + owner, + repo, + tag + }); + + // Get the upload URL for the created Release from the response + const { + data: { upload_url: uploadUrl } + } = response; + + return uploadUrl; +} + async function run() { try { // Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage const github = new GitHub(process.env.GITHUB_TOKEN); // Get the inputs from the workflow file: https://github.com/actions/toolkit/tree/master/packages/core#inputsoutputs - const uploadUrl = core.getInput('upload_url', { required: true }); + const uploadUrlVar = core.getInput('upload_url', { required: false }); + const releaseTag = core.getInput('release_tag', { required: false }); + + const uploadUrl = uploadUrlVar || (await getUploadUrlByReleaseTag(github, releaseTag)); + if (!uploadUrl) { + throw new Error('UploadUrl was not supplied and was failed getting by releaseTag'); + } + const assetPath = core.getInput('asset_path', { required: true }); const assetName = core.getInput('asset_name', { required: true }); const assetContentType = core.getInput('asset_content_type', { required: true }); diff --git a/src/upload-release-asset.js b/src/upload-release-asset.js index 411bdcf..538ecc6 100644 --- a/src/upload-release-asset.js +++ b/src/upload-release-asset.js @@ -1,14 +1,48 @@ const core = require('@actions/core'); -const { GitHub } = require('@actions/github'); +const { GitHub, context } = require('@actions/github'); const fs = require('fs'); +async function getUploadUrlByReleaseTag(github, releaseTag) { + // Get owner and repo from context of payload that triggered the action + const { owner, repo } = context.repo; + + // Get the default tag name from the triggered action when not given + const tagName = releaseTag || context.ref; + + // This removes the 'refs/tags' portion of the string, i.e. from 'refs/tags/xxx' to 'xxx' + const tag = tagName.replace('refs/tags/', ''); + + // Get a release from the tag name + // API Documentation: https://developer.github.com/v3/repos/releases/#create-a-release + // Octokit Documentation: https://octokit.github.io/rest.js/#octokit-routes-repos-create-release + const response = await github.repos.getReleaseByTag({ + owner, + repo, + tag + }); + + // Get the upload URL for the created Release from the response + const { + data: { upload_url: uploadUrl } + } = response; + + return uploadUrl; +} + async function run() { try { // Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage const github = new GitHub(process.env.GITHUB_TOKEN); // Get the inputs from the workflow file: https://github.com/actions/toolkit/tree/master/packages/core#inputsoutputs - const uploadUrl = core.getInput('upload_url', { required: true }); + const uploadUrlVar = core.getInput('upload_url', { required: false }); + const releaseTag = core.getInput('release_tag', { required: false }); + + const uploadUrl = uploadUrlVar || (await getUploadUrlByReleaseTag(github, releaseTag)); + if (!uploadUrl) { + throw new Error('UploadUrl was not supplied and was failed getting by releaseTag'); + } + const assetPath = core.getInput('asset_path', { required: true }); const assetName = core.getInput('asset_name', { required: true }); const assetContentType = core.getInput('asset_content_type', { required: true }); diff --git a/tests/upload-release-asset.test.js b/tests/upload-release-asset.test.js index cf95ec8..1c22f33 100644 --- a/tests/upload-release-asset.test.js +++ b/tests/upload-release-asset.test.js @@ -9,6 +9,7 @@ const run = require('../src/upload-release-asset'); /* eslint-disable no-undef */ describe('Upload Release Asset', () => { + let getReleaseByTag; let uploadReleaseAsset; let content; @@ -19,6 +20,12 @@ describe('Upload Release Asset', () => { } }); + getReleaseByTag = jest.fn().mockReturnValueOnce({ + data: { + upload_url: 'upload_url' + } + }); + fs.statSync = jest.fn().mockReturnValueOnce({ size: 527 }); @@ -31,8 +38,11 @@ describe('Upload Release Asset', () => { repo: 'repo' }; + context.ref = 'refs/tags/release_tag'; + const github = { repos: { + getReleaseByTag, uploadReleaseAsset } }; @@ -40,10 +50,49 @@ describe('Upload Release Asset', () => { GitHub.mockImplementation(() => github); }); - test('Upload release asset endpoint is called', async () => { + test('Upload release asset endpoint is called via upload_url', async () => { core.getInput = jest .fn() .mockReturnValueOnce('upload_url') + .mockReturnValueOnce(null) + .mockReturnValueOnce('asset_path') + .mockReturnValueOnce('asset_name') + .mockReturnValueOnce('asset_content_type'); + + await run(); + + expect(uploadReleaseAsset).toHaveBeenCalledWith({ + url: 'upload_url', + headers: { 'content-type': 'asset_content_type', 'content-length': 527 }, + name: 'asset_name', + file: content + }); + }); + + test('Upload release asset endpoint is called via release_tag', async () => { + core.getInput = jest + .fn() + .mockReturnValueOnce(null) + .mockReturnValueOnce('release_tag') + .mockReturnValueOnce('asset_path') + .mockReturnValueOnce('asset_name') + .mockReturnValueOnce('asset_content_type'); + + await run(); + + expect(uploadReleaseAsset).toHaveBeenCalledWith({ + url: 'upload_url', + headers: { 'content-type': 'asset_content_type', 'content-length': 527 }, + name: 'asset_name', + file: content + }); + }); + + test('Upload release asset endpoint is called via default release_tag', async () => { + core.getInput = jest + .fn() + .mockReturnValueOnce(null) + .mockReturnValueOnce(null) .mockReturnValueOnce('asset_path') .mockReturnValueOnce('asset_name') .mockReturnValueOnce('asset_content_type');