Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
928bbd3
create-release-pr
marek-saji Oct 7, 2025
f621854
create-release-pr: Pretty white space in CHANGELOG
marek-saji Jul 28, 2025
0edcbd1
create-release: new vision
marek-saji Oct 7, 2025
a546802
feat(create-release): Use semantic-release
marek-saji Oct 2, 2025
c4884eb
chore: Kebab case for inputs
marek-saji Oct 6, 2025
7673a14
create-release: Remove working-dir input, until it’s needed
marek-saji Oct 7, 2025
c81996d
create-release: dry-run
marek-saji Oct 7, 2025
155d04a
create-release: Triggering GH user in git commit
marek-saji Oct 7, 2025
797486b
create-release: Use ::error::
marek-saji Oct 7, 2025
ca4defb
fix(create-release): Piping release notes to step summary
marek-saji Oct 7, 2025
c349fd0
create-release: Notify about success
marek-saji Oct 7, 2025
8a33082
create-release: No notify on dry-run
marek-saji Oct 7, 2025
9d52128
create-release: Use GitHub app token
marek-saji Oct 7, 2025
901e3e3
Remove TODOs
marek-saji Oct 7, 2025
7556e22
doc(create-release)
marek-saji Oct 7, 2025
af58a91
DEBUG(create-release): Use setup@feat/create-release
marek-saji Oct 7, 2025
897a962
create-release: Note about $s
marek-saji Oct 8, 2025
443b82f
create-release: Skip previous_tag_name, if there’s none
marek-saji Oct 8, 2025
f408ce2
create-release: Custom success PR comment
marek-saji Oct 9, 2025
a7e511d
fixup! create-release: Skip previous_tag_name, if there’s none
marek-saji Oct 9, 2025
45890b2
fixup! create-release: Note about $s
marek-saji Oct 9, 2025
5b69386
fixup! create-release: Custom success PR comment
marek-saji Oct 9, 2025
5020218
create-release: unindent shell commands
marek-saji Oct 9, 2025
5058987
fixup! create-release: Custom success PR comment
marek-saji Oct 9, 2025
aae9465
fixup! create-release: Use GitHub app token
marek-saji Oct 9, 2025
d380320
create-release: Improve setup instructions
marek-saji Oct 13, 2025
af8dd51
create-release: docs
marek-saji Oct 14, 2025
b5b898a
fixup! create-release: Triggering GH user in git commit
marek-saji Oct 23, 2025
9f49b22
create-release: Move config to separate file
marek-saji Oct 23, 2025
7a872b0
Merge remote-tracking branch 'origin/v1' into feat/create-release
marek-saji Oct 23, 2025
a6c1d21
create-release: New version and notes in notification
marek-saji Oct 23, 2025
4ee526b
fixup! create-release: New version and notes in notification
marek-saji Oct 23, 2025
a1d8ea0
fixup! create-release: New version and notes in notification
marek-saji Oct 23, 2025
5eb0a9a
create-release: Quote shell env vars
marek-saji Oct 27, 2025
e413b85
setup: Multi–line descriptions
marek-saji Nov 10, 2025
c7a3628
setup: Expand token–related descriptions
marek-saji Nov 10, 2025
f19ddb7
Typo
marek-saji Nov 13, 2025
18085a4
create-release: inputs, not vars
marek-saji Nov 13, 2025
646a51e
create-release: Drop ENV_VARS
marek-saji Nov 13, 2025
a44a7a1
fixup! create-release: New version and notes in notification
marek-saji Nov 17, 2025
f577d46
feat(notify-status): Include text–only value
marek-saji Nov 17, 2025
dcc7879
feat(create-release): Graceful when slackifying fails
marek-saji Nov 17, 2025
41570c3
fixup! feat(notify-status): Include text–only value
marek-saji Nov 17, 2025
48c0298
fixup! fixup! feat(notify-status): Include text–only value
marek-saji Nov 17, 2025
afdab38
feat(create-release): Update package.json if exists only
marek-saji Nov 18, 2025
fb41b25
fix(create-release): Drop type:module import assertion
marek-saji Nov 18, 2025
373c222
fixup! feat(create-release): Update package.json if exists only
marek-saji Nov 18, 2025
50141c4
feat(create-release): Check other workflows before running
marek-saji Nov 18, 2025
4233b53
Merge remote-tracking branch 'origin/v1' into feat/create-release
marek-saji Nov 18, 2025
28ebaa4
fixup! feat(create-release): Check other workflows before running
marek-saji Nov 18, 2025
6d0035c
fix(create-release): Brute force when installing semantic-release plu…
marek-saji Nov 18, 2025
28499b1
feat(create-release): Split Release step
marek-saji Nov 18, 2025
c312664
Merge remote-tracking branch 'origin/v1' into feat/create-release
marek-saji Nov 25, 2025
e08fa8f
chore(create-release): Switch versions from feat branch to v1
marek-saji Nov 25, 2025
a3edfda
chore: prettier -w .
marek-saji Nov 25, 2025
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
208 changes: 208 additions & 0 deletions .github/workflows/create-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
name: Create Release

# Uses `semantic-release` under the hood, but we did not want to force
# devs to be pedantic with commit messages, so instead we take GitHub
# approach and list only merged PRs in the release notes and allow
# releaser to decide if it’s major, minor or patch release.
#
# - Push a commit bumping up a version:
# - Version in `package.json` and — if it exists — `version.txt`
# - Release notes in `CHANGELOG.md`
# - Create git version tag
# - Create GitHub release
# - Send slack notification
#
#
# # Authentication
#
# This workflow needs higher permissions to be able to push to main
# branch etc. We obtain it using a GitHub app. App needs to have these
# permissions:
#
# - Contents: Read & Write
# - Issues: Read & Write
# - Pull requests: Read & Write
#
# Install it and and set `CREATE_RELEASE_APP_ID` and
# `CREATE_RELEASE_APP_PRIVATE_KEY` secrets. Convinent to do so on the
# organisation level.

on:
workflow_call:
inputs:
release-type:
description: 'Type of release (major, minor, patch, or prerelease)'
type: string
default: 'patch'
dry-run:
description: 'Dry–run'
type: boolean
default: false

jobs:
release:
name: 'Create Release'
runs-on: ubuntu-latest

permissions:
contents: write
pull-requests: write
issues: write

steps:
- name: Verify inputs
run: |
if ! echo "${{ inputs.release-type }}" | grep -qE '^(major|minor|patch|prerelease)$'
then
echo "::error::Invalid release-type: ${{ inputs.release-type }}. Must be one of: major, minor, patch, prerelease."
exit 64 # EX_USAGE
fi

if [ "${{ inputs.release-type }}" = "prerelease" ]
then
echo "::error::Prerelease not supported (yet)."
exit 64 # EX_USAGE
fi
Comment thread
marek-saji marked this conversation as resolved.
Comment thread
marek-saji marked this conversation as resolved.

- name: Set environment variables
env:
ENV_VARS: ${{ secrets.env_vars }}
run: |
echo "$ENV_VARS" >> "$GITHUB_ENV"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Guard ENV_VARS append to avoid malformed GITHUB_ENV.

If the secret is empty or misformatted, it can corrupt the environment for later steps. Add a simple guard.

-          echo "$ENV_VARS" >> "$GITHUB_ENV"
+          if [ -n "$ENV_VARS" ]; then
+            printf '%s\n' "$ENV_VARS" >> "$GITHUB_ENV"
+          fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Set environment variables
env:
ENV_VARS: ${{ secrets.env_vars }}
run: |
echo "$ENV_VARS" >> "$GITHUB_ENV"
- name: Set environment variables
env:
ENV_VARS: ${{ secrets.env_vars }}
run: |
if [ -n "$ENV_VARS" ]; then
printf '%s\n' "$ENV_VARS" >> "$GITHUB_ENV"
fi
🤖 Prompt for AI Agents
.github/workflows/create-release.yaml around lines 56 to 61: the workflow
unconditionally appends the ENV_VARS secret to GITHUB_ENV which can corrupt
subsequent steps if the secret is empty or malformed; update the step to first
check that ENV_VARS is non-empty and well-formed (e.g., contains at least one
KEY=VALUE line), and only append when that validation passes, otherwise emit a
clear warning and exit non-zero (or skip append) to avoid corrupting GITHUB_ENV.

- name: 'GitHub App token: Token'
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.CREATE_RELEASE_APP_ID }}
private-key: ${{ secrets.CREATE_RELEASE_APP_PRIVATE_KEY }}

- name: 'GitHub App token: App Data'
id: app-token-data
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
user_id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)
echo "user-id=$user_id" >> "$GITHUB_OUTPUT"

- name: Setup
id: setup
uses: verkstedt/actions/setup@feat/create-release
with:
token: ${{ steps.app-token.outputs.token }}
github-npm-registry-personal-access-token: ${{ secrets.GH_NPM_REGISTRY_PERSONAL_ACCESS_TOKEN }}
Comment thread
marek-saji marked this conversation as resolved.
Outdated

- name: Download semantic-release config
id: release-config
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
# Unfortunatelly when using reusable workflows, GitHub doesn’t
# expose paths to these workflows in any way, so we need to
# hardcode repo and reference and fetch the file manually 🙄
run: |
config_repo="verkstedt/actions"
config_ref="feat/create-release" # TODO DEBUG Change to v1 before merging
config_path="create-release/semantic-release.config.mjs"

release_config_mjs="$RUNNER_TEMP/$( basename "$config_path" )"
echo "path=$release_config_mjs" | tee -a "$GITHUB_OUTPUT"

gh api \
-H 'Accept: application/vnd.github.raw' \
"/repos/${config_repo}/contents/${config_path}?ref=${config_ref}" \
> "$release_config_mjs"

- name: Release
id: release
env:
# Note: This script doesn’t access steps.*.outputs and
# inputs.* directly to make it easier to copy it to separate
# file for testing
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
PACKAGE_MANAGER: ${{ steps.setup.outputs.package-manager }}
RELEASE_TYPE: ${{ inputs.release-type }}
RELEASE_CONFIG_MJS: ${{ steps.release-config.outputs.path }}
run: |
Comment thread
marek-saji marked this conversation as resolved.
echo "::group::Install semantic-release plugins"
install-plugin-pkgs () {
# shellcheck disable=SC2046
set -- $(
node --input-type=module --eval "
const { default: config } = await import(process.argv[1], { assert: { type: 'module' } });
process.stdout.write(config.plugins.map(p => p[0]).join('\\n'));
" "$RELEASE_CONFIG_MJS"
)

case "$PACKAGE_MANAGER" in
yarn)
yarn add -D "$@"
;;
npm)
npm install -D "$@"
;;
*)
echo "::error::Unsupported package manager: $PACKAGE_MANAGER"
exit 69 # EX_UNAVAILABLE
;;
esac

git restore package.json || :
git restore package-lock.json || :
git restore yarn.lock || :
}
install-plugin-pkgs
Comment thread
marek-saji marked this conversation as resolved.
Outdated
echo "::endgroup::"

echo "::group::Setup git"
# Use our GitHub app bot as author of the release commit
GIT_AUTHOR_NAME='${{ steps.app-token.outputs.app-slug }}[bot]'
GIT_AUTHOR_EMAIL='${{ steps.app-token-data.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com'
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL

# Not really semantically true, because semantic-release is
# both the author and commiter, but with this we’ll have a
# nice info in git of who triggered the release
GIT_COMMITTER_NAME="$GITHUB_ACTOR"
GIT_COMMITTER_EMAIL="$GITHUB_ACTOR_ID+$GITHUB_ACTOR@users.noreply.github.com"
export GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
echo "::endgroup::"

echo "::group::Run semantic-release"
if [ "${{ inputs.dry-run }}" = "true" ]
then
set -- "$@" --dry-run
fi
export RELEASE_TYPE
export GITHUB_STEP_SUMMARY
export GITHUB_OUTPUT # Note: It will add some outputs
npx --yes semantic-release --extends "$RELEASE_CONFIG_MJS" "$@"
echo "::endgroup::"

- name: Convert release notes to Slack’s markdown–like markup
id: slack-release-notes
env:
TEXT: ${{ steps.release.outputs.release-notes }}
run: |
if [ -n "$TEXT" ]
then
mkdir -p "$RUNNER_TEMP/slackify-markdown"
cd "$RUNNER_TEMP/slackify-markdown"
echo '{}' > package.json
npm install slackify-markdown
TEXT=$( node -e 'process.stdout.write(require("slackify-markdown")(process.argv[1]))' "$TEXT" )
fi

{
echo 'text<<TEXT_EOL'
echo "$TEXT"
echo 'TEXT_EOL'
} | tee -a "$GITHUB_OUTPUT"

- name: Notify
if: inputs.dry-run != true
uses: verkstedt/actions/notify-status@feat/create-release
with:
status: success
slack-bot-token: ${{ secrets.SLACK_BOT_TOKEN }}
slack-channel-id: ${{ vars.SLACK_CHANNEL_ID }}
text: ${{ steps.slack-release-notes.outputs.text }}
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ Template:

When a GH commit URL is included in commit message, link the commit from said comment.

### Create Release

Template:
<https://github.com/verkstedt/.github/tree/main/workflow-templates/create-release.yaml>
Comment thread
smelchior marked this conversation as resolved.

See [create-release.yaml](./.github/workflows/create-release.yaml) for details.

## Deploying new versions of actions and workflows

You might have noticed that main branch in this repository is called
Expand Down
1 change: 1 addition & 0 deletions create-release/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Files used by [`create-release` workflow](../.github/workflows/create-release.yaml).
Comment thread
marek-saji marked this conversation as resolved.
Loading