diff --git a/README.md b/README.md index 057faff..66bd219 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,96 @@ # Autotag -...fill me in... +Outputs: tag - empty if no tag is created. + +## Usage + +The following is an example `.github/main.workflow` that will execute when a `push` to the `master` branch occurs. + +```yaml +name: Node CI + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - uses: butlerlogic/action-autotag@1.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + +``` + +To make this work, the workflow must have the checkout action _before_ the autotag action. + +This **order** is important! + +```yaml +- uses: actions/checkout@master +- uses: butlerlogic/action-autotag@1.0.0 +``` + +> If the repository is not checked out first, the autotagger cannot find the package.json file. + +## Configuration + +The `GITHUB_TOKEN` must be passed in. Without this, it is not possible to create a new tag. Make sure the autotag action looks like the following example: + +```yaml +- uses: butlerlogic/action-autotag@1.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" +``` + +The action will automatically extract the token at runtime. **DO NOT MANUALLY ENTER YOUR TOKEN.** If you put the actual token in your workflow file, you're make it accessible in plaintext to anyone who ever views the repository (it wil be in your git history). + +### Optional Configurations + +There are several options to customize how the tag is created. + +1. `tag_prefx` + + By default, `package.json` uses [semantic versioning](https://semver.org/), such as `1.0.0`. A prefix can be used to add text before the tag name. For example, if `tag_prefx` is set to `v`, then the tag would be labeled as `v1.0.0`. + + ```yaml + - uses: butlerlogic/action-autotag@1.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + tag_prefx: "v" + ``` + +1. `tag_suffix` + + Text can also be applied to the end of the tag by setting `tag_suffix`. For example, if `tag_suffix` is ` (beta)`, the tag would be `1.0.0 (beta)`. Please note this example violates semantic versioning and is merely here to illustrate how to add text to the end of a tag name if you _really_ want to. + + ```yaml + - uses: butlerlogic/action-autotag@1.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + tag_suffix: " (beta)" + ``` + +1. `tag_message` + + This is the annotated commit message associated with the tag. By default, a + changelog will be generated from the commits between the latest tag and the new tag (HEAD). This will override that with a hard-coded message. + + ```yaml + - uses: butlerlogic/action-autotag@1.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + tag_message: "Custom message goes here." + ``` + +## Developer Notes + +If you are building an action that runs after this one, be aware this action produces several [outputs](https://help.github.com/en/articles/metadata-syntax-for-github-actions#outputs): + +1. `tagname` will be empty if no tag was created, or it will be the value of the new tag. +1. `tagsha`: The SHA of the new tag. +1. `taguri`: The URI/URL of the new tag reference. +1. `version` will be the version attribute found in the `package.json` file. \ No newline at end of file diff --git a/lib/main.js b/lib/main.js index 3698f4f..15d754c 100644 --- a/lib/main.js +++ b/lib/main.js @@ -6,22 +6,131 @@ const path = require('path') async function run() { try { - const git = new github.GitHub(process.env.INPUT_GITHUB_TOKEN) - core.warning(` Available environment variables:\n -> ${Object.keys(process.env).map(i => i + ' :: ' + process.env[i]).join('\n -> ')}`) + core.debug(` Available environment variables:\n -> ${Object.keys(process.env).map(i => i + ' :: ' + process.env[i]).join('\n -> ')}`) let dir = fs.readdirSync(path.resolve(process.env.GITHUB_WORKSPACE), { withFileTypes: true }).map(entry => { return `${entry.isDirectory() ? '> ' : ' - '}${entry.name}` }).join('\n') - core.warning(` Working Directory: ${process.env.GITHUB_WORKSPACE}:\n${dir}`) + core.debug(` Working Directory: ${process.env.GITHUB_WORKSPACE}:\n${dir}`) - let pkg = require(path.join(process.env.GITHUB_WORKSPACE, 'package.json')) + let pkgfile = path.join(process.env.GITHUB_WORKSPACE, 'package.json') - core.warning(` ${pkg.version}`) + if (!fs.existsSync(pkgfile)) { + core.setFailed('package.json does not exist.') + return + } - core.setOutput('tag', '') + let pkg = require(pkgfile) + + core.setOutput('version', pkg.version) + core.debug(` Detected version ${pkg.version}`) + + if (!process.env.hasOwnProperty('INPUT_GITHUB_TOKEN') || process.env.INPUT_GITHUB_TOKEN.trim().length === 0) { + core.setFailed('Invalid or missing GITHUB_TOKEN.') + return + } + + // Check existing for existing tag + const git = new github.GitHub(process.env.INPUT_GITHUB_TOKEN) + const owner = process.env.GITHUB_ACTOR + const repo = process.env.GITHUB_REPOSITORY.split('/').pop() + + let tags = await git.repos.listTags({ + owner, + repo, + per_page: 100 + }) + + // Check for existance of tag and abort (short circuit) if it already exists. + for (let tag of tags.data) { + if (tag.name.trim().toLowerCase() === pkg.version.trim().toLowerCase()) { + core.warning(`"${tag.name.trim()}" tag already exists.`) + core.setOutput('tagname', '') + return + } + } + + // Create the new tag name + let tagName = pkg.version + const tagPrefix = core.getInput('tag_prefix', { required: false }) + const tagSuffix = core.getInput('tag_suffix', { required: false }) + const tagMsg = core.getInput('tag_message', { required: false }).trim() + + tagName = `${tagPrefix}${tagName}${tagSuffix}` + + if (tagMsg.length === 0 && tags.data.length > 0) { + try { + latestTag = tags.data.shift() + + let changelog = await git.repos.compareCommits({ + owner, + repo, + base: latestTag.name, + head: 'master' + }) + + tagMsg = changelog.data.commits.map(commit => `**1) ${commit.commit.message}** (${commit.author.login})\n(SHA: ${commit.sha})\n`).join('\n') + } catch (e) { + core.setFailed(e.message) + return + } + } + + let newTag + + try { + newTag = await git.git.createTag({ + owner, + repo, + tag: tagName, + message: tagMsg.trim().length > 0 + ? tagMsg + : `Version ${pkg.version}`, + object: process.env.GITHUB_SHA, + type: 'commit' + }) + + core.warning(`Created new tag: ${newTag.data.tag}`) + } catch (e) { + core.setFailed(e.message) + return + } + + let newReference + + try { + newReference = await git.git.createRef({ + owner, + repo, + ref: `refs/tags/${newTag.data.tag}`, + sha: newTag.data.sha + }) + + core.warning(`Reference ${newReference.data.ref} available at ${newReference.data.url}`) + } catch (e) { + core.warning({ + owner, + repo, + ref: `refs/tags/${newTag.data.tag}`, + sha: newTag.data.sha + }) + + core.setFailed(e.message) + return + } + + // Store values for other actions + if (typeof newTag === 'object' && typeof newReference === 'object') { + core.setOutput('tagname', tagName) + core.setOutput('tagsha', newTag.data.sha) + core.setOutput('taguri', newReference.data.url) + } } catch (error) { - core.setFailed(error.message) + core.warning(error.message) + core.setOutput('tagname', '') + core.setOutput('tagsha', '') + core.setOutput('taguri', '') } }