chore: Sync MokoStandards v04.04 #110
76
.github/workflows/auto-assign.yml
vendored
Normal file
76
.github/workflows/auto-assign.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: GitHub.Workflow
|
||||||
|
# INGROUP: MokoStandards.Workflows.Shared
|
||||||
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
|
# PATH: /.github/workflows/auto-assign.yml
|
||||||
|
# VERSION: 04.05.11
|
||||||
|
# BRIEF: Auto-assign jmiller-moko to unassigned issues and PRs every 15 minutes
|
||||||
|
|
||||||
|
name: Auto-Assign Issues & PRs
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
schedule:
|
||||||
|
- cron: '0 */12 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-assign:
|
||||||
|
name: Assign unassigned issues and PRs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Assign unassigned issues
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
|
run: |
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
ASSIGNEE="jmiller-moko"
|
||||||
|
|
||||||
|
echo "## 🏷️ Auto-Assign Report" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
ASSIGNED_ISSUES=0
|
||||||
|
ASSIGNED_PRS=0
|
||||||
|
|
||||||
|
# Assign unassigned open issues
|
||||||
|
ISSUES=$(gh api "repos/$REPO/issues?state=open&per_page=100&assignee=none" --jq '.[].number' 2>/dev/null || true)
|
||||||
|
for NUM in $ISSUES; do
|
||||||
|
# Skip PRs (the issues endpoint returns PRs too)
|
||||||
|
IS_PR=$(gh api "repos/$REPO/issues/$NUM" --jq '.pull_request // empty' 2>/dev/null || true)
|
||||||
|
if [ -z "$IS_PR" ]; then
|
||||||
|
gh api "repos/$REPO/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
|
||||||
|
ASSIGNED_ISSUES=$((ASSIGNED_ISSUES + 1))
|
||||||
|
echo " Assigned issue #$NUM"
|
||||||
|
} || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Assign unassigned open PRs
|
||||||
|
PRS=$(gh api "repos/$REPO/pulls?state=open&per_page=100" --jq '.[] | select(.assignees | length == 0) | .number' 2>/dev/null || true)
|
||||||
|
for NUM in $PRS; do
|
||||||
|
gh api "repos/$REPO/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
|
||||||
|
ASSIGNED_PRS=$((ASSIGNED_PRS + 1))
|
||||||
|
echo " Assigned PR #$NUM"
|
||||||
|
} || true
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "| Type | Assigned |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Issues | $ASSIGNED_ISSUES |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Pull Requests | $ASSIGNED_PRS |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
if [ "$ASSIGNED_ISSUES" -eq 0 ] && [ "$ASSIGNED_PRS" -eq 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "✅ All issues and PRs already have assignees" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
176
.github/workflows/auto-dev-issue.yml
vendored
176
.github/workflows/auto-dev-issue.yml
vendored
@@ -9,14 +9,22 @@
|
|||||||
# INGROUP: MokoStandards.Automation
|
# INGROUP: MokoStandards.Automation
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
|
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
|
||||||
# VERSION: 04.05.00
|
# VERSION: 04.05.13
|
||||||
# BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed
|
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
|
||||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
|
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
|
||||||
|
|
||||||
name: Auto Dev Branch Issue
|
name: Dev/RC Branch Issue
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
# Auto-create on RC branch creation
|
||||||
create:
|
create:
|
||||||
|
# Manual trigger for dev branches
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: 'Branch name (e.g., dev/my-feature or dev/04.06)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
@@ -30,15 +38,20 @@ jobs:
|
|||||||
name: Create version tracking issue
|
name: Create version tracking issue
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: >-
|
if: >-
|
||||||
github.event.ref_type == 'branch' &&
|
(github.event_name == 'workflow_dispatch') ||
|
||||||
(startsWith(github.event.ref, 'dev/') || startsWith(github.event.ref, 'rc/'))
|
(github.event.ref_type == 'branch' && startsWith(github.event.ref, 'rc/'))
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Create tracking issue
|
- name: Create tracking issue and sub-issues
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ github.event.ref }}"
|
# For manual dispatch, use input; for auto, use event ref
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
BRANCH="${{ inputs.branch }}"
|
||||||
|
else
|
||||||
|
BRANCH="${{ github.event.ref }}"
|
||||||
|
fi
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
ACTOR="${{ github.actor }}"
|
ACTOR="${{ github.actor }}"
|
||||||
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
|
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||||
@@ -58,45 +71,122 @@ jobs:
|
|||||||
|
|
||||||
TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}"
|
TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}"
|
||||||
|
|
||||||
BODY="## ${BRANCH_TYPE} Branch Created
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|-------|-------|
|
|
||||||
| **Branch** | \`${BRANCH}\` |
|
|
||||||
| **Version** | \`${VERSION}\` |
|
|
||||||
| **Type** | ${BRANCH_TYPE} |
|
|
||||||
| **Created by** | @${ACTOR} |
|
|
||||||
| **Created at** | ${NOW} |
|
|
||||||
| **Repository** | \`${REPO}\` |
|
|
||||||
|
|
||||||
## Checklist
|
|
||||||
|
|
||||||
- [ ] Feature development complete
|
|
||||||
- [ ] Tests passing
|
|
||||||
- [ ] README.md version bumped to \`${VERSION}\`
|
|
||||||
- [ ] CHANGELOG.md updated
|
|
||||||
- [ ] PR created targeting \`main\`
|
|
||||||
- [ ] Code reviewed and approved
|
|
||||||
- [ ] Merged to \`main\`
|
|
||||||
|
|
||||||
---
|
|
||||||
*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*"
|
|
||||||
|
|
||||||
# Dedent heredoc
|
|
||||||
BODY=$(echo "$BODY" | sed 's/^ //')
|
|
||||||
|
|
||||||
# Check for existing issue with same title prefix
|
# Check for existing issue with same title prefix
|
||||||
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=5" \
|
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=10" \
|
||||||
--jq ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1)
|
--jq ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1)
|
||||||
|
|
||||||
if [ -n "$EXISTING" ]; then
|
if [ -n "$EXISTING" ]; then
|
||||||
echo "ℹ️ Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "ℹ️ Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
exit 0
|
||||||
ISSUE_URL=$(gh issue create \
|
|
||||||
--repo "$REPO" \
|
|
||||||
--title "$TITLE" \
|
|
||||||
--body "$BODY" \
|
|
||||||
--label "${LABEL_TYPE},version" \
|
|
||||||
--assignee "jmiller-moko" 2>&1)
|
|
||||||
echo "✅ Created tracking issue: ${ISSUE_URL}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Define sub-issues for the dev workflow ────────────────────────
|
||||||
|
if [[ "$BRANCH" == rc/* ]]; then
|
||||||
|
SUB_ISSUES=(
|
||||||
|
"RC Testing|Verify all features work on rc branch|type: test,release-candidate"
|
||||||
|
"Regression Testing|Run full regression suite before merge to main|type: test,release-candidate"
|
||||||
|
"Version Bump|Bump version in README.md and all headers|type: version,release-candidate"
|
||||||
|
"Changelog Update|Update CHANGELOG.md with release notes|documentation,release-candidate"
|
||||||
|
"Merge to Main|Create PR from rc branch to main|type: release,needs-review"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
SUB_ISSUES=(
|
||||||
|
"Development|Implement feature/fix on dev branch|type: feature,status: in-progress"
|
||||||
|
"Unit Testing|Write and pass unit tests|type: test,status: pending"
|
||||||
|
"Code Review|Request and complete code review|needs-review,status: pending"
|
||||||
|
"Version Bump|Bump version in README.md and all headers|type: version,status: pending"
|
||||||
|
"Changelog Update|Update CHANGELOG.md with release notes|documentation,status: pending"
|
||||||
|
"Create RC Branch|Promote dev to rc branch for final testing|type: release,status: pending"
|
||||||
|
"Merge to Main|Create PR from rc/dev to main|type: release,needs-review,status: pending"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Create sub-issues first ───────────────────────────────────────
|
||||||
|
SUB_LIST=""
|
||||||
|
SUB_NUMBERS=""
|
||||||
|
for SUB in "${SUB_ISSUES[@]}"; do
|
||||||
|
IFS='|' read -r SUB_TITLE SUB_DESC SUB_LABELS <<< "$SUB"
|
||||||
|
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
|
||||||
|
|
||||||
|
SUB_BODY=$(printf '### %s\n\n%s\n\n| Field | Value |\n|-------|-------|\n| **Parent Branch** | `%s` |\n| **Version** | `%s` |\n\n---\n*Sub-issue of the %s tracking issue for `%s`.*' \
|
||||||
|
"$SUB_TITLE" "$SUB_DESC" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$BRANCH")
|
||||||
|
|
||||||
|
SUB_URL=$(gh issue create \
|
||||||
|
--repo "$REPO" \
|
||||||
|
--title "$SUB_FULL_TITLE" \
|
||||||
|
--body "$SUB_BODY" \
|
||||||
|
--label "${SUB_LABELS}" \
|
||||||
|
--assignee "jmiller-moko" 2>&1)
|
||||||
|
|
||||||
|
SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$')
|
||||||
|
if [ -n "$SUB_NUM" ]; then
|
||||||
|
SUB_LIST="${SUB_LIST}\n- [ ] ${SUB_TITLE} (#${SUB_NUM})"
|
||||||
|
SUB_NUMBERS="${SUB_NUMBERS} #${SUB_NUM}"
|
||||||
|
fi
|
||||||
|
sleep 0.3
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Create parent tracking issue ──────────────────────────────────
|
||||||
|
PARENT_BODY=$(printf '## %s Branch Created\n\n| Field | Value |\n|-------|-------|\n| **Branch** | `%s` |\n| **Version** | `%s` |\n| **Type** | %s |\n| **Created by** | @%s |\n| **Created at** | %s |\n| **Repository** | `%s` |\n\n## Workflow Sub-Issues\n\n%b\n\n---\n*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*' \
|
||||||
|
"$BRANCH_TYPE" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$ACTOR" "$NOW" "$REPO" "$SUB_LIST")
|
||||||
|
|
||||||
|
PARENT_URL=$(gh issue create \
|
||||||
|
--repo "$REPO" \
|
||||||
|
--title "$TITLE" \
|
||||||
|
--body "$PARENT_BODY" \
|
||||||
|
--label "${LABEL_TYPE},version" \
|
||||||
|
--assignee "jmiller-moko" 2>&1)
|
||||||
|
|
||||||
|
PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$')
|
||||||
|
|
||||||
|
# ── Link sub-issues back to parent ────────────────────────────────
|
||||||
|
if [ -n "$PARENT_NUM" ]; then
|
||||||
|
for SUB in "${SUB_ISSUES[@]}"; do
|
||||||
|
IFS='|' read -r SUB_TITLE _ _ <<< "$SUB"
|
||||||
|
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
|
||||||
|
SUB_NUM=$(gh api "repos/${REPO}/issues?state=open&per_page=20" \
|
||||||
|
--jq ".[] | select(.title == \"${SUB_FULL_TITLE}\") | .number" 2>/dev/null | head -1)
|
||||||
|
if [ -n "$SUB_NUM" ]; then
|
||||||
|
gh api "repos/${REPO}/issues/${SUB_NUM}" -X PATCH \
|
||||||
|
-f body="$(gh api "repos/${REPO}/issues/${SUB_NUM}" --jq '.body' 2>/dev/null)
|
||||||
|
|
||||||
|
> **Parent Issue:** #${PARENT_NUM}" --silent 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── RC: Create or update draft release ────────────────────────────
|
||||||
|
if [[ "$BRANCH" == rc/* ]]; then
|
||||||
|
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||||
|
RELEASE_TAG="v${MAJOR}"
|
||||||
|
DRAFT_EXISTS=$(gh release view "$RELEASE_TAG" --json isDraft -q .isDraft 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -z "$DRAFT_EXISTS" ]; then
|
||||||
|
# No release exists — create draft
|
||||||
|
gh release create "$RELEASE_TAG" \
|
||||||
|
--title "v${MAJOR} (RC: ${VERSION})" \
|
||||||
|
--notes "## Release Candidate ${VERSION}\n\nRC branch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \
|
||||||
|
--draft \
|
||||||
|
--target main 2>/dev/null || true
|
||||||
|
echo "Draft release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "$DRAFT_EXISTS" = "true" ]; then
|
||||||
|
# Draft exists — update title
|
||||||
|
gh release edit "$RELEASE_TAG" \
|
||||||
|
--title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true
|
||||||
|
echo "Draft release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
# Release exists and is published — set back to draft for RC
|
||||||
|
gh release edit "$RELEASE_TAG" \
|
||||||
|
--title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true
|
||||||
|
echo "Release ${RELEASE_TAG} set to draft for RC" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Summary ───────────────────────────────────────────────────────
|
||||||
|
echo "## Dev Workflow Issues Created" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Item | Issue |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Parent** | ${PARENT_URL} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| **Sub-issues** |${SUB_NUMBERS} |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
521
.github/workflows/auto-release.yml
vendored
521
.github/workflows/auto-release.yml
vendored
@@ -6,29 +6,31 @@
|
|||||||
# DEFGROUP: GitHub.Workflow
|
# DEFGROUP: GitHub.Workflow
|
||||||
# INGROUP: MokoStandards.Release
|
# INGROUP: MokoStandards.Release
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
# PATH: /templates/workflows/shared/auto-release.yml.template
|
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
||||||
# VERSION: 04.05.00
|
# VERSION: 04.05.13
|
||||||
# BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release
|
# BRIEF: Joomla build & release — ZIP package, update.xml, SHA-256 checksum
|
||||||
#
|
#
|
||||||
# ╔════════════════════════════════════════════════════════════════════════╗
|
# +========================================================================+
|
||||||
# ║ BUILD & RELEASE PIPELINE ║
|
# | BUILD & RELEASE PIPELINE (JOOMLA) |
|
||||||
# ╠════════════════════════════════════════════════════════════════════════╣
|
# +========================================================================+
|
||||||
# ║ ║
|
# | |
|
||||||
# ║ Triggers on push to main (skips bot commits + [skip ci]): ║
|
# | Triggers on push to main (skips bot commits + [skip ci]): |
|
||||||
# ║ ║
|
# | |
|
||||||
# ║ Every push: ║
|
# | Every push: |
|
||||||
# ║ 1. Read version from README.md ║
|
# | 1. Read version from README.md |
|
||||||
# ║ 3. Set platform version (Dolibarr $this->version, Joomla <version>)║
|
# | 3. Set platform version (Joomla <version>) |
|
||||||
# ║ 4. Update [VERSION: XX.YY.ZZ] badges in markdown files ║
|
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
||||||
# ║ 5. Write update.txt / update.xml ║
|
# | 5. Write update.xml (Joomla update server XML) |
|
||||||
# ║ 6. Create git tag vXX.YY.ZZ ║
|
# | 6. Create git tag vXX.YY.ZZ |
|
||||||
# ║ 7a. Patch: update existing GitHub Release for this minor ║
|
# | 7a. Patch: update existing GitHub Release for this minor |
|
||||||
# ║ ║
|
# | 8. Build ZIP, upload asset, write SHA-256 to update.xml |
|
||||||
# ║ Minor releases only (patch == 00): ║
|
# | |
|
||||||
# ║ 2. Create/update version/XX.YY branch (patches update in-place) ║
|
# | Every version change: archives main -> version/XX.YY branch |
|
||||||
# ║ 7b. Create new GitHub Release ║
|
# | Patch 00 = development (no release). First release = patch 01. |
|
||||||
# ║ ║
|
# | First release only (patch == 01): |
|
||||||
# ╚════════════════════════════════════════════════════════════════════════╝
|
# | 7b. Create new GitHub Release |
|
||||||
|
# | |
|
||||||
|
# +========================================================================+
|
||||||
|
|
||||||
name: Build & Release
|
name: Build & Release
|
||||||
|
|
||||||
@@ -70,13 +72,13 @@ jobs:
|
|||||||
cd /tmp/mokostandards
|
cd /tmp/mokostandards
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
|
||||||
# ── STEP 1: Read version ───────────────────────────────────────────
|
# -- STEP 1: Read version -----------------------------------------------
|
||||||
- name: "Step 1: Read version from README.md"
|
- name: "Step 1: Read version from README.md"
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
|
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
|
||||||
if [ -z "$VERSION" ]; then
|
if [ -z "$VERSION" ]; then
|
||||||
echo "⏭️ No VERSION in README.md — skipping release"
|
echo "No VERSION in README.md — skipping release"
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -84,24 +86,34 @@ jobs:
|
|||||||
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
||||||
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
|
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 "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "branch=version/${MINOR}" >> "$GITHUB_OUTPUT"
|
echo "branch=version/${MINOR}" >> "$GITHUB_OUTPUT"
|
||||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||||
if [ "$PATCH" = "00" ]; then
|
if [ "$PATCH" = "00" ]; then
|
||||||
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "✅ Version: $VERSION (minor release — full pipeline)"
|
|
||||||
else
|
|
||||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||||
echo "✅ Version: $VERSION (patch — platform version + badges only)"
|
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
|
fi
|
||||||
|
|
||||||
- name: Check if already released
|
- name: Check if already released
|
||||||
if: steps.version.outputs.skip != 'true'
|
if: steps.version.outputs.skip != 'true'
|
||||||
id: check
|
id: check
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.version.outputs.tag }}"
|
TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
BRANCH="${{ steps.version.outputs.branch }}"
|
||||||
|
|
||||||
TAG_EXISTS=false
|
TAG_EXISTS=false
|
||||||
@@ -119,102 +131,109 @@ jobs:
|
|||||||
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── SANITY CHECKS ────────────────────────────────────────────────────
|
# -- SANITY CHECKS -------------------------------------------------------
|
||||||
- name: "Sanity: Platform-specific validation"
|
- name: "Sanity: Pre-release validation"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
steps.check.outputs.already_released != 'true'
|
steps.check.outputs.already_released != 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
|
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
|
|
||||||
echo "## 🔍 Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY
|
echo "## Pre-Release Sanity Checks (Joomla)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Platform: \`${PLATFORM}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $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
|
# Common checks
|
||||||
if [ ! -f "LICENSE" ]; then
|
if [ ! -f "LICENSE" ]; then
|
||||||
echo "❌ Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
||||||
ERRORS=$((ERRORS+1))
|
ERRORS=$((ERRORS+1))
|
||||||
else
|
else
|
||||||
echo "✅ LICENSE" >> $GITHUB_STEP_SUMMARY
|
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ! -d "src" ]; then
|
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
|
||||||
echo "⚠️ No src/ directory" >> $GITHUB_STEP_SUMMARY
|
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "✅ src/ directory" >> $GITHUB_STEP_SUMMARY
|
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Dolibarr-specific checks
|
# -- Joomla: manifest version drift --------
|
||||||
if [ "$PLATFORM" = "crm-module" ]; then
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
MOD_FILE=$(find src htdocs -path "*/core/modules/mod*.class.php" -print -quit 2>/dev/null)
|
if [ -n "$MANIFEST" ]; then
|
||||||
if [ -z "$MOD_FILE" ]; then
|
XML_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
|
||||||
echo "❌ No module descriptor (src/core/modules/mod*.class.php)" >> $GITHUB_STEP_SUMMARY
|
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
|
||||||
|
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
ERRORS=$((ERRORS+1))
|
ERRORS=$((ERRORS+1))
|
||||||
else
|
else
|
||||||
echo "✅ Module descriptor: \`${MOD_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Check module number
|
|
||||||
NUMERO=$(grep -oP '\$this->numero\s*=\s*\K\d+' "$MOD_FILE" 2>/dev/null || echo "0")
|
|
||||||
if [ "$NUMERO" = "0" ] || [ -z "$NUMERO" ]; then
|
|
||||||
echo "❌ Module number (\$this->numero) is 0 or not set" >> $GITHUB_STEP_SUMMARY
|
|
||||||
ERRORS=$((ERRORS+1))
|
|
||||||
else
|
|
||||||
echo "✅ Module number: ${NUMERO}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check url_last_version exists
|
|
||||||
if grep -q 'url_last_version' "$MOD_FILE" 2>/dev/null; then
|
|
||||||
echo "✅ url_last_version is set" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "⚠️ url_last_version not set — update checks won't work" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Joomla-specific checks
|
# -- Joomla: XML manifest existence --------
|
||||||
if [ "$PLATFORM" = "waas-component" ]; then
|
if [ -z "$MANIFEST" ]; then
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
echo "- No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
|
||||||
if [ -z "$MANIFEST" ]; then
|
ERRORS=$((ERRORS+1))
|
||||||
echo "❌ No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
|
else
|
||||||
ERRORS=$((ERRORS+1))
|
echo "- Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
|
||||||
echo "✅ Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# Check extension type
|
# -- Joomla: extension type check --------
|
||||||
TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null)
|
TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null)
|
||||||
echo "✅ Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
|
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
if [ "$ERRORS" -gt 0 ]; then
|
if [ "$ERRORS" -gt 0 ]; then
|
||||||
echo "**❌ ${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "**✅ All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── STEP 2: Create or update version/XX.YY branch ──────────────────
|
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
||||||
- name: "Step 2: Version branch"
|
# Always runs — every version change on main archives to version/XX.YY
|
||||||
if: >-
|
- name: "Step 2: Version archive branch"
|
||||||
steps.version.outputs.skip != 'true' &&
|
if: steps.check.outputs.already_released != 'true'
|
||||||
steps.check.outputs.already_released != 'true'
|
|
||||||
run: |
|
run: |
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
BRANCH="${{ steps.version.outputs.branch }}"
|
||||||
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
||||||
if [ "$IS_MINOR" = "true" ]; then
|
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 checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
|
||||||
git push origin "$BRANCH" --force
|
git push origin "$BRANCH" --force
|
||||||
echo "🌿 Created branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
|
||||||
git push origin HEAD:"$BRANCH" --force
|
|
||||||
echo "📝 Updated branch: ${BRANCH} (patch)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── STEP 3: Set platform version ───────────────────────────────────
|
# -- STEP 3: Set platform version ----------------------------------------
|
||||||
- name: "Step 3: Set platform version"
|
- name: "Step 3: Set platform version"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
@@ -224,7 +243,7 @@ jobs:
|
|||||||
php /tmp/mokostandards/api/cli/version_set_platform.php \
|
php /tmp/mokostandards/api/cli/version_set_platform.php \
|
||||||
--path . --version "$VERSION" --branch main
|
--path . --version "$VERSION" --branch main
|
||||||
|
|
||||||
# ── STEP 4: Update version badges ──────────────────────────────────
|
# -- STEP 4: Update version badges ----------------------------------------
|
||||||
- name: "Step 4: Update version badges"
|
- name: "Step 4: Update version badges"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
@@ -237,107 +256,100 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# ── STEP 5: Write update files (Dolibarr: update.txt / Joomla: update.xml)
|
# -- STEP 5: Write update.xml (Joomla update server) ---------------------
|
||||||
- name: "Step 5: Write update files"
|
- name: "Step 5: Write update.xml"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
steps.check.outputs.already_released != 'true'
|
steps.check.outputs.already_released != 'true'
|
||||||
run: |
|
run: |
|
||||||
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
|
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
REPO="${{ github.repository }}"
|
REPO="${{ github.repository }}"
|
||||||
|
|
||||||
if [ "$PLATFORM" = "crm-module" ]; then
|
# -- Parse extension metadata from XML manifest ----------------
|
||||||
printf '%s' "$VERSION" > update.txt
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
echo "📦 update.txt: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "Warning: No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$PLATFORM" = "waas-component" ]; then
|
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
|
||||||
# ── Parse extension metadata from XML manifest ──────────────
|
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
|
||||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
||||||
if [ -z "$MANIFEST" ]; then
|
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
||||||
echo "⚠️ No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY
|
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
||||||
else
|
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
||||||
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
|
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
||||||
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
|
|
||||||
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
|
||||||
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
|
||||||
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
|
||||||
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
|
||||||
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
|
||||||
|
|
||||||
# Derive element from manifest filename if not in XML
|
# Derive element from manifest filename if not in XML
|
||||||
if [ -z "$EXT_ELEMENT" ]; then
|
if [ -z "$EXT_ELEMENT" ]; then
|
||||||
EXT_ELEMENT=$(basename "$MANIFEST" .xml)
|
EXT_ELEMENT=$(basename "$MANIFEST" .xml)
|
||||||
fi
|
|
||||||
|
|
||||||
# Build client tag: plugins and frontend modules need <client>site</client>
|
|
||||||
CLIENT_TAG=""
|
|
||||||
if [ -n "$EXT_CLIENT" ]; then
|
|
||||||
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
|
||||||
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
|
|
||||||
CLIENT_TAG="<client>site</client>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build folder tag for plugins (required for Joomla to match the update)
|
|
||||||
FOLDER_TAG=""
|
|
||||||
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
|
|
||||||
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build targetplatform (fallback to Joomla 5+6 if not in manifest)
|
|
||||||
if [ -z "$TARGET_PLATFORM" ]; then
|
|
||||||
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build php_minimum tag
|
|
||||||
PHP_TAG=""
|
|
||||||
if [ -n "$PHP_MINIMUM" ]; then
|
|
||||||
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
|
||||||
fi
|
|
||||||
|
|
||||||
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
|
||||||
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
|
|
||||||
|
|
||||||
# ── Write update.xml (stable release) ───────────────────────
|
|
||||||
{
|
|
||||||
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
|
||||||
printf '%s\n' '<updates>'
|
|
||||||
printf '%s\n' ' <update>'
|
|
||||||
printf '%s\n' " <name>${EXT_NAME}</name>"
|
|
||||||
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
|
||||||
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
|
|
||||||
printf '%s\n' " <type>${EXT_TYPE}</type>"
|
|
||||||
printf '%s\n' " <version>${VERSION}</version>"
|
|
||||||
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
|
||||||
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
|
||||||
printf '%s\n' ' <tags>'
|
|
||||||
printf '%s\n' ' <tag>stable</tag>'
|
|
||||||
printf '%s\n' ' </tags>'
|
|
||||||
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
|
||||||
printf '%s\n' ' <downloads>'
|
|
||||||
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
|
||||||
printf '%s\n' ' </downloads>'
|
|
||||||
printf '%s\n' " ${TARGET_PLATFORM}"
|
|
||||||
[ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
|
|
||||||
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
|
||||||
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
|
||||||
printf '%s\n' ' </update>'
|
|
||||||
printf '%s\n' '</updates>'
|
|
||||||
} > update.xml
|
|
||||||
|
|
||||||
echo "📦 update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Commit all changes ─────────────────────────────────────────────
|
# Build client tag: plugins and frontend modules need <client>site</client>
|
||||||
|
CLIENT_TAG=""
|
||||||
|
if [ -n "$EXT_CLIENT" ]; then
|
||||||
|
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||||
|
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
|
||||||
|
CLIENT_TAG="<client>site</client>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build folder tag for plugins (required for Joomla to match the update)
|
||||||
|
FOLDER_TAG=""
|
||||||
|
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
|
||||||
|
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
||||||
|
if [ -z "$TARGET_PLATFORM" ]; then
|
||||||
|
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build php_minimum tag
|
||||||
|
PHP_TAG=""
|
||||||
|
if [ -n "$PHP_MINIMUM" ]; then
|
||||||
|
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
|
||||||
|
|
||||||
|
# -- Write update.xml (stable release) --------------------------
|
||||||
|
{
|
||||||
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||||
|
printf '%s\n' '<updates>'
|
||||||
|
printf '%s\n' ' <update>'
|
||||||
|
printf '%s\n' " <name>${EXT_NAME}</name>"
|
||||||
|
printf '%s\n' " <description>${EXT_NAME} update</description>"
|
||||||
|
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
|
||||||
|
printf '%s\n' " <type>${EXT_TYPE}</type>"
|
||||||
|
printf '%s\n' " <version>${VERSION}</version>"
|
||||||
|
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
|
||||||
|
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
|
||||||
|
printf '%s\n' ' <tags>'
|
||||||
|
printf '%s\n' ' <tag>stable</tag>'
|
||||||
|
printf '%s\n' ' </tags>'
|
||||||
|
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
|
||||||
|
printf '%s\n' ' <downloads>'
|
||||||
|
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
|
||||||
|
printf '%s\n' ' </downloads>'
|
||||||
|
printf '%s\n' " ${TARGET_PLATFORM}"
|
||||||
|
[ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
|
||||||
|
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
|
||||||
|
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
|
||||||
|
printf '%s\n' ' </update>'
|
||||||
|
printf '%s\n' '</updates>'
|
||||||
|
} > update.xml
|
||||||
|
|
||||||
|
echo "update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# -- Commit all changes ---------------------------------------------------
|
||||||
- name: Commit release changes
|
- name: Commit release changes
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
steps.check.outputs.already_released != 'true'
|
steps.check.outputs.already_released != 'true'
|
||||||
run: |
|
run: |
|
||||||
if git diff --quiet && git diff --cached --quiet; then
|
if git diff --quiet && git diff --cached --quiet; then
|
||||||
echo "ℹ️ No changes to commit"
|
echo "No changes to commit"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
@@ -348,18 +360,25 @@ jobs:
|
|||||||
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
||||||
git push
|
git push
|
||||||
|
|
||||||
# ── STEP 6: Create tag ─────────────────────────────────────────────
|
# -- STEP 6: Create tag ---------------------------------------------------
|
||||||
- name: "Step 6: Create git tag"
|
- name: "Step 6: Create git tag"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
steps.check.outputs.tag_exists != 'true'
|
steps.check.outputs.tag_exists != 'true' &&
|
||||||
|
steps.version.outputs.is_minor == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.version.outputs.tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
git tag "$TAG"
|
# Only create the major release tag if it doesn't exist yet
|
||||||
git push origin "$TAG"
|
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
||||||
echo "🏷️ Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
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 ──────────────────────────
|
# -- STEP 7: Create or update GitHub Release ------------------------------
|
||||||
- name: "Step 7: GitHub Release"
|
- name: "Step 7: GitHub Release"
|
||||||
if: >-
|
if: >-
|
||||||
steps.version.outputs.skip != 'true' &&
|
steps.version.outputs.skip != 'true' &&
|
||||||
@@ -368,67 +387,129 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
TAG="${{ steps.version.outputs.tag }}"
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
BRANCH="${{ steps.version.outputs.branch }}"
|
BRANCH="${{ steps.version.outputs.branch }}"
|
||||||
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
MAJOR="${{ steps.version.outputs.major }}"
|
||||||
|
|
||||||
# Derive the minor version base (XX.YY.00)
|
|
||||||
MINOR_BASE=$(echo "$VERSION" | sed 's/\.[0-9]*$/.00/')
|
|
||||||
MINOR_TAG="v${MINOR_BASE}"
|
|
||||||
|
|
||||||
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||||
echo "$NOTES" > /tmp/release_notes.md
|
echo "$NOTES" > /tmp/release_notes.md
|
||||||
|
|
||||||
if [ "$IS_MINOR" = "true" ]; then
|
# Check if the major release already exists
|
||||||
# Minor release: create new GitHub Release
|
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||||
gh release create "$TAG" \
|
|
||||||
--title "${VERSION}" \
|
if [ -z "$EXISTING" ]; then
|
||||||
|
# First release for this major
|
||||||
|
gh release create "$RELEASE_TAG" \
|
||||||
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||||
--notes-file /tmp/release_notes.md \
|
--notes-file /tmp/release_notes.md \
|
||||||
--target "$BRANCH"
|
--target "$BRANCH"
|
||||||
echo "🚀 Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
# Patch release: update the existing minor release with new tag
|
# Append version notes to existing major release
|
||||||
# Find the latest release for this minor version
|
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
||||||
EXISTING=$(gh release view "$MINOR_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
{
|
||||||
if [ -n "$EXISTING" ]; then
|
echo "$CURRENT_NOTES"
|
||||||
# Update existing release body with patch info
|
echo ""
|
||||||
CURRENT_NOTES=$(gh release view "$MINOR_TAG" --json body -q .body 2>/dev/null || true)
|
echo "---"
|
||||||
{
|
echo "### ${VERSION}"
|
||||||
echo "$CURRENT_NOTES"
|
echo ""
|
||||||
echo ""
|
cat /tmp/release_notes.md
|
||||||
echo "---"
|
} > /tmp/updated_notes.md
|
||||||
echo "### Patch ${VERSION}"
|
|
||||||
echo ""
|
|
||||||
cat /tmp/release_notes.md
|
|
||||||
} > /tmp/updated_notes.md
|
|
||||||
|
|
||||||
gh release edit "$MINOR_TAG" \
|
gh release edit "$RELEASE_TAG" \
|
||||||
--title "${MINOR_BASE} (latest: ${VERSION})" \
|
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||||
--notes-file /tmp/updated_notes.md
|
--notes-file /tmp/updated_notes.md
|
||||||
echo "📝 Release updated: ${MINOR_BASE} → patch ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
|
||||||
# No existing minor release found — create one for this patch
|
|
||||||
gh release create "$TAG" \
|
|
||||||
--title "${VERSION}" \
|
|
||||||
--notes-file /tmp/release_notes.md
|
|
||||||
echo "🚀 Release created: ${VERSION} (no minor release found)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Summary ────────────────────────────────────────────────────────
|
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
||||||
|
# Every patch builds an install-ready ZIP and uploads it to the minor release.
|
||||||
|
# Result: one Release per minor version with a ZIP for each patch.
|
||||||
|
- name: "Step 8: Build Joomla package and update checksum"
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
|
||||||
|
# All ZIPs upload to the major release tag (vXX)
|
||||||
|
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
|
||||||
|
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find extension element name from manifest
|
||||||
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||||
|
[ -z "$MANIFEST" ] && exit 0
|
||||||
|
|
||||||
|
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
|
||||||
|
PACKAGE_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
|
||||||
|
# -- Build install-ready ZIP from src/ ----------------------------
|
||||||
|
SOURCE_DIR="src"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||||
|
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; }
|
||||||
|
|
||||||
|
cd "$SOURCE_DIR"
|
||||||
|
zip -r "/tmp/${PACKAGE_NAME}" .
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
FILESIZE=$(stat -c%s "/tmp/${PACKAGE_NAME}" 2>/dev/null || stat -f%z "/tmp/${PACKAGE_NAME}" 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
# -- Calculate SHA-256 -------------------------------------------
|
||||||
|
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||||
|
|
||||||
|
# -- Upload ZIP to the minor release tag -------------------------
|
||||||
|
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || {
|
||||||
|
echo "Could not upload with --clobber, retrying..."
|
||||||
|
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# -- Update update.xml with SHA-256 for latest patch -------------
|
||||||
|
if [ -f "update.xml" ]; then
|
||||||
|
if grep -q '<sha256>' update.xml; then
|
||||||
|
sed -i "s|<sha256>.*</sha256>|<sha256>sha256:${SHA256}</sha256>|" update.xml
|
||||||
|
else
|
||||||
|
sed -i "s|</downloads>|</downloads>\n <sha256>sha256:${SHA256}</sha256>|" update.xml
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Also update the download URL to point to this patch's ZIP
|
||||||
|
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||||
|
sed -i "s|<downloadurl[^>]*>[^<]*</downloadurl>|<downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>|" update.xml
|
||||||
|
|
||||||
|
git add update.xml
|
||||||
|
git commit -m "chore(release): SHA-256 + download URL for ${VERSION} [skip ci]" \
|
||||||
|
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" || true
|
||||||
|
git push || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "### Joomla Package" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Package | \`${PACKAGE_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Size | ${FILESIZE} bytes |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| SHA-256 | \`${SHA256}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Release | \`${RELEASE_TAG}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Download | [${PACKAGE_NAME}](https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# -- Summary --------------------------------------------------------------
|
||||||
- name: Pipeline Summary
|
- name: Pipeline Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.version.outputs.version }}"
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||||
echo "## ⏭️ Release Skipped" >> $GITHUB_STEP_SUMMARY
|
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||||
echo "## ℹ️ Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "## ✅ Build & Release Complete" >> $GITHUB_STEP_SUMMARY
|
echo "## Build & Release Complete (Joomla)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|||||||
101
.github/workflows/changelog-validation.yml
vendored
Normal file
101
.github/workflows/changelog-validation.yml
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# This file is part of a Moko Consulting project.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: GitHub.Workflow.Template
|
||||||
|
# INGROUP: MokoStandards.CI
|
||||||
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
|
# PATH: /templates/workflows/shared/changelog-validation.yml.template
|
||||||
|
# VERSION: 04.05.13
|
||||||
|
# BRIEF: Validates CHANGELOG.md format and version consistency
|
||||||
|
# NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos.
|
||||||
|
|
||||||
|
name: Changelog Validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-changelog:
|
||||||
|
name: Validate CHANGELOG.md
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
|
||||||
|
- name: Check CHANGELOG.md exists
|
||||||
|
run: |
|
||||||
|
echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ ! -f "CHANGELOG.md" ]; then
|
||||||
|
echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Check VERSION header matches README.md
|
||||||
|
run: |
|
||||||
|
# Extract version from README.md FILE INFORMATION block
|
||||||
|
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
|
||||||
|
if [ -z "$README_VERSION" ]; then
|
||||||
|
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check that CHANGELOG.md has a matching version header
|
||||||
|
CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' CHANGELOG.md | head -1)
|
||||||
|
if [ -z "$CHANGELOG_VERSION" ]; then
|
||||||
|
echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then
|
||||||
|
echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: Validate conventional changelog format
|
||||||
|
run: |
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format
|
||||||
|
while IFS= read -r LINE; do
|
||||||
|
if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]{2}\.[0-9]{2}\.[0-9]{2}\]\s*-\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
|
||||||
|
echo "Malformed version header: \`${LINE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo " Expected format: \`## [XX.YY.ZZ] - YYYY-MM-DD\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done < <(grep -P '^\#\#\s*\[' CHANGELOG.md)
|
||||||
|
|
||||||
|
ENTRY_COUNT=$(grep -cP '^\#\#\s*\[' CHANGELOG.md || echo "0")
|
||||||
|
if [ "$ENTRY_COUNT" -eq 0 ]; then
|
||||||
|
echo "No version entries found in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
391
.github/workflows/ci-joomla.yml
vendored
Normal file
391
.github/workflows/ci-joomla.yml
vendored
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# This file is part of a Moko Consulting project.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: GitHub.Workflow.Template
|
||||||
|
# INGROUP: MokoStandards.CI
|
||||||
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
|
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
|
||||||
|
# VERSION: 04.05.13
|
||||||
|
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
|
||||||
|
# NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos.
|
||||||
|
|
||||||
|
name: Joomla Extension CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
|
- version/**
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-and-validate:
|
||||||
|
name: Lint & Validate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
extensions: mbstring, xml, zip, gd, curl, json, simplexml
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Clone MokoStandards
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 --branch version/04.05 --quiet \
|
||||||
|
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
||||||
|
/tmp/mokostandards
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install \
|
||||||
|
--no-interaction \
|
||||||
|
--prefer-dist \
|
||||||
|
--optimize-autoloader
|
||||||
|
else
|
||||||
|
echo "No composer.json found — skipping dependency install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHP syntax check
|
||||||
|
run: |
|
||||||
|
ERRORS=0
|
||||||
|
for DIR in src/ htdocs/; do
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
FOUND=1
|
||||||
|
while IFS= read -r -d '' FILE; do
|
||||||
|
OUTPUT=$(php -l "$FILE" 2>&1)
|
||||||
|
if echo "$OUTPUT" | grep -q "Parse error"; then
|
||||||
|
echo "::error file=${FILE}::${OUTPUT}"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$DIR" -name "*.php" -print0)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: XML manifest validation
|
||||||
|
run: |
|
||||||
|
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Find the extension manifest (XML with <extension tag)
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Validate well-formed XML
|
||||||
|
php -r "
|
||||||
|
\$xml = @simplexml_load_file('$MANIFEST');
|
||||||
|
if (\$xml === false) {
|
||||||
|
echo 'INVALID';
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
echo 'VALID';
|
||||||
|
" > /tmp/xml_result 2>&1
|
||||||
|
XML_RESULT=$(cat /tmp/xml_result)
|
||||||
|
if [ "$XML_RESULT" != "VALID" ]; then
|
||||||
|
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check required tags: name, version, author, namespace (Joomla 5+)
|
||||||
|
for TAG in name version author namespace; do
|
||||||
|
if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then
|
||||||
|
echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check language files referenced in manifest
|
||||||
|
run: |
|
||||||
|
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$MANIFEST" ]; then
|
||||||
|
# Extract language file references from manifest
|
||||||
|
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
|
||||||
|
if [ -z "$LANG_FILES" ]; then
|
||||||
|
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
while IFS= read -r LANG_FILE; do
|
||||||
|
LANG_FILE=$(echo "$LANG_FILE" | xargs)
|
||||||
|
if [ -z "$LANG_FILE" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Check in common locations
|
||||||
|
FOUND=0
|
||||||
|
for BASE in "." "src" "htdocs"; do
|
||||||
|
if [ -f "${BASE}/${LANG_FILE}" ]; then
|
||||||
|
FOUND=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$FOUND" -eq 0 ]; then
|
||||||
|
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
done <<< "$LANG_FILES"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${ERRORS}" -gt 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check index.html files in directories
|
||||||
|
run: |
|
||||||
|
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
|
||||||
|
MISSING=0
|
||||||
|
CHECKED=0
|
||||||
|
|
||||||
|
for DIR in src/ htdocs/; do
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
while IFS= read -r -d '' SUBDIR; do
|
||||||
|
CHECKED=$((CHECKED + 1))
|
||||||
|
if [ ! -f "${SUBDIR}/index.html" ]; then
|
||||||
|
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
MISSING=$((MISSING + 1))
|
||||||
|
fi
|
||||||
|
done < <(find "$DIR" -type d -print0)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${CHECKED}" -eq 0 ]; then
|
||||||
|
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${MISSING}" -gt 0 ]; then
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
release-readiness:
|
||||||
|
name: Release Readiness Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
|
||||||
|
- name: Validate release readiness
|
||||||
|
run: |
|
||||||
|
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=0
|
||||||
|
|
||||||
|
# Extract version from README.md
|
||||||
|
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
|
||||||
|
if [ -z "$README_VERSION" ]; then
|
||||||
|
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the extension manifest
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# Check <version> matches README VERSION
|
||||||
|
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
||||||
|
if [ -z "$MANIFEST_VERSION" ]; then
|
||||||
|
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
|
||||||
|
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check extension type, element, client attributes
|
||||||
|
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
|
||||||
|
if [ -z "$EXT_TYPE" ]; then
|
||||||
|
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
else
|
||||||
|
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Element check (component/module/plugin name)
|
||||||
|
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
|
||||||
|
if [ "$HAS_ELEMENT" -eq 0 ]; then
|
||||||
|
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Client attribute for site/admin modules and plugins
|
||||||
|
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
|
||||||
|
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
|
||||||
|
if [ "$HAS_CLIENT" -eq 0 ]; then
|
||||||
|
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check update.xml exists
|
||||||
|
if [ -f "update.xml" ] || [ -f "updates.xml" ]; then
|
||||||
|
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "No update.xml found." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check CHANGELOG.md exists
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ $ERRORS -gt 0 ]; then
|
||||||
|
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Tests (PHP ${{ matrix.php }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint-and-validate
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: ['8.2', '8.3']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
|
||||||
|
- name: Setup PHP ${{ matrix.php }}
|
||||||
|
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php }}
|
||||||
|
extensions: mbstring, xml, zip, gd, curl, json, simplexml
|
||||||
|
tools: composer:v2
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
env:
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install \
|
||||||
|
--no-interaction \
|
||||||
|
--prefer-dist \
|
||||||
|
--optimize-autoloader
|
||||||
|
else
|
||||||
|
echo "No composer.json found — skipping dependency install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
||||||
|
vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log
|
||||||
|
EXIT=${PIPESTATUS[0]}
|
||||||
|
if [ $EXIT -eq 0 ]; then
|
||||||
|
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
exit $EXIT
|
||||||
|
else
|
||||||
|
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
# INGROUP: MokoStandards.Firewall
|
# INGROUP: MokoStandards.Firewall
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
|
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
|
||||||
# VERSION: 04.05.00
|
# VERSION: 04.05.13
|
||||||
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server
|
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server
|
||||||
# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules.
|
# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules.
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/repository-cleanup.yml
vendored
2
.github/workflows/repository-cleanup.yml
vendored
@@ -9,7 +9,7 @@
|
|||||||
# INGROUP: MokoStandards.Maintenance
|
# INGROUP: MokoStandards.Maintenance
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
|
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
|
||||||
# VERSION: 04.05.00
|
# VERSION: 04.05.13
|
||||||
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
|
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
|
||||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos.
|
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos.
|
||||||
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
|
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
|
||||||
|
|||||||
2
.github/workflows/sync-version-on-merge.yml
vendored
2
.github/workflows/sync-version-on-merge.yml
vendored
@@ -9,7 +9,7 @@
|
|||||||
# INGROUP: MokoStandards.Automation
|
# INGROUP: MokoStandards.Automation
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
|
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
|
||||||
# VERSION: 04.05.00
|
# VERSION: 04.05.13
|
||||||
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
|
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
|
||||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos.
|
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos.
|
||||||
# README.md is the single source of truth for the repository version.
|
# README.md is the single source of truth for the repository version.
|
||||||
|
|||||||
236
.github/workflows/update-server.yml
vendored
Normal file
236
.github/workflows/update-server.yml
vendored
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: GitHub.Workflow
|
||||||
|
# INGROUP: MokoStandards.Joomla
|
||||||
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
|
# PATH: /templates/workflows/joomla/update-server.yml.template
|
||||||
|
# VERSION: 04.05.13
|
||||||
|
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
|
||||||
|
#
|
||||||
|
# Writes update.xml with multiple <update> entries:
|
||||||
|
# - <tag>stable</tag> on push to main (from auto-release)
|
||||||
|
# - <tag>rc</tag> on push to rc/**
|
||||||
|
# - <tag>development</tag> on push to dev/**
|
||||||
|
#
|
||||||
|
# Joomla filters by user's "Minimum Stability" setting.
|
||||||
|
|
||||||
|
name: Update Joomla Update Server XML Feed
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'dev/**'
|
||||||
|
- 'rc/**'
|
||||||
|
paths:
|
||||||
|
- 'src/**'
|
||||||
|
- 'htdocs/**'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
stability:
|
||||||
|
description: 'Stability tag (development, rc, stable)'
|
||||||
|
required: true
|
||||||
|
default: 'development'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- development
|
||||||
|
- rc
|
||||||
|
- stable
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-xml:
|
||||||
|
name: Update update.xml
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup MokoStandards tools
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 --branch version/04.05 --quiet \
|
||||||
|
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
||||||
|
/tmp/mokostandards 2>/dev/null || true
|
||||||
|
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||||
|
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Generate update.xml entry
|
||||||
|
run: |
|
||||||
|
BRANCH="${{ github.ref_name }}"
|
||||||
|
REPO="${{ github.repository }}"
|
||||||
|
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
# Determine stability from branch or input
|
||||||
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
|
STABILITY="${{ inputs.stability }}"
|
||||||
|
elif [[ "$BRANCH" == rc/* ]]; then
|
||||||
|
STABILITY="rc"
|
||||||
|
elif [[ "$BRANCH" == dev/* ]]; then
|
||||||
|
STABILITY="development"
|
||||||
|
else
|
||||||
|
STABILITY="stable"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse manifest
|
||||||
|
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "No Joomla manifest found — skipping"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
|
||||||
|
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
|
||||||
|
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
|
||||||
|
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
||||||
|
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
|
||||||
|
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
||||||
|
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
||||||
|
|
||||||
|
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml)
|
||||||
|
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||||
|
|
||||||
|
CLIENT_TAG=""
|
||||||
|
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
|
||||||
|
[ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="<client>site</client>"
|
||||||
|
|
||||||
|
FOLDER_TAG=""
|
||||||
|
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
||||||
|
|
||||||
|
PHP_TAG=""
|
||||||
|
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
|
||||||
|
|
||||||
|
# Version suffix for non-stable
|
||||||
|
DISPLAY_VERSION="$VERSION"
|
||||||
|
[ "$STABILITY" = "rc" ] && DISPLAY_VERSION="${VERSION}-rc"
|
||||||
|
[ "$STABILITY" = "development" ] && DISPLAY_VERSION="${VERSION}-dev"
|
||||||
|
|
||||||
|
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||||
|
RELEASE_TAG="v${MAJOR}"
|
||||||
|
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
INFO_URL="https://github.com/${REPO}"
|
||||||
|
|
||||||
|
# ── Build the new entry ───────────────────────────────────────
|
||||||
|
NEW_ENTRY=$(cat <<XMLEOF
|
||||||
|
<update>
|
||||||
|
<name>${EXT_NAME}</name>
|
||||||
|
<description>${EXT_NAME} (${STABILITY})</description>
|
||||||
|
<element>${EXT_ELEMENT}</element>
|
||||||
|
<type>${EXT_TYPE}</type>
|
||||||
|
<version>${DISPLAY_VERSION}</version>
|
||||||
|
$([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}")
|
||||||
|
$([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}")
|
||||||
|
<tags>
|
||||||
|
<tag>${STABILITY}</tag>
|
||||||
|
</tags>
|
||||||
|
<infourl title="${EXT_NAME}">${INFO_URL}</infourl>
|
||||||
|
<downloads>
|
||||||
|
<downloadurl type="full" format="zip">${DOWNLOAD_URL}</downloadurl>
|
||||||
|
</downloads>
|
||||||
|
${TARGET_PLATFORM}
|
||||||
|
$([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}")
|
||||||
|
<maintainer>Moko Consulting</maintainer>
|
||||||
|
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
|
||||||
|
</update>
|
||||||
|
XMLEOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# ── Merge into update.xml ─────────────────────────────────────
|
||||||
|
if [ ! -f "update.xml" ]; then
|
||||||
|
# Create fresh
|
||||||
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > update.xml
|
||||||
|
printf '%s\n' '<updates>' >> update.xml
|
||||||
|
echo "$NEW_ENTRY" >> update.xml
|
||||||
|
printf '%s\n' '</updates>' >> update.xml
|
||||||
|
else
|
||||||
|
# Remove existing entry for this stability, add new one
|
||||||
|
# Use python for reliable XML manipulation
|
||||||
|
python3 -c "
|
||||||
|
import re, sys
|
||||||
|
|
||||||
|
with open('update.xml', 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Remove existing entry with this stability tag
|
||||||
|
pattern = r' <update>.*?<tag>${STABILITY}</tag>.*?</update>\n?'
|
||||||
|
content = re.sub(pattern, '', content, flags=re.DOTALL)
|
||||||
|
|
||||||
|
# Insert new entry before </updates>
|
||||||
|
new_entry = '''${NEW_ENTRY}'''
|
||||||
|
content = content.replace('</updates>', new_entry + '\n</updates>')
|
||||||
|
|
||||||
|
# Clean up empty lines
|
||||||
|
content = re.sub(r'\n{3,}', '\n\n', content)
|
||||||
|
|
||||||
|
with open('update.xml', 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
" 2>/dev/null || {
|
||||||
|
# Fallback: just rewrite the whole file if python fails
|
||||||
|
# Keep existing stable entry if present
|
||||||
|
STABLE_ENTRY=""
|
||||||
|
if [ "$STABILITY" != "stable" ] && grep -q '<tag>stable</tag>' update.xml; then
|
||||||
|
STABLE_ENTRY=$(sed -n '/<update>/,/<\/update>/{ /<tag>stable<\/tag>/,/<\/update>/p; /<update>/,/<tag>stable<\/tag>/p }' update.xml | sort -u)
|
||||||
|
fi
|
||||||
|
RC_ENTRY=""
|
||||||
|
if [ "$STABILITY" != "rc" ] && grep -q '<tag>rc</tag>' update.xml; then
|
||||||
|
RC_ENTRY=$(python3 -c "
|
||||||
|
import re
|
||||||
|
with open('update.xml') as f: c = f.read()
|
||||||
|
m = re.search(r'(<update>.*?<tag>rc</tag>.*?</update>)', c, re.DOTALL)
|
||||||
|
if m: print(m.group(1))
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
DEV_ENTRY=""
|
||||||
|
if [ "$STABILITY" != "development" ] && grep -q '<tag>development</tag>' update.xml; then
|
||||||
|
DEV_ENTRY=$(python3 -c "
|
||||||
|
import re
|
||||||
|
with open('update.xml') as f: c = f.read()
|
||||||
|
m = re.search(r'(<update>.*?<tag>development</tag>.*?</update>)', c, re.DOTALL)
|
||||||
|
if m: print(m.group(1))
|
||||||
|
" 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||||
|
printf '%s\n' '<updates>'
|
||||||
|
[ -n "$STABLE_ENTRY" ] && echo "$STABLE_ENTRY"
|
||||||
|
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
|
||||||
|
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
|
||||||
|
echo "$NEW_ENTRY"
|
||||||
|
printf '%s\n' '</updates>'
|
||||||
|
} > update.xml
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git add update.xml
|
||||||
|
git diff --cached --quiet || {
|
||||||
|
git commit -m "chore: update update.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \
|
||||||
|
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
||||||
|
git push
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
Reference in New Issue
Block a user