14 Commits

Author SHA1 Message Date
jmiller af82b46fe0 chore: add dlid and blockChildUninstall to package manifest [skip ci] 2026-06-04 22:02:32 +00:00
jmiller 02d8bfb089 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:58:52 +00:00
jmiller 6aebfc1953 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:41:45 +00:00
jmiller 8fb3262eb3 chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:32:55 +00:00
jmiller 03b53d937a chore: remove updates.xml [skip ci] 2026-06-04 15:27:06 +00:00
jmiller eb5513f4af chore: sync .mokogitea/workflows/pr-check.yml from moko-platform [skip ci] 2026-06-04 15:19:46 +00:00
jmiller 003e9617a0 feat(update): migrate update server URL to Gitea Pages [skip ci] 2026-06-04 14:34:14 +00:00
jmiller 01139c6fd4 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-06-04 14:24:01 +00:00
jmiller a6d843fd9b chore: sync updates.xml from development [skip ci] 2026-06-04 13:57:48 +00:00
jmiller b40482d8a5 chore: sync .mokogitea/workflows/repo-health.yml from moko-platform [skip ci] 2026-06-04 13:47:45 +00:00
jmiller 8ebbfa7aed chore: sync updates.xml from development [skip ci] 2026-06-04 13:16:03 +00:00
jmiller 83b47ce849 chore: sync updates.xml from development [skip ci] 2026-06-04 13:02:06 +00:00
jmiller d383d1fc09 chore: sync updates.xml from development [skip ci] 2026-06-04 12:41:04 +00:00
jmiller 31941e80c3 chore: sync updates.xml 01.02.00-rc from rc [skip ci] 2026-06-04 12:13:17 +00:00
5 changed files with 1507 additions and 1380 deletions
+285 -283
View File
@@ -1,283 +1,285 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release # INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/auto-release.yml.template # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00 # VERSION: 05.00.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from manifest.xml
# #
# +========================================================================+ # +========================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE | # | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+ # +========================================================================+
# | | # | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | | # | |
# | Platform-specific: | # | Platform-specific: |
# | joomla: XML manifest, updates.xml, type-prefixed packages | # | joomla: XML manifest, updates.xml, type-prefixed packages |
# | dolibarr: mod*.class.php, update.txt, dev version reset | # | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream | # | generic: README-only, no update stream |
# | | # | |
# +========================================================================+ # +========================================================================+
name: "Universal: Build & Release" name: "Universal: Build & Release"
on: on:
pull_request: pull_request:
types: [opened, closed] types: [opened, closed]
branches: branches:
- main - main
workflow_dispatch: workflow_dispatch:
inputs: inputs:
action: action:
description: 'Action to perform' description: 'Action to perform'
required: false required: false
type: choice type: choice
default: release default: release
options: options:
- release - release
- promote-rc - promote-rc
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
permissions: permissions:
contents: write contents: write
jobs: jobs:
# ── PR Opened → Rename branch to RC and build RC release ───────────────────── # ── PR Opened → Rename branch to RC and build RC release ─────────────────────
promote-rc: promote-rc:
name: Promote to RC name: Promote to RC
runs-on: release runs-on: release
if: >- if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) || (github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
- name: Setup moko-platform tools - name: Setup moko-platform tools
env: env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | run: |
if ! command -v composer &> /dev/null; then if ! command -v composer &> /dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi fi
# Always fetch latest CLI tools — never use stale cache from previous runs # Always fetch latest CLI tools — never use stale cache from previous runs
rm -rf /tmp/moko-platform-api rm -rf /tmp/moko-platform-api
git clone --depth 1 --branch main --quiet \ git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api /tmp/moko-platform-api
cd /tmp/moko-platform-api cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet composer install --no-dev --no-interaction --quiet
- name: Rename branch to rc - name: Rename branch to rc
run: | run: |
php /tmp/moko-platform-api/cli/branch_rename.php \ php /tmp/moko-platform-api/cli/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ --api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}" --pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git - name: Checkout rc and configure git
run: | run: |
git fetch origin rc git fetch origin rc
git checkout rc git checkout rc
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]" git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Publish RC release - name: Publish RC release
run: | run: |
php /tmp/moko-platform-api/cli/release_publish.php \ php /tmp/moko-platform-api/cli/release_publish.php \
--path . --stability rc --bump minor --branch rc \ --path . --stability rc --bump minor --branch rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--skip-update-stream
- name: Summary
if: always() - name: Summary
run: | if: always()
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY run: |
echo "Branch renamed to rc, minor bump, RC + lesser stream releases built, updates.xml synced" >> $GITHUB_STEP_SUMMARY echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release: # ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
name: Build & Release Pipeline release:
runs-on: release name: Build & Release Pipeline
if: >- runs-on: release
github.event.pull_request.merged == true || if: >-
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository steps:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Checkout repository
with: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
token: ${{ secrets.MOKOGITEA_TOKEN }} with:
fetch-depth: 0 token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0
- name: Configure git for bot pushes
run: | - name: Configure git for bot pushes
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech" run: |
git config --local user.name "gitea-actions[bot]" git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
- name: Check for merge conflict markers
run: | - name: Check for merge conflict markers
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) run: |
if [ -n "$CONFLICTS" ]; then CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
echo "::error::Merge conflict markers found — aborting release" if [ -n "$CONFLICTS" ]; then
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY echo "::error::Merge conflict markers found — aborting release"
echo '```' >> $GITHUB_STEP_SUMMARY echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
exit 1 echo '```' >> $GITHUB_STEP_SUMMARY
fi exit 1
echo "No conflict markers found" fi
echo "No conflict markers found"
- name: Setup moko-platform tools
env: - name: Setup moko-platform tools
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} env:
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
run: | COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
# Ensure PHP + Composer are available run: |
if ! command -v composer &> /dev/null; then # Ensure PHP + Composer are available
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 if ! command -v composer &> /dev/null; then
fi sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
# Always fetch latest CLI tools — never use stale cache from previous runs fi
rm -rf /tmp/moko-platform-api # Always fetch latest CLI tools — never use stale cache from previous runs
git clone --depth 1 --branch main --quiet \ rm -rf /tmp/moko-platform-api
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ git clone --depth 1 --branch main --quiet \
/tmp/moko-platform-api "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
cd /tmp/moko-platform-api /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
- name: "Publish stable release"
run: | - name: "Publish stable release"
php /tmp/moko-platform-api/cli/release_publish.php \ run: |
--path . --stability stable --bump minor --branch main \ php /tmp/moko-platform-api/cli/release_publish.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --path . --stability stable --bump minor --branch main \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
# -- STEP 9: Mirror to GitHub (stable only) -------------------------------- --skip-update-stream
- name: "Step 9: Mirror release to GitHub"
if: >- # -- STEP 9: Mirror to GitHub (stable only) --------------------------------
steps.version.outputs.skip != 'true' && - name: "Step 9: Mirror release to GitHub"
secrets.GH_MIRROR_TOKEN != '' if: >-
continue-on-error: true steps.version.outputs.skip != 'true' &&
run: | secrets.GH_MIRROR_TOKEN != ''
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" continue-on-error: true
RELEASE_TAG="${{ steps.version.outputs.release_tag }}" run: |
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
php /tmp/moko-platform-api/cli/release_mirror.php \ GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
--version "$VERSION" --tag "$RELEASE_TAG" \ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ php /tmp/moko-platform-api/cli/release_mirror.php \
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \ --version "$VERSION" --tag "$RELEASE_TAG" \
--branch main 2>&1 || true --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY --gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
--branch main 2>&1 || true
# -- STEP 10: Sync main branch to GitHub mirror ---------------------------- echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
- name: "Step 10: Push main to GitHub mirror"
if: >- # -- STEP 10: Sync main branch to GitHub mirror ----------------------------
steps.version.outputs.skip != 'true' && - name: "Step 10: Push main to GitHub mirror"
secrets.GH_MIRROR_TOKEN != '' if: >-
continue-on-error: true steps.version.outputs.skip != 'true' &&
run: | secrets.GH_MIRROR_TOKEN != ''
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" continue-on-error: true
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1) run: |
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2) GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \ GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
git fetch origin main --depth=1 git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
git push github origin/main:refs/heads/main --force 2>/dev/null \ git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
&& echo "main branch pushed to GitHub mirror" \ git fetch origin main --depth=1
|| echo "WARNING: GitHub mirror push failed" git push github origin/main:refs/heads/main --force 2>/dev/null \
&& echo "main branch pushed to GitHub mirror" \
- name: "Step 11: Delete rc branch and recreate dev from main" || echo "WARNING: GitHub mirror push failed"
if: steps.version.outputs.skip != 'true'
continue-on-error: true - name: "Step 11: Delete rc branch and recreate dev from main"
run: | if: steps.version.outputs.skip != 'true'
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" continue-on-error: true
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Delete rc branch (ephemeral — created by promote-rc) TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \ # Delete rc branch (ephemeral — created by promote-rc)
&& echo "Deleted rc branch" || echo "rc branch not found" curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/rc" 2>/dev/null \
# Delete dev branch && echo "Deleted rc branch" || echo "rc branch not found"
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch" # Delete dev branch
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
# Recreate dev from main (now includes version bump + changelog promotion) "${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \ # Recreate dev from main (now includes version bump + changelog promotion)
"${API_BASE}/branches" \ curl -sf -X POST -H "Authorization: token ${TOKEN}" \
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main" -H "Content-Type: application/json" \
"${API_BASE}/branches" \
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY -d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
- name: "Step 12: Create version branch from main" echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
if: steps.version.outputs.skip != 'true'
continue-on-error: true - name: "Step 12: Create version branch from main"
run: | if: steps.version.outputs.skip != 'true'
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" continue-on-error: true
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
BRANCH_NAME="version/${VERSION}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
MAIN_SHA=$(git rev-parse HEAD) VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}"
# Delete old version branch if it exists (same version re-release) MAIN_SHA=$(git rev-parse HEAD)
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
# Delete old version branch if it exists (same version re-release)
# Create version/XX.YY.ZZ from main curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
# Create version/XX.YY.ZZ from main
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Post-release: Reset dev version"
if: steps.version.outputs.skip != 'true' # -- Dolibarr post-release: Reset dev version -----------------------------
continue-on-error: true - name: "Post-release: Reset dev version"
run: | if: steps.version.outputs.skip != 'true'
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" continue-on-error: true
php /tmp/moko-platform-api/cli/version_reset_dev.php \ run: |
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
--branch dev --path . 2>&1 || true php /tmp/moko-platform-api/cli/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
# -- Summary -------------------------------------------------------------- --branch dev --path . 2>&1 || true
- name: Pipeline Summary
if: always() # -- Summary --------------------------------------------------------------
run: | - name: Pipeline Summary
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" if: always()
PLATFORM="${{ steps.platform.outputs.platform }}" run: |
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY PLATFORM="${{ steps.platform.outputs.platform }}"
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
else elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
echo "" >> $GITHUB_STEP_SUMMARY echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY else
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
fi echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi
+508 -264
View File
@@ -1,264 +1,508 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# #
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.CI # INGROUP: moko-platform.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
# PATH: /templates/workflows/universal/pr-check.yml.template # PATH: /templates/workflows/universal/pr-check.yml.template
# VERSION: 09.23.00 # VERSION: 09.23.00
# BRIEF: PR gate — branch policy + code validation before merge # BRIEF: PR gate — branch policy + code validation before merge
name: "Universal: PR Check" name: "Universal: PR Check"
on: on:
pull_request: pull_request:
types: [opened, synchronize, reopened, edited] types: [opened, synchronize, reopened, edited]
permissions: permissions:
contents: read contents: read
pull-requests: write pull-requests: write
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs: jobs:
# ── Branch Policy ────────────────────────────────────────────────────── # ── Branch Policy ──────────────────────────────────────────────────────
branch-policy: branch-policy:
name: Branch Policy name: Branch Policy
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check branch merge target - name: Check branch merge target
run: | run: |
HEAD="${{ github.head_ref }}" HEAD="${{ github.head_ref }}"
BASE="${{ github.base_ref }}" BASE="${{ github.base_ref }}"
echo "PR: ${HEAD} → ${BASE}" echo "PR: ${HEAD} → ${BASE}"
ALLOWED=true ALLOWED=true
REASON="" REASON=""
case "$HEAD" in case "$HEAD" in
feature/*|feat/*) feature/*|feat/*)
if [ "$BASE" != "dev" ]; then if [ "$BASE" != "dev" ]; then
ALLOWED=false ALLOWED=false
REASON="Feature branches must target 'dev', not '${BASE}'" REASON="Feature branches must target 'dev', not '${BASE}'"
fi fi
;; ;;
fix/*|bugfix/*) fix/*|bugfix/*)
if [ "$BASE" != "dev" ]; then if [ "$BASE" != "dev" ]; then
ALLOWED=false ALLOWED=false
REASON="Fix branches must target 'dev', not '${BASE}'" REASON="Fix branches must target 'dev', not '${BASE}'"
fi fi
;; ;;
patch/*) patch/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then
ALLOWED=false ALLOWED=false
REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'"
fi fi
;; ;;
hotfix/*) hotfix/*)
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
ALLOWED=false ALLOWED=false
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
fi fi
;; ;;
rc) rc)
if [ "$BASE" != "main" ]; then if [ "$BASE" != "main" ]; then
ALLOWED=false ALLOWED=false
REASON="RC branch can only merge into 'main', not '${BASE}'" REASON="RC branch can only merge into 'main', not '${BASE}'"
fi fi
;; ;;
dev) dev)
if [ "$BASE" != "main" ]; then if [ "$BASE" != "main" ]; then
ALLOWED=false ALLOWED=false
REASON="Dev branch can only merge into 'main', not '${BASE}'" REASON="Dev branch can only merge into 'main', not '${BASE}'"
fi fi
;; ;;
esac esac
if [ "$ALLOWED" = false ]; then if [ "$ALLOWED" = false ]; then
echo "::error::${REASON}" echo "::error::${REASON}"
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "${REASON}" >> $GITHUB_STEP_SUMMARY echo "${REASON}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
exit 1 exit 1
fi fi
echo "Branch policy: OK (${HEAD} → ${BASE})" echo "Branch policy: OK (${HEAD} → ${BASE})"
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
# ── Code Validation ──────────────────────────────────────────────────── # ── Code Validation ────────────────────────────────────────────────────
validate: validate:
name: Validate PR name: Validate PR
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Detect platform - name: Check for merge conflict markers
id: platform run: |
run: | CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
# Read platform from XML manifest (<platform> tag) or plain text fallback if [ -n "$CONFLICTS" ]; then
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1) echo "::error::Merge conflict markers found in source files"
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]') echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY
[ -z "$PLATFORM" ] && PLATFORM="generic" echo '```' >> $GITHUB_STEP_SUMMARY
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Setup PHP exit 1
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' fi
run: | echo "No conflict markers found"
if ! command -v php &> /dev/null; then
sudo apt-get update -qq - name: Detect platform
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 id: platform
fi run: |
# Read platform from XML manifest (<platform> tag) or plain text fallback
- name: PHP syntax check PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' [ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
run: | [ -z "$PLATFORM" ] && PLATFORM="generic"
ERRORS=0 echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
while IFS= read -r -d '' file; do
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then - name: Setup PHP
ERRORS=$((ERRORS + 1)) if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
fi run: |
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0) if ! command -v php &> /dev/null; then
echo "PHP lint: ${ERRORS} error(s)" sudo apt-get update -qq
[ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; } sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
fi
- name: Validate platform manifest
run: | - name: PHP syntax check
PLATFORM="${{ steps.platform.outputs.platform }}" if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
case "$PLATFORM" in run: |
joomla) ERRORS=0
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1) while IFS= read -r -d '' file; do
if [ -z "$MANIFEST" ]; then if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
echo "::warning::No Joomla manifest found (WaaS site)" ERRORS=$((ERRORS + 1))
exit 0 fi
fi done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0)
echo "Manifest: ${MANIFEST}" echo "PHP lint: ${ERRORS} error(s)"
if command -v php &> /dev/null; then [ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; }
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
fi - name: Joomla JEXEC guard check
for ELEMENT in name version description; do if: steps.platform.outputs.platform == 'joomla'
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } run: |
done ERRORS=0
echo "Joomla manifest valid" while IFS= read -r -d '' file; do
;; # Skip vendor, node_modules, and index.html stub files
dolibarr) case "$file" in ./vendor/*|./node_modules/*) continue ;; esac
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1) # Check first 10 lines for JEXEC or JPATH guard
if [ -z "$MOD_FILE" ]; then if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then
echo "::error::No mod*.class.php found" echo "::error file=${file}::Missing JEXEC guard: ${file}"
exit 1 ERRORS=$((ERRORS + 1))
fi fi
echo "Dolibarr module: ${MOD_FILE}" done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0)
;; if [ "$ERRORS" -gt 0 ]; then
*) echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard"
echo "Generic platform — no manifest validation" echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY
;; echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY
esac exit 1
fi
- name: Check update stream format echo "JEXEC guard: OK"
run: |
PLATFORM="${{ steps.platform.outputs.platform }}" - name: Joomla directory listing protection
case "$PLATFORM" in if: steps.platform.outputs.platform == 'joomla'
joomla) run: |
if [ -f "updates.xml" ]; then MISSING=0
if command -v php &> /dev/null; then SOURCE_DIR="src"
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; } [ ! -d "$SOURCE_DIR" ] && exit 0
fi while IFS= read -r dir; do
echo "updates.xml valid" if [ ! -f "${dir}/index.html" ]; then
fi echo "::warning::Missing index.html in ${dir} (directory listing protection)"
;; MISSING=$((MISSING + 1))
dolibarr) fi
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt" done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*")
;; if [ "$MISSING" -gt 0 ]; then
esac echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY
echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY
- name: Check changelog has unreleased entry fi
run: | echo "Directory protection: ${MISSING} missing (advisory)"
if [ ! -f "CHANGELOG.md" ]; then
echo "::warning::No CHANGELOG.md found" - name: Joomla script file and asset checks
exit 0 if: steps.platform.outputs.platform == 'joomla'
fi run: |
# Check for content under [Unreleased] section ERRORS=0
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
echo "::error::CHANGELOG.md missing [Unreleased] section" [ -z "$MANIFEST" ] && exit 0
exit 1 MANIFEST_DIR=$(dirname "$MANIFEST")
fi
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased # Check scriptfile exists if declared
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true) SCRIPTFILE=$(sed -n 's/.*<scriptfile>\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null)
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then if [ -n "$SCRIPTFILE" ]; then
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes." if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY echo "::error::Manifest declares <scriptfile>${SCRIPTFILE}</scriptfile> but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}"
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY ERRORS=$((ERRORS + 1))
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY else
exit 1 echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)"
fi fi
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]" fi
- name: Verify package source # Require joomla.asset.json and validate it
run: | ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1)
SOURCE_DIR="src" if [ -z "$ASSET_JSON" ]; then
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" echo "::error::joomla.asset.json not found — Joomla asset system is required"
if [ ! -d "$SOURCE_DIR" ]; then ERRORS=$((ERRORS + 1))
echo "::warning::No src/ or htdocs/ directory" else
exit 0 if command -v php &> /dev/null; then
fi php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || {
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) echo "::error::joomla.asset.json is not valid JSON"
echo "Source: ${FILE_COUNT} files" ERRORS=$((ERRORS + 1))
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } }
fi
# ── Pre-Release RC Build ───────────────────────────────────────────────── echo "joomla.asset.json: valid"
pre-release: fi
name: Build RC Package
runs-on: ubuntu-latest # Validate all XML files in src/ are well-formed
needs: [branch-policy, validate] XML_ERRORS=0
if command -v php &> /dev/null; then
steps: while IFS= read -r -d '' xmlfile; do
- name: Trigger RC pre-release if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then
env: XML_ERRORS=$((XML_ERRORS + 1))
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} fi
REPO: ${{ github.repository }} done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0)
BRANCH: ${{ github.head_ref }} fi
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} if [ "$XML_ERRORS" -gt 0 ]; then
run: | echo "::error::${XML_ERRORS} XML file(s) are malformed"
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" ERRORS=$((ERRORS + 1))
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY else
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY echo "XML well-formedness: OK"
fi
# ── Issue Reporter ──────────────────────────────────────────────────────
report-issues: [ "$ERRORS" -gt 0 ] && exit 1
name: Report Issues echo "Joomla asset checks: OK"
runs-on: ubuntu-latest
needs: [branch-policy, validate] - name: Validate platform manifest
if: >- run: |
always() && PLATFORM="${{ steps.platform.outputs.platform }}"
needs.validate.result == 'failure' case "$PLATFORM" in
joomla)
steps: MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
- name: Checkout if [ -z "$MANIFEST" ]; then
uses: actions/checkout@v4 echo "::warning::No Joomla manifest found (WaaS site)"
with: exit 0
sparse-checkout: automation/ci-issue-reporter.sh fi
sparse-checkout-cone-mode: false echo "Manifest: ${MANIFEST}"
if command -v php &> /dev/null; then
- name: "File issue for PR validation failure" php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; }
env: fi
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} for ELEMENT in name version description; do
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
run: | done
chmod +x automation/ci-issue-reporter.sh # Block legacy raw/branch update server URLs on MokoGitea
./automation/ci-issue-reporter.sh \ RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
--gate "PR Validation" \ if [ -n "$RAW_URLS" ]; then
--workflow "PR Check" \ echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
--severity error \ echo "$RAW_URLS"
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed." exit 1
fi
echo "Joomla manifest valid"
;;
dolibarr)
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
if [ -z "$MOD_FILE" ]; then
echo "::error::No mod*.class.php found"
exit 1
fi
echo "Dolibarr module: ${MOD_FILE}"
;;
*)
echo "Generic platform — no manifest validation"
;;
esac
- name: Check update stream format
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
case "$PLATFORM" in
joomla)
if [ -f "updates.xml" ]; then
if command -v php &> /dev/null; then
php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; }
fi
echo "updates.xml valid"
fi
;;
dolibarr)
[ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt"
;;
esac
- name: Validate Joomla language files
if: steps.platform.outputs.platform == 'joomla'
run: |
ERRORS=0
WARNINGS=0
# Require both en-GB and en-US language directories
LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$LANG_ROOT" ]; then
echo "No language/ directory found — skipping"
exit 0
fi
if [ ! -d "$LANG_ROOT/en-GB" ]; then
echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)"
ERRORS=$((ERRORS + 1))
fi
if [ ! -d "$LANG_ROOT/en-US" ]; then
echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)"
ERRORS=$((ERRORS + 1))
fi
# Check that en-GB and en-US have matching .ini files
if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then
for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do
[ ! -f "$GB_INI" ] && continue
US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")"
if [ ! -f "$US_INI" ]; then
echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US"
ERRORS=$((ERRORS + 1))
fi
done
for US_INI in "$LANG_ROOT/en-US"/*.ini; do
[ ! -f "$US_INI" ] && continue
GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")"
if [ ! -f "$GB_INI" ]; then
echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB"
ERRORS=$((ERRORS + 1))
fi
done
fi
# Find all .ini language files
INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null)
if [ -z "$INI_FILES" ]; then
echo "No .ini language files found"
[ "$ERRORS" -gt 0 ] && exit 1
exit 0
fi
echo "Found $(echo "$INI_FILES" | wc -l) language file(s)"
for FILE in $INI_FILES; do
FNAME=$(basename "$FILE")
LINENUM=0
SEEN_KEYS=""
while IFS= read -r line || [ -n "$line" ]; do
LINENUM=$((LINENUM + 1))
# Skip empty lines and comments
[ -z "$line" ] && continue
echo "$line" | grep -qE '^\s*;' && continue
echo "$line" | grep -qE '^\s*$' && continue
# Must match KEY="VALUE" format
if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then
echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}"
ERRORS=$((ERRORS + 1))
continue
fi
# Extract key and check for duplicates
KEY=$(echo "$line" | sed 's/=.*//')
if echo "$SEEN_KEYS" | grep -qx "$KEY"; then
echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}"
ERRORS=$((ERRORS + 1))
fi
SEEN_KEYS="${SEEN_KEYS}
${KEY}"
done < "$FILE"
echo " ${FILE}: checked ${LINENUM} lines"
done
# Cross-check en-GB vs en-US key consistency
GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1)
US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then
for GB_FILE in "$GB_DIR"/*.ini; do
[ ! -f "$GB_FILE" ] && continue
FNAME=$(basename "$GB_FILE")
US_FILE="$US_DIR/$FNAME"
[ ! -f "$US_FILE" ] && continue
GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort)
US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort)
# Keys in en-GB but not en-US
MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_US" ]; then
echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:"
echo "$MISSING_US" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
# Keys in en-US but not en-GB
MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS"))
if [ -n "$MISSING_GB" ]; then
echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:"
echo "$MISSING_GB" | while read -r k; do echo " - $k"; done
WARNINGS=$((WARNINGS + 1))
fi
done
fi
{
echo "### Language File Validation"
echo "| Metric | Count |"
echo "|---|---|"
echo "| Files checked | $(echo "$INI_FILES" | wc -l) |"
echo "| Errors | ${ERRORS} |"
echo "| Warnings | ${WARNINGS} |"
} >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "::error::Language validation failed with ${ERRORS} error(s)"
exit 1
fi
echo "Language files: OK (${WARNINGS} warning(s))"
- name: Check changelog has unreleased entry
run: |
if [ ! -f "CHANGELOG.md" ]; then
echo "::warning::No CHANGELOG.md found"
exit 0
fi
# Check for content under [Unreleased] section
if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then
echo "::error::CHANGELOG.md missing [Unreleased] section"
exit 1
fi
# Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased
UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true)
if [ "$UNRELEASED_CONTENT" -eq 0 ]; then
echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes."
echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY
echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY
echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]"
- name: Verify package source
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ ! -d "$SOURCE_DIR" ]; then
echo "::warning::No src/ or htdocs/ directory"
exit 0
fi
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
echo "Source: ${FILE_COUNT} files"
[ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; }
# ── Pre-Release RC Build ─────────────────────────────────────────────────
pre-release:
name: Build RC Package
runs-on: ubuntu-latest
needs: [branch-policy, validate]
steps:
- name: Trigger RC pre-release
env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
# ── Issue Reporter ──────────────────────────────────────────────────────
report-issues:
name: Report Issues
runs-on: ubuntu-latest
needs: [branch-policy, validate]
if: >-
always() &&
needs.validate.result == 'failure'
steps:
- name: Checkout
uses: actions/checkout@v4
with:
sparse-checkout: automation/ci-issue-reporter.sh
sparse-checkout-cone-mode: false
- name: "File issue for PR validation failure"
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x automation/ci-issue-reporter.sh
./automation/ci-issue-reporter.sh \
--gate "PR Validation" \
--workflow "PR Check" \
--severity error \
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -32,6 +32,8 @@
</languages> </languages>
<updateservers> <updateservers>
<server type="extension" name="MokoJoomBackup Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomBackup/raw/branch/main/updates.xml</server> <server type="extension" name="MokoJoomBackup Updates">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomBackup/updates.xml</server>
</updateservers> </updateservers>
<dlid prefix="dlid=" suffix=""/>
<blockChildUninstall>true</blockChildUninstall>
</extension> </extension>
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<updates>
<update>
<name>Package - MokoJoomBackup</name>
<description>Full-site backup and restore for Joomla</description>
<element>mokobackup</element>
<type>package</type>
<version>01.00.00</version>
<infourl title="Changelog">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomBackup/releases/tag/v01.00.00</infourl>
<downloadurl type="full" format="zip">https://git.mokoconsulting.tech/MokoConsulting/MokoJoomBackup/releases/download/v01.00.00/pkg_mokobackup-01.00.00.zip</downloadurl>
<sha256></sha256>
<targetplatform name="joomla" version="[45]\.\d+"/>
<php_minimum>8.1.0</php_minimum>
</update>
</updates>