Skip to content

Commit

Permalink
Release automation workflows
Browse files Browse the repository at this point in the history
Add GitHub Actions workflows to automatically:
- Create a draft release with release notes.
- Bump the version and close the milestone when a new release is published.
  • Loading branch information
martincostello committed May 12, 2024
1 parent 3ce85e7 commit 0aac77e
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 0 deletions.
211 changes: 211 additions & 0 deletions .github/workflows/bump-version.yml
@@ -0,0 +1,211 @@
name: bump-version

on:
release:
types: [ published ]
workflow_dispatch:
inputs:
version:
description: 'The optional version string for the next release.'
required: false
type: string
default: ''

permissions: {}

jobs:
bump-version:
runs-on: [ ubuntu-latest ]

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

permissions:
contents: write
pull-requests: write

steps:

- name: Checkout code
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2

- name: Bump version
id: bump-version
shell: pwsh
env:
NEXT_VERSION: ${{ inputs.version }}
run: |
$properties = Join-Path "." "Directory.Build.props"
$xml = [xml](Get-Content $properties)
$versionPrefix = $xml.SelectSingleNode('Project/PropertyGroup/VersionPrefix')
if (-Not [string]::IsNullOrEmpty(${env:NEXT_VERSION})) {
$version = [System.Version]::new(${env:NEXT_VERSION})
} else {
$version = [System.Version]::new($versionPrefix.InnerText)
$version = [System.Version]::new($version.Major, $version.Minor, $version.Build + 1)
}
$updatedVersion = $version.ToString()
$versionPrefix.InnerText = $updatedVersion
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($false)
$settings.Indent = $true
$settings.OmitXmlDeclaration = $true
$writer = [System.Xml.XmlWriter]::Create($properties, $settings)
$xml.Save($writer)
$writer.Flush()
$writer.Close()
$writer = $null
"" >> $properties
"version=${updatedVersion}" >> $env:GITHUB_OUTPUT
- name: Push changes to GitHub
id: push-changes
shell: pwsh
env:
GIT_COMMIT_USER_EMAIL: 'github-actions[bot]@users.noreply.github.com'
GIT_COMMIT_USER_NAME: 'github-actions[bot]'
NEXT_VERSION: ${{ steps.bump-version.outputs.version }}
run: |
$gitStatus = (git status --porcelain)
if ([string]::IsNullOrEmpty($gitStatus)) {
throw "No changes to commit."
}
git config color.diff always
git --no-pager diff
$branchName = "bump-version-${env:NEXT_VERSION}"
git config user.email ${env:GIT_COMMIT_USER_EMAIL} | Out-Null
git config user.name ${env:GIT_COMMIT_USER_NAME} | Out-Null
git fetch origin --no-tags | Out-Null
git rev-parse --verify --quiet "remotes/origin/${branchName}" | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "Branch ${branchName} already exists."
exit 0
}
git checkout -b $branchName
git add .
git commit -m "Bump version`n`nBump version to ${env:NEXT_VERSION} for the next release."
git push -u origin $branchName
"branch-name=${branchName}" >> $env:GITHUB_OUTPUT
"updated-version=true" >> $env:GITHUB_OUTPUT
"version=${env:NEXT_VERSION}" >> $env:GITHUB_OUTPUT
- name: Create pull request
if: steps.push-changes.outputs.updated-version == 'true'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
BASE_BRANCH: ${{ github.event.repository.default_branch }}
HEAD_BRANCH: ${{ steps.push-changes.outputs.branch-name }}
NEXT_VERSION: ${{ steps.push-changes.outputs.version }}
with:
script: |
const nextVersion = process.env.NEXT_VERSION;
const { repo, owner } = context.repo;
const workflowUrl = `${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
const { data: pr } = await github.rest.pulls.create({
title: 'Bump version',
owner,
repo,
head: process.env.HEAD_BRANCH,
base: process.env.BASE_BRANCH,
draft: true,
body: [
`Bump version to \`${nextVersion}\` for the next release.`,
'',
`This pull request was generated by [GitHub Actions](${workflowUrl}).`
].join('\n')
});
core.notice(`Created pull request ${owner}/${repo}#${pr.number}: ${pr.html_url}`);
try {
const { data: milestones } = await github.rest.issues.listMilestones({
owner,
repo,
state: 'open',
});
const title = `v${nextVersion}`;
let milestone = milestones.find((p) => p.title === title);
if (!milestone) {
const created = await github.rest.issues.createMilestone({
owner,
repo,
title,
});
milestone = created.data;
}
await github.rest.issues.update({
owner,
repo,
issue_number: pr.number,
milestone: milestone.number
});
} catch (error) {
// Ignore
}
close-milestone:
runs-on: [ ubuntu-latest ]
if: github.event_name == 'release'

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

permissions:
issues: write

steps:

- name: Close milestone
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
RELEASE_DATE: ${{ github.event.release.published_at }}
RELEASE_VERSION: ${{ github.event.release.tag_name }}
with:
script: |
const { repo, owner } = context.repo;
const { data: milestones } = await github.rest.issues.listMilestones({
owner,
repo,
state: 'open',
});
const milestone = milestones.find((p) => p.title === process.env.RELEASE_VERSION);
if (!milestone) {
return;
}
try {
await github.rest.issues.updateMilestone({
owner,
repo,
milestone_number: milestone.number,
state: 'closed',
due_on: process.env.RELEASE_DATE,
});
} catch (error) {
// Ignore
}
76 changes: 76 additions & 0 deletions .github/workflows/release.yml
@@ -0,0 +1,76 @@
name: release

on:
workflow_dispatch:
inputs:
publish:
description: 'If true, does not create the release as a draft.'
required: false
type: boolean
default: false

permissions: {}

jobs:
release:
runs-on: [ ubuntu-latest ]

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

permissions:
contents: write
issues: write

steps:

- name: Checkout code
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2

- name: Get version
id: get-version
shell: pwsh
run: |
$properties = Join-Path "." "Directory.Build.props"
$xml = [xml](Get-Content $properties)
$version = $xml.SelectSingleNode('Project/PropertyGroup/VersionPrefix').InnerText
"version=${version}" >> $env:GITHUB_OUTPUT
- name: Create release
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
DRAFT: ${{ inputs.publish != true }}
VERSION: ${{ steps.get-version.outputs.version }}
with:
script: |
const { repo, owner } = context.repo;
const draft = process.env.DRAFT === 'true';
const version = process.env.VERSION;
const tag_name = `v${version}`;
const name = tag_name;
const { data: notes } = await github.rest.repos.generateReleaseNotes({
owner,
repo,
tag_name,
target_commitish: process.env.DEFAULT_BRANCH,
});
const body = notes.body
.split('\n')
.filter((line) => !line.includes(' @dependabot '))
.filter((line) => !line.includes(' @github-actions '))
.join('\n');
const { data: release } = await github.rest.repos.createRelease({
owner,
repo,
tag_name,
name,
body,
draft,
});
core.notice(`Created release ${release.name}: ${release.html_url}`);

0 comments on commit 0aac77e

Please sign in to comment.