Public Access
f42791464a
All templates now clone MokoStandards-API.git (this repo) instead of MokoStandards.git for CLI tools. Path mappings: - /tmp/mokostandards → /tmp/mokostandards-api - /api/cli/ → /cli/ - /api/deploy/ → /deploy/ - /api/maintenance/ → /maintenance/ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
340 lines
15 KiB
Plaintext
340 lines
15 KiB
Plaintext
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# FILE INFORMATION
|
|
# DEFGROUP: Gitea.Workflow
|
|
# INGROUP: MokoStandards.Release
|
|
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
|
# PATH: /templates/workflows/shared/auto-release.yml.template
|
|
# VERSION: 04.06.00
|
|
# BRIEF: Generic build & release pipeline — version branch, platform version, badges, tag, release
|
|
#
|
|
# +========================================================================+
|
|
# | BUILD & RELEASE PIPELINE |
|
|
# +========================================================================+
|
|
# | |
|
|
# | Triggers on push to main (skips bot commits + [skip ci]): |
|
|
# | |
|
|
# | Every push: |
|
|
# | 1. Read version from README.md |
|
|
# | 3. Set platform version |
|
|
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
|
# | 6. Create git tag vXX.YY.ZZ |
|
|
# | 7a. Patch: update existing GitHub Release for this minor |
|
|
# | |
|
|
# | Every version change: archives main -> version/XX.YY branch |
|
|
# | Patch 00 = development (no release). First release = patch 01. |
|
|
# | First release only (patch == 01): |
|
|
# | 7b. Create new GitHub Release |
|
|
# | |
|
|
# +========================================================================+
|
|
|
|
name: Build & Release
|
|
|
|
on:
|
|
pull_request:
|
|
types: [closed]
|
|
branches:
|
|
- main
|
|
paths:
|
|
- 'src/**'
|
|
- 'htdocs/**'
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
release:
|
|
name: Build & Release Pipeline
|
|
runs-on: ubuntu-latest
|
|
if: >-
|
|
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
with:
|
|
token: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }}
|
|
fetch-depth: 0
|
|
|
|
- name: Setup MokoStandards tools
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }}
|
|
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }}
|
|
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
|
run: |
|
|
git clone --depth 1 --branch {{standards_branch}} --quiet \
|
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
|
/tmp/mokostandards-api
|
|
cd /tmp/mokostandards-api
|
|
composer install --no-dev --no-interaction --quiet
|
|
|
|
# -- STEP 1: Read version -----------------------------------------------
|
|
- name: "Step 1: Read version from README.md"
|
|
id: version
|
|
run: |
|
|
VERSION=$(php /tmp/mokostandards-api/cli/version_read.php --path . 2>/dev/null)
|
|
if [ -z "$VERSION" ]; then
|
|
echo "No VERSION in README.md — skipping release"
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
# Derive major.minor for branch naming (patches update existing branch)
|
|
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
|
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
|
|
|
|
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
|
MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}')
|
|
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
|
|
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
|
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
|
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
|
if [ "$PATCH" = "00" ]; then
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
|
echo "Version: $VERSION (patch 00 = development — skipping release)"
|
|
else
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
if [ "$PATCH" = "01" ]; then
|
|
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
|
echo "Version: $VERSION (first release — full pipeline)"
|
|
else
|
|
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
|
echo "Version: $VERSION (patch — platform version + badges only)"
|
|
fi
|
|
fi
|
|
|
|
- name: Check if already released
|
|
if: steps.version.outputs.skip != 'true'
|
|
id: check
|
|
run: |
|
|
TAG="${{ steps.version.outputs.release_tag }}"
|
|
BRANCH="${{ steps.version.outputs.branch }}"
|
|
|
|
TAG_EXISTS=false
|
|
BRANCH_EXISTS=false
|
|
|
|
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
|
|
git ls-remote --heads origin "$BRANCH" 2>/dev/null | grep -q "$BRANCH" && BRANCH_EXISTS=true
|
|
|
|
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
|
|
echo "branch_exists=$BRANCH_EXISTS" >> "$GITHUB_OUTPUT"
|
|
|
|
if [ "$TAG_EXISTS" = "true" ] && [ "$BRANCH_EXISTS" = "true" ]; then
|
|
echo "already_released=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
# -- SANITY CHECKS -------------------------------------------------------
|
|
- name: "Sanity: Pre-release validation"
|
|
if: >-
|
|
steps.version.outputs.skip != 'true' &&
|
|
steps.check.outputs.already_released != 'true'
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
ERRORS=0
|
|
|
|
echo "## Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# -- Version drift check (must pass before release) --------
|
|
README_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' README.md 2>/dev/null | head -1)
|
|
if [ "$README_VER" != "$VERSION" ]; then
|
|
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
ERRORS=$((ERRORS+1))
|
|
else
|
|
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
# Check CHANGELOG version matches
|
|
CL_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' CHANGELOG.md 2>/dev/null | head -1)
|
|
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
|
|
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
ERRORS=$((ERRORS+1))
|
|
fi
|
|
|
|
# Check composer.json version if present
|
|
if [ -f "composer.json" ]; then
|
|
COMP_VER=$(grep -oP '"version"\s*:\s*"\K[^"]+' composer.json 2>/dev/null | head -1)
|
|
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
|
|
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
|
ERRORS=$((ERRORS+1))
|
|
fi
|
|
fi
|
|
|
|
# Common checks
|
|
if [ ! -f "LICENSE" ]; then
|
|
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
|
ERRORS=$((ERRORS+1))
|
|
else
|
|
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
|
|
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
if [ "$ERRORS" -gt 0 ]; then
|
|
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
|
# Always runs — every version change on main archives to version/XX.YY
|
|
- name: "Step 2: Version archive branch"
|
|
if: steps.check.outputs.already_released != 'true'
|
|
run: |
|
|
BRANCH="${{ steps.version.outputs.branch }}"
|
|
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
|
PATCH="${{ steps.version.outputs.version }}"
|
|
PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
|
|
|
|
# Check if branch exists
|
|
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
|
git push origin HEAD:"$BRANCH" --force
|
|
echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
|
|
git push origin "$BRANCH" --force
|
|
echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
# -- STEP 3: Set platform version ----------------------------------------
|
|
- name: "Step 3: Set platform version"
|
|
if: >-
|
|
steps.version.outputs.skip != 'true' &&
|
|
steps.check.outputs.already_released != 'true'
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
php /tmp/mokostandards-api/cli/version_set_platform.php \
|
|
--path . --version "$VERSION" --branch main
|
|
|
|
# -- STEP 4: Update version badges ----------------------------------------
|
|
- name: "Step 4: Update version badges"
|
|
if: >-
|
|
steps.version.outputs.skip != 'true' &&
|
|
steps.check.outputs.already_released != 'true'
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
|
|
if grep -q '\[VERSION:' "$f" 2>/dev/null; then
|
|
sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
|
|
fi
|
|
done
|
|
|
|
# -- Commit all changes ---------------------------------------------------
|
|
- name: Commit release changes
|
|
if: >-
|
|
steps.version.outputs.skip != 'true' &&
|
|
steps.check.outputs.already_released != 'true'
|
|
run: |
|
|
if git diff --quiet && git diff --cached --quiet; then
|
|
echo "No changes to commit"
|
|
exit 0
|
|
fi
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
|
git config --local user.name "github-actions[bot]"
|
|
git add -A
|
|
git commit -m "chore(release): build ${VERSION} [skip ci]" \
|
|
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
|
git push
|
|
|
|
# -- STEP 6: Create tag ---------------------------------------------------
|
|
- name: "Step 6: Create git tag"
|
|
if: >-
|
|
steps.version.outputs.skip != 'true' &&
|
|
steps.check.outputs.tag_exists != 'true' &&
|
|
steps.version.outputs.is_minor == 'true'
|
|
run: |
|
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
# Only create the major release tag if it doesn't exist yet
|
|
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
|
git tag "$RELEASE_TAG"
|
|
git push origin "$RELEASE_TAG"
|
|
echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# -- STEP 7: Create or update GitHub Release ------------------------------
|
|
- name: "Step 7: GitHub Release"
|
|
if: >-
|
|
steps.version.outputs.skip != 'true' &&
|
|
steps.check.outputs.tag_exists != 'true'
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GA_TOKEN || secrets.GH_TOKEN || github.token }}
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
BRANCH="${{ steps.version.outputs.branch }}"
|
|
MAJOR="${{ steps.version.outputs.major }}"
|
|
|
|
NOTES=$(php /tmp/mokostandards-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
|
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
|
echo "$NOTES" > /tmp/release_notes.md
|
|
|
|
# Check if the major release already exists
|
|
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
|
|
|
if [ -z "$EXISTING" ]; then
|
|
# First release for this major: create GitHub Release
|
|
gh release create "$RELEASE_TAG" \
|
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
--notes-file /tmp/release_notes.md \
|
|
--target "$BRANCH"
|
|
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
# Update existing major release with new version info
|
|
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
|
{
|
|
echo "$CURRENT_NOTES"
|
|
echo ""
|
|
echo "---"
|
|
echo "### ${VERSION}"
|
|
echo ""
|
|
cat /tmp/release_notes.md
|
|
} > /tmp/updated_notes.md
|
|
|
|
gh release edit "$RELEASE_TAG" \
|
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
|
--notes-file /tmp/updated_notes.md
|
|
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
|
fi
|
|
|
|
# -- Summary --------------------------------------------------------------
|
|
- name: Pipeline Summary
|
|
if: always()
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
|
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
|
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
|
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
|
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
|
else
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "## Build & Release Complete" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
|
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
echo "| Release | [View](https://github.com/${{ github.repository }}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
|
fi
|