Skip to content
Merged
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ A collection of GitHub actions used at Railsware
- [read-from-ssm-param-store](/read-from-ssm-param-store/README.md) - retrieve information about parameters in a specific hierarchy
- [run-ecs-task](/run-ecs-task/README.md) - run a one-off task on AWS Elastic Container Service

SDK release:

- [resolve-pending-release](/resolve-pending-release/action.yml) - read the newest CHANGELOG.md version and report whether a draft GitHub Release exists for it
- [check-sdk-github-release](/check-sdk-github-release/action.yml) - fail unless a draft GitHub Release exists for the tag
- [check-sdk-npm-release](/check-sdk-npm-release/action.yml) - fail if the version is already on npm
- [check-sdk-rubygems-release](/check-sdk-rubygems-release/action.yml) - fail if the version is already on RubyGems
- [publish-github-release](/publish-github-release/action.yml) - pin a draft GitHub Release to a commit and un-draft it

---

## running action on local machine
Expand Down
37 changes: 37 additions & 0 deletions check-sdk-github-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Check SDK GitHub release
description: Fail the job unless a draft GitHub Release exists for the tag (fails if it is missing or already published).
inputs:
tag:
required: true
description: "GitHub Release tag (vX.Y.Z)"
repo:
required: false
default: ${{ github.repository }}
description: "owner/name of the repository hosting the release"
github-token:
required: false
default: ${{ github.token }}
description: "Token with read access to the repository's releases"
runs:
using: composite
steps:
- shell: bash
env:
GH_TOKEN: ${{ inputs.github-token }}
TAG: ${{ inputs.tag }}
REPO: ${{ inputs.repo }}
run: |
state=$(gh release view "$TAG" --repo "$REPO" --json isDraft --jq .isDraft 2>/dev/null || echo missing)
case "$state" in
true)
echo "Draft GitHub Release $TAG found"
;;
false)
echo "::error::GitHub Release $TAG is already published"
exit 1
;;
*)
echo "::error::No draft GitHub Release $TAG found; run the draft-release workflow first"
exit 1
;;
esac
27 changes: 27 additions & 0 deletions check-sdk-npm-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Check SDK npm release
description: Fail the job if the package version is already published to npm.
inputs:
package:
required: true
description: "npm package name"
version:
required: true
description: "Version to check (X.Y.Z)"
runs:
using: composite
steps:
- shell: bash
env:
PACKAGE: ${{ inputs.package }}
VERSION: ${{ inputs.version }}
run: |
# E404 means the version (or the whole package) is not published yet; any other error
# must not read as "not published".
if output=$(npm view "$PACKAGE@$VERSION" version 2>&1); then
echo "::error::$PACKAGE@$VERSION is already published to npm"
exit 1
elif [[ "$output" != *E404* ]]; then
echo "$output" >&2
echo "::error::Failed to check whether $PACKAGE@$VERSION is published to npm"
exit 1
fi
37 changes: 37 additions & 0 deletions check-sdk-rubygems-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Check SDK RubyGems release
description: Fail the job if the gem version is already published to RubyGems.
inputs:
gem:
required: true
description: "Gem name"
version:
required: true
description: "Version to check (X.Y.Z)"
runs:
using: composite
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
GEM: ${{ inputs.gem }}
VERSION: ${{ inputs.version }}
with:
script: |
const gem = process.env.GEM;
const version = process.env.VERSION;

const res = await fetch(`https://rubygems.org/api/v1/versions/${gem}.json`);

// 404 means the gem has no published versions yet — nothing to check.
if (res.status === 404) {
core.info(`${gem} has no published versions yet`);
return;
}
if (!res.ok) {
core.setFailed(`Failed to query RubyGems for ${gem} (HTTP ${res.status})`);
return;
}

const versions = await res.json();
if (versions.some((v) => v.number === version)) {
core.setFailed(`${gem} ${version} is already published to RubyGems`);
}
68 changes: 68 additions & 0 deletions publish-github-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Publish GitHub release
description: Mark a draft GitHub Release as published (un-draft it), optionally pinning it to a commit first.
inputs:
tag:
required: true
description: "Release tag (vX.Y.Z)"
target:
required: false
default: ""
description: "Commit SHA to pin the release to before un-drafting; the tag is created at this commit"
repo:
required: false
default: ${{ github.repository }}
description: "owner/name of the repository hosting the release"
github-token:
required: false
default: ${{ github.token }}
description: "Token with permission to edit the repository's releases"
runs:
using: composite
steps:
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
TAG: ${{ inputs.tag }}
TARGET: ${{ inputs.target }}
REPO: ${{ inputs.repo }}
with:
github-token: ${{ inputs.github-token }}
script: |
const { TAG: tag, TARGET: target } = process.env;
const [owner, repo] = process.env.REPO.split('/');

// getReleaseByTag does not return drafts (they have no tag yet), so list and match.
const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 });
const release = releases.find((r) => r.tag_name === tag);
if (!release) {
core.setFailed(`No GitHub Release ${tag} found`);
return;
}

if (target) {
// GitHub ignores target_commitish when the tag already exists, so an existing tag
// must already point at the target. A missing tag is created at the target below.
let tagged;
try {
const { data: ref } = await github.rest.git.getRef({ owner, repo, ref: `tags/${tag}` });
tagged = ref.object.type === 'tag'
? (await github.rest.git.getTag({ owner, repo, tag_sha: ref.object.sha })).data.object.sha
: ref.object.sha;
} catch (err) {
if (err.status !== 404) throw err;
}
if (tagged && tagged !== target) {
core.setFailed(`Tag ${tag} already exists at ${tagged}, not at ${target}`);
return;
}
}

// A draft's target is usually a branch name, so un-drafting would tag wherever that
// branch points at that moment. Pinning to the built commit keeps the tag on what shipped.
await github.rest.repos.updateRelease({
owner,
repo,
release_id: release.id,
draft: false,
...(target && { target_commitish: target }),
});
core.info(`Published GitHub Release ${tag}${target ? ` at ${target}` : ''}`);
67 changes: 67 additions & 0 deletions resolve-pending-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Resolve pending release
description: >
Read the newest version from the top "## [X.Y.Z]" heading of the changelog and report whether
a draft GitHub Release exists for it. Does not fail when nothing is pending.
inputs:
changelog:
required: false
default: CHANGELOG.md
description: "Path to the changelog written by prepend-changelog"
tag-prefix:
required: false
default: v
description: "Prefix of the release tag before the version (empty for bare X.Y.Z tags)"
repo:
required: false
default: ${{ github.repository }}
description: "owner/name of the repository hosting the release"
github-token:
required: false
default: ${{ github.token }}
description: "Token with read access to the repository's releases"
outputs:
version:
description: "Newest changelog version (X.Y.Z)"
value: ${{ steps.resolve.outputs.version }}
tag:
description: "Release tag (tag-prefix + version)"
value: ${{ steps.resolve.outputs.tag }}
pending:
description: "'true' when a draft GitHub Release exists for the tag, 'false' otherwise"
value: ${{ steps.resolve.outputs.pending }}
runs:
using: composite
steps:
- id: resolve
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
env:
CHANGELOG: ${{ inputs.changelog }}
TAG_PREFIX: ${{ inputs.tag-prefix }}
REPO: ${{ inputs.repo }}
with:
github-token: ${{ inputs.github-token }}
script: |
const fs = require('fs');
const changelog = process.env.CHANGELOG;
const [owner, repo] = process.env.REPO.split('/');

// Only version headings count, so a hand-added "## [Unreleased]" is skipped.
const match = fs.readFileSync(changelog, 'utf8').match(/^## \[(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\]/m);
if (!match) {
core.setFailed(`No '## [X.Y.Z]' heading found in ${changelog}`);
return;
}
const version = match[1];
const tag = `${process.env.TAG_PREFIX}${version}`;

// Matching the changelog, not just any draft, ties the release to the release commit:
// SDKs without a version file have their draft from the moment it is drafted.
// getReleaseByTag does not return drafts (they have no tag yet), so list and match.
const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 });
const release = releases.find((r) => r.tag_name === tag);
const state = release ? (release.draft ? 'draft' : 'published') : 'missing';

core.info(`Newest changelog version ${version}, GitHub Release ${tag}: ${state}`);
core.setOutput('version', version);
core.setOutput('tag', tag);
core.setOutput('pending', String(state === 'draft'));
Loading