Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Inputs can be used to customize the behavior of the action in both modes.

| Name | Description |
| ---- | ----------- |
| `initial-version` | If defined when run against the first commit of a new repository (e.g. a templatized repository), idempotently tags that commit with this version number. |
| `mode` | ***Required.*** `validate` or `bump`. |
| `repo-token` | ***Required.*** The `GITHUB_TOKEN` for the repo. Needed for fetching pull request data and tagging new releases. |
| `major-label` | The name of the label that indicates the pull request should result in a **major** version bump. _Default: 'major release'_. |
Expand Down
16 changes: 16 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
const core = require('@actions/core')
const github = require('@actions/github')

// Gets and validates the 'initial-version' input
function getInitialVersion() {
const initialVersion = core.getInput('initial-version')
if (initialVersion === undefined || initialVersion === '') {
return undefined
}

const match = initialVersion.match(/^v?(\d+\.\d+\.\d+)$/)
if (match) {
return match[1]
}

throw new Error('initial-version must be in one of the following forms: X.Y.Z or vX.Y.Z')
}

// Gets all the required inputs and validates them before proceeding.
function getConfig() {
const mode = core.getInput('mode', { required: true }).toLowerCase()
Expand Down Expand Up @@ -30,6 +45,7 @@ function getConfig() {
releaseLabels[core.getInput('patch-label') || 'patch release'] = 'patch'

return {
initialVersion: getInitialVersion(),
mode: mode,
octokit: github.getOctokit(token),
releaseLabels: releaseLabels,
Expand Down
8 changes: 8 additions & 0 deletions config.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ test('establishes config from minimum required inputs', () => {
})

test('establishes config from complete set of inputs', () => {
process.env['INPUT_INITIAL-VERSION'] = 'v1.2.3'
process.env['INPUT_MODE'] = 'validate'
process.env['INPUT_REPO-TOKEN'] = 'mockRepoToken'
process.env['INPUT_MAJOR-LABEL'] = 'major-label-name'
Expand All @@ -33,6 +34,7 @@ test('establishes config from complete set of inputs', () => {
process.env['INPUT_WITH-V'] = 'true'

const config = getConfig()
expect(config.initialVersion).toBe('1.2.3')
expect(config.mode).toBe('validate')
expect(config.releaseLabels).toEqual({
'major-label-name': 'major',
Expand All @@ -53,6 +55,12 @@ test('throws when a required input is missing', () => {
expect(getConfig).toThrow('Input required and not supplied: mode')
})

test('errors out when an invalid initial-version is specified', () => {
process.env['INPUT_INITIAL-VERSION'] = 'invalid'
process.env['INPUT_MODE'] = 'bump'
expect(getConfig).toThrow('initial-version must be in one of the following forms: X.Y.Z or vX.Y.Z')
})

test('errors out when an invalid mode is specified', () => {
process.env['INPUT_MODE'] = 'invalid'
process.env['INPUT_REPO-TOKEN'] = 'mockRepoToken'
Expand Down
121 changes: 97 additions & 24 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

75 changes: 52 additions & 23 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,21 @@ function isActivePR() {
return github.context.eventName === 'pull_request' && github.context.payload.pull_request !== undefined
}

// Returns true if an initial version is configured
// and the current commit is the repository's initial commit.
async function isInitialVersion(config) {
if (config.initialVersion === undefined) {
return false
}

const commit = await config.octokit.git.getCommit({
...github.context.repo,
commit_sha: github.context.sha,
})

return commit.data.parents.length === 0
}

// Returns true if the current context looks like a merge commit.
function isMergeCommit() {
return github.context.eventName === 'push' && github.context.payload.head_commit !== undefined
Expand Down Expand Up @@ -60,36 +75,50 @@ async function validateActivePR(config) {

// Increments the version according to the release type and tags a new version with release notes.
async function bumpAndTagNewVersion(config) {
if (!isMergeCommit()) {
core.warning("in 'bump' mode, but this doesn't look like a PR merge commit event (is your workflow misconfigured?)")
return
}
let currentVersion
let idempotent = false
let newVersion
let releaseNotes
let releaseType

const num = extractPRNumber(github.context.payload.head_commit.message)
let pr
if (num == null) {
core.info('Unable to determine PR from commit msg, searching for PR by SHA')
// Try to search the commit sha for the PR number
pr = await searchPRByCommit(process.env.GITHUB_SHA, config)
if (pr == null) {
// Don't want to fail the job if some other commit comes in, but let's warn about it.
// Might be a good point for configuration in the future.
core.warning("head commit doesn't look like a PR merge, skipping version bumping and tagging")
return
if (isMergeCommit()) {
const num = extractPRNumber(github.context.payload.head_commit.message)
let pr
if (num == null) {
core.info('Unable to determine PR from commit msg, searching for PR by SHA')
// Try to search the commit sha for the PR number
pr = await searchPRByCommit(process.env.GITHUB_SHA, config)
if (pr == null) {
// Don't want to fail the job if some other commit comes in,
// but let's warn about it. Might be a good point for configuration
// in the future.
core.warning("head commit doesn't look like a PR merge, skipping version bumping and tagging")
return
}
} else {
pr = await fetchPR(num, config)
}
core.info(`Processing version bump for PR request #${pr.number}`)
releaseType = getReleaseType(pr, config)
releaseNotes = getReleaseNotes(pr, config)
currentVersion = await getCurrentVersion(config)
newVersion = semver.inc(currentVersion, releaseType)
} else if (isInitialVersion(config)) {
idempotent = true
releaseNotes = 'Initial commit'
newVersion = config.initialVersion
} else {
pr = await fetchPR(num, config)
core.warning("in 'bump' mode, but this doesn't look like a PR merge commit event (is your workflow misconfigured?)")
return
}
core.info(`Processing version bump for PR request #${pr.number}`)
const releaseType = getReleaseType(pr, config)
const releaseNotes = getReleaseNotes(pr, config)
const currentVersion = await getCurrentVersion(config)

const newVersion = semver.inc(currentVersion, releaseType)
const newTag = await createRelease(newVersion, releaseNotes, config)
const newTag = await createRelease(newVersion, releaseNotes, config, idempotent)
core.info(`Created release tag ${newTag} with the following release notes:\n${releaseNotes}\n`)

core.setOutput('old-version', `${config.v}${currentVersion}`)
if (currentVersion !== undefined) {
core.setOutput('old-version', `${config.v}${currentVersion}`)
}

core.setOutput('version', newTag)
core.setOutput('release-notes', releaseNotes)
}
Expand Down
30 changes: 29 additions & 1 deletion version.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
const core = require('@actions/core')
const github = require('@actions/github')
const semver = require('semver')

const httpNotFound = 404

// Returns true if the current commit already has the given tag.
async function isAlreadyTagged(tag, config) {
try {
await config.octokit.git.getRef({
...github.context.repo,
ref: `tags/${tag}`,
})
return true
} catch (error) {
if (error.status === httpNotFound) {
return false
}

throw error
}
}

// Tags the specified version and annotates it with the provided release notes.
async function createRelease(version, releaseNotes, config) {
// If idempotent is true and the commit already contains the tag, the commit
// is untouched.
async function createRelease(version, releaseNotes, config, idempotent) {
const tag = `${config.v}${version}`

if (idempotent && await isAlreadyTagged(tag, config)) {
core.info(`Commit is already tagged with ${tag} -- skipping`)
return tag
}

const tagCreateResponse = await config.octokit.git.createTag({
...github.context.repo,
tag: tag,
Expand Down
Loading