Skip to content

Expose API error metadata (#49) #8

Expose API error metadata (#49)

Expose API error metadata (#49) #8

name: Deploy on main when version increases
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Decide release version
id: version
shell: bash
run: |
set -euo pipefail
if [[ ! -f VERSION.md ]]; then
echo "Missing VERSION.md file"
exit 1
fi
LOCAL_VERSION="$(head -n1 VERSION.md | tr -d '[:space:]')"
RELEASE_BODY="$(tail -n +2 VERSION.md || true)"
if [[ ! "$LOCAL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
echo "VERSION.md first line must be valid semver. Got: $LOCAL_VERSION"
exit 1
fi
git fetch --tags origin
REMOTE_MAX="$(git ls-remote --tags --refs origin \
| awk '{print $2}' \
| sed 's#refs/tags/##' \
| sed 's/^v//' \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$' \
| sort -V \
| tail -n1 || true)"
if [[ -z "$REMOTE_MAX" ]]; then
REMOTE_MAX="0.0.0"
fi
echo "Local version: $LOCAL_VERSION"
echo "Remote version: $REMOTE_MAX"
if [[ "$LOCAL_VERSION" == "$REMOTE_MAX" ]]; then
echo "should_deploy=false" >> "$GITHUB_OUTPUT"
exit 0
fi
HIGHEST="$(printf '%s\n%s\n' "$REMOTE_MAX" "$LOCAL_VERSION" | sort -V | tail -n1)"
if [[ "$HIGHEST" != "$LOCAL_VERSION" ]]; then
echo "Local VERSION.md is not greater than remote max tag"
echo "should_deploy=false" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! printf '%s' "$RELEASE_BODY" | grep -q '[^[:space:]]'; then
echo "VERSION.md requires release notes after the first line when publishing."
echo "Example:"
echo " 3.6.1"
echo " ## What changed"
echo " - Added X"
exit 1
fi
printf '%s\n' "$RELEASE_BODY" > RELEASE_NOTES.md
echo "should_deploy=true" >> "$GITHUB_OUTPUT"
echo "tag=$LOCAL_VERSION" >> "$GITHUB_OUTPUT"
- name: Create and push tag
if: steps.version.outputs.should_deploy == 'true'
run: |
set -euo pipefail
TAG="${{ steps.version.outputs.tag }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag already exists locally: $TAG"
exit 0
fi
git tag "$TAG"
git push origin "$TAG"
- name: Create GitHub release
id: release
if: steps.version.outputs.should_deploy == 'true'
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
name: ${{ steps.version.outputs.tag }}
body_path: RELEASE_NOTES.md
- name: Notify Slack (success)
if: steps.version.outputs.should_deploy == 'true' && steps.release.outcome == 'success'
env:
SLACK_DEPLOY_WEBHOOK_URL: ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }}
VERSION: ${{ steps.version.outputs.tag }}
shell: bash
run: |
set -euo pipefail
if [ -z "${SLACK_DEPLOY_WEBHOOK_URL:-}" ]; then
echo "SLACK_DEPLOY_WEBHOOK_URL not set; skipping Slack notification"
exit 0
fi
node <<'JS'
const fs = require('node:fs');
const version = process.env.VERSION;
const releaseNotes = fs.existsSync('RELEASE_NOTES.md')
? fs.readFileSync('RELEASE_NOTES.md', 'utf8').trim()
: 'See release notes for details.';
fs.writeFileSync('/tmp/slack_payload.json', JSON.stringify({
text: `Facturapi PHP SDK ${version} released`,
blocks: [
{
type: 'header',
text: {
type: 'plain_text',
text: `PHP SDK ${version} released`,
},
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Package:* \`facturapi/facturapi-php:${version}\`` },
{ type: 'mrkdwn', text: `*Branch:* \`${process.env.GITHUB_REF_NAME}\`` },
{ type: 'mrkdwn', text: `*Commit:* \`${process.env.GITHUB_SHA}\`` },
{ type: 'mrkdwn', text: `*Actor:* \`${process.env.GITHUB_ACTOR}\`` },
],
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: [
'*Useful links*',
`• Packagist: <https://packagist.org/packages/facturapi/facturapi-php#${version}|View package>`,
`• GitHub release: <${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/releases/tag/${version}|Open release>`,
`• Workflow run: <${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}|Open run>`,
].join('\n'),
},
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*Latest changes*\n${releaseNotes}`,
},
},
],
}));
JS
curl -sS -X POST -H "Content-type: application/json" --data "@/tmp/slack_payload.json" "$SLACK_DEPLOY_WEBHOOK_URL" || true
- name: Notify Slack (failure)
if: failure() && steps.version.outputs.should_deploy == 'true'
env:
SLACK_DEPLOY_WEBHOOK_URL: ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }}
VERSION: ${{ steps.version.outputs.tag }}
shell: bash
run: |
set -euo pipefail
if [ -z "${SLACK_DEPLOY_WEBHOOK_URL:-}" ]; then
echo "SLACK_DEPLOY_WEBHOOK_URL not set; skipping Slack notification"
exit 0
fi
node <<'JS'
const fs = require('node:fs');
fs.writeFileSync('/tmp/slack_payload_failure.json', JSON.stringify({
text: `Facturapi PHP SDK ${process.env.VERSION} release failed`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: [
`*PHP SDK ${process.env.VERSION} release failed*`,
`• Workflow run: <${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}|Open run>`,
`• Commit: \`${process.env.GITHUB_SHA}\``,
].join('\n'),
},
},
],
}));
JS
curl -sS -X POST -H "Content-type: application/json" --data "@/tmp/slack_payload_failure.json" "$SLACK_DEPLOY_WEBHOOK_URL" || true
- name: Deploy skipped
if: steps.version.outputs.should_deploy != 'true'
run: |
echo "Skipping deploy: local VERSION.md is not greater than remote semver tag."