Public Access
Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4637dab159 | |||
| b885f12ece | |||
| e99881ad65 | |||
| fd3c847fb1 | |||
| 424fe658ba | |||
| 0ff87f3631 | |||
| 3164f21ba9 | |||
| 713cb61efc | |||
| fa342a278b | |||
| d91baf996e | |||
| 2100a3bec7 | |||
| 8d772df3e7 | |||
| 3972e4fb84 | |||
| 86b8449eba | |||
| 601451867d | |||
| 09ffcdce49 | |||
| a49e0d7b2a | |||
| bc1a111775 | |||
| 98c41c3a53 | |||
| 52ebcde1ff | |||
| ded17c34b8 | |||
| 1b79de20a9 | |||
| bba1cbc845 | |||
| 3f956f5661 | |||
| 9036d20898 | |||
| 7c1edcf813 | |||
| fec5a48b4a | |||
| 468b962aaa | |||
| cc66a79fea | |||
| 660ae5bed4 | |||
| 5ffaf95d20 | |||
| ddfb479bc2 | |||
| 13e2729124 | |||
| 05ee59d75e | |||
| c4bb08073b | |||
| b32556fdef | |||
| c57a7e4b96 | |||
| 5846ccc3e8 | |||
| 6ba0208571 | |||
| 759b04e088 | |||
| 4dfa2d9722 | |||
| 590ad7337a | |||
| c607966e83 | |||
| 97f2226288 | |||
| b1f2c391cb | |||
| 7e4d88eea5 | |||
| 9fdd6c5cf9 | |||
| 113af457d9 | |||
| abd67f6c43 | |||
| d3dce5d24e | |||
| c9592d8e78 | |||
| 6bf6251705 | |||
| 520acd4fee | |||
| a6c4fe0688 | |||
| 3c420284e3 | |||
| 66543c14d3 | |||
| d9908e264a | |||
| 9206f716e1 | |||
| 84633e22e5 | |||
| 510d1248ab | |||
| 8e30fa54b3 | |||
| 1aaec7d8ff | |||
| 5efdaf6c05 | |||
| 448e11568b | |||
| dce0fe0994 | |||
| 2dc115327b | |||
| 4715dba7fc | |||
| 5ab1782715 | |||
| 08db79da17 | |||
| c94564cec6 | |||
| ac52a697e5 | |||
| 57b458b79a | |||
| 2a84b38194 | |||
| 9d9b78b4e9 | |||
| 092543db1c | |||
| 2286514c59 | |||
| 6993f30513 | |||
| 0b30cc5a6d | |||
| 7b0c239dd2 | |||
| cf278cf914 | |||
| fdeacc73c5 | |||
| 117bba7765 | |||
| 65a11a19ac | |||
| fae2eab41b | |||
| f91b05287a | |||
| 43fd64a15b | |||
| 15074fb0ae | |||
| 415b5403be | |||
| 84fdb39d20 | |||
| 44b9e991b5 | |||
| 9b647b2540 | |||
| cedf10bdff | |||
| 8422292b46 | |||
| b1f3487a43 |
@@ -124,7 +124,8 @@ jobs:
|
||||
"rule_name": "main",
|
||||
"enable_push": true,
|
||||
"enable_push_whitelist": true,
|
||||
"push_whitelist_usernames": ["jmiller"],
|
||||
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||
"push_whitelist_actions_user": true,
|
||||
"enable_force_push": false,
|
||||
"enable_force_push_allowlist": false,
|
||||
"force_push_allowlist_usernames": [],
|
||||
@@ -140,10 +141,12 @@ jobs:
|
||||
"rule_name": "dev",
|
||||
"enable_push": true,
|
||||
"enable_push_whitelist": true,
|
||||
"push_whitelist_usernames": ["jmiller"],
|
||||
"enable_force_push": false,
|
||||
"enable_force_push_allowlist": false,
|
||||
"force_push_allowlist_usernames": [],
|
||||
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||
"push_whitelist_actions_user": true,
|
||||
"enable_force_push": true,
|
||||
"enable_force_push_allowlist": true,
|
||||
"force_push_allowlist_usernames": ["jmiller", "moko-deploy"],
|
||||
"force_push_allowlist_actions_user": true,
|
||||
"enable_merge_whitelist": false,
|
||||
"required_approvals": 0,
|
||||
"block_on_rejected_reviews": false,
|
||||
@@ -154,10 +157,12 @@ jobs:
|
||||
"rule_name": "rc",
|
||||
"enable_push": true,
|
||||
"enable_push_whitelist": true,
|
||||
"push_whitelist_usernames": ["jmiller"],
|
||||
"enable_force_push": false,
|
||||
"enable_force_push_allowlist": false,
|
||||
"force_push_allowlist_usernames": [],
|
||||
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||
"push_whitelist_actions_user": true,
|
||||
"enable_force_push": true,
|
||||
"enable_force_push_allowlist": true,
|
||||
"force_push_allowlist_usernames": ["jmiller", "moko-deploy"],
|
||||
"force_push_allowlist_actions_user": true,
|
||||
"enable_merge_whitelist": false,
|
||||
"required_approvals": 0,
|
||||
"block_on_rejected_reviews": false,
|
||||
@@ -168,7 +173,8 @@ jobs:
|
||||
"rule_name": "beta",
|
||||
"enable_push": true,
|
||||
"enable_push_whitelist": true,
|
||||
"push_whitelist_usernames": ["jmiller"],
|
||||
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||
"push_whitelist_actions_user": true,
|
||||
"enable_force_push": false,
|
||||
"enable_force_push_allowlist": false,
|
||||
"force_push_allowlist_usernames": [],
|
||||
@@ -182,7 +188,8 @@ jobs:
|
||||
"rule_name": "alpha",
|
||||
"enable_push": true,
|
||||
"enable_push_whitelist": true,
|
||||
"push_whitelist_usernames": ["jmiller"],
|
||||
"push_whitelist_usernames": ["jmiller", "moko-deploy"],
|
||||
"push_whitelist_actions_user": true,
|
||||
"enable_force_push": false,
|
||||
"enable_force_push_allowlist": false,
|
||||
"force_push_allowlist_usernames": [],
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup mokocli tools
|
||||
run: |
|
||||
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
|
||||
fi
|
||||
if [ -d "/opt/mokocli/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||
/tmp/mokocli
|
||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup mokocli tools
|
||||
run: |
|
||||
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
|
||||
fi
|
||||
if [ -d "/opt/mokocli/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||
/tmp/mokocli
|
||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
@@ -1,467 +1,477 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||
#
|
||||
# +=======================================================================+
|
||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||
# +=======================================================================+
|
||||
# | |
|
||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||
# | |
|
||||
# | Platform-specific: |
|
||||
# | joomla: XML manifest, type-prefixed packages |
|
||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||
# | generic: README-only, no update stream |
|
||||
# | |
|
||||
# +=======================================================================+
|
||||
|
||||
name: "Universal: Build & Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '.mokogitea/workflows/**'
|
||||
- '*.md'
|
||||
- 'wiki/**'
|
||||
- '.editorconfig'
|
||||
- '.gitignore'
|
||||
- '.gitattributes'
|
||||
- '.gitmessage'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action to perform'
|
||||
required: false
|
||||
type: choice
|
||||
default: release
|
||||
options:
|
||||
- release
|
||||
- promote-rc
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
|
||||
promote-rc:
|
||||
name: Promote to RC
|
||||
runs-on: release
|
||||
if: >-
|
||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokocli
|
||||
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; 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
|
||||
fi
|
||||
rm -rf /tmp/mokocli
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||
cd /tmp/mokocli
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Rename branch to rc
|
||||
run: |
|
||||
php ${MOKO_CLI}/branch_rename.php \
|
||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--pr "${{ github.event.pull_request.number }}"
|
||||
|
||||
- name: Checkout rc and configure git
|
||||
run: |
|
||||
git fetch origin rc
|
||||
git checkout rc
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
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: Publish RC release
|
||||
run: |
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability rc --bump minor --branch rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: Update RC release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
NOTES=""
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
fi
|
||||
[ -z "$NOTES" ] && NOTES="Release candidate"
|
||||
|
||||
# Find the RC release and update its body
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/releases/tags/release-candidate" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "RC release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
|
||||
release:
|
||||
name: Build & Release Pipeline
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Configure git for bot pushes
|
||||
run: |
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
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: |
|
||||
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)
|
||||
if [ -n "$CONFLICTS" ]; then
|
||||
echo "::error::Merge conflict markers found — aborting release"
|
||||
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "No conflict markers found"
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||
run: |
|
||||
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokocli
|
||||
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; 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
|
||||
fi
|
||||
rm -rf /tmp/mokocli
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||
cd /tmp/mokocli
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: "Detect platform"
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
|
||||
|
||||
- name: "Determine version bump level"
|
||||
id: bump
|
||||
run: |
|
||||
# Fix/patch branches: version was already bumped by pre-release, just strip suffix
|
||||
# Feature/dev branches: bump minor for the new stable release
|
||||
HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||
case "$HEAD_REF" in
|
||||
fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;;
|
||||
*) BUMP="minor" ;;
|
||||
esac
|
||||
echo "level=${BUMP}" >> "$GITHUB_OUTPUT"
|
||||
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})"
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
BUMP_FLAG=""
|
||||
if [ "${{ steps.bump.outputs.level }}" != "none" ]; then
|
||||
BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}"
|
||||
fi
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability stable ${BUMP_FLAG} --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: "Read published version"
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [[ "$PLATFORM" == joomla* ]]; then
|
||||
echo "tag=stable" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "branch=main" >> "$GITHUB_OUTPUT"
|
||||
echo "Published version: ${VERSION}"
|
||||
|
||||
- name: "Create semver tag for non-Joomla repos"
|
||||
id: semver
|
||||
if: |
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
SEMVER_TAG="v${VERSION}"
|
||||
|
||||
echo "Creating semver tag: ${SEMVER_TAG}"
|
||||
|
||||
# Create the git tag via API
|
||||
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
|
||||
-X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/tags" \
|
||||
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "Created semver tag: ${SEMVER_TAG}"
|
||||
elif [ "$HTTP_CODE" = "409" ]; then
|
||||
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
|
||||
else
|
||||
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
|
||||
fi
|
||||
|
||||
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Update release notes and promote changelog
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Get the stable release info (version and ID)
|
||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
|
||||
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
|
||||
VERSION=$(python3 -c "
|
||||
import json, sys, re
|
||||
r = json.load(sys.stdin)
|
||||
name = r.get('name', '')
|
||||
m = re.search(r'(\d+\.\d+\.\d+)', name)
|
||||
print(m.group(1) if m else '')
|
||||
" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
NOTES=""
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
fi
|
||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||
|
||||
# Update release body via API
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
|
||||
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
python3 -c "
|
||||
import sys
|
||||
version, date = sys.argv[1], sys.argv[2]
|
||||
content = open('CHANGELOG.md').read()
|
||||
old = '## [Unreleased]'
|
||||
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
|
||||
content = content.replace(old, new, 1)
|
||||
open('CHANGELOG.md', 'w').write(content)
|
||||
" "$VERSION" "$DATE"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
|
||||
git push origin main || true
|
||||
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
|
||||
fi
|
||||
|
||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||
- name: "Step 9: Mirror release to GitHub"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_mirror.php \
|
||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||
--branch main 2>&1 || true
|
||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||
- name: "Step 10: Push main to GitHub mirror"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||
git fetch origin main --depth=1
|
||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||
&& echo "main branch pushed to GitHub mirror" \
|
||||
|| echo "WARNING: GitHub mirror push failed"
|
||||
|
||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Delete rc branch (ephemeral — created by promote-rc)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||
|
||||
# Delete dev branch
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||
|
||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/branches" \
|
||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||
|
||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Step 12: Create version branch from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
BRANCH_NAME="version/${VERSION}"
|
||||
MAIN_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# Delete old version branch if it exists (same version re-release)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||
|
||||
# Create version/XX.YY.ZZ from main
|
||||
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'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/version_reset_dev.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||
--branch dev --path . 2>&1 || true
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
|
||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||
#
|
||||
# +=======================================================================+
|
||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||
# +=======================================================================+
|
||||
# | |
|
||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||
# | |
|
||||
# | Platform-specific: |
|
||||
# | joomla: XML manifest, type-prefixed packages |
|
||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||
# | generic: README-only, no update stream |
|
||||
# | |
|
||||
# +=======================================================================+
|
||||
|
||||
name: "Universal: Build & Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, closed]
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '.mokogitea/workflows/**'
|
||||
- '*.md'
|
||||
- 'wiki/**'
|
||||
- '.editorconfig'
|
||||
- '.gitignore'
|
||||
- '.gitattributes'
|
||||
- '.gitmessage'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
action:
|
||||
description: 'Action to perform'
|
||||
required: false
|
||||
type: choice
|
||||
default: release
|
||||
options:
|
||||
- release
|
||||
- promote-rc
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
|
||||
promote-rc:
|
||||
name: Promote to RC
|
||||
runs-on: release
|
||||
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||
if: >-
|
||||
!startsWith(github.event.repository.name, 'Template-') &&
|
||||
(
|
||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||
)
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokocli
|
||||
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; 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
|
||||
fi
|
||||
rm -rf /tmp/mokocli
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||
cd /tmp/mokocli
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Rename branch to rc
|
||||
run: |
|
||||
php ${MOKO_CLI}/branch_rename.php \
|
||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--pr "${{ github.event.pull_request.number }}"
|
||||
|
||||
- name: Checkout rc and configure git
|
||||
run: |
|
||||
git fetch origin rc
|
||||
git checkout rc
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
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: Publish RC release
|
||||
run: |
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability rc --bump minor --branch rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: Update RC release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
NOTES=""
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
fi
|
||||
[ -z "$NOTES" ] && NOTES="Release candidate"
|
||||
|
||||
# Find the RC release and update its body
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/releases/tags/release-candidate" \
|
||||
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "RC release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
|
||||
release:
|
||||
name: Build & Release Pipeline
|
||||
runs-on: release
|
||||
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||
if: >-
|
||||
!startsWith(github.event.repository.name, 'Template-') &&
|
||||
(
|
||||
github.event.pull_request.merged == true ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||
)
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
submodules: recursive
|
||||
|
||||
- name: Configure git for bot pushes
|
||||
run: |
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
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: |
|
||||
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)
|
||||
if [ -n "$CONFLICTS" ]; then
|
||||
echo "::error::Merge conflict markers found — aborting release"
|
||||
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
echo "No conflict markers found"
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||
run: |
|
||||
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokocli
|
||||
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||
else
|
||||
echo Falling back to fresh clone
|
||||
if ! command -v composer > /dev/null 2>&1; 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
|
||||
fi
|
||||
rm -rf /tmp/mokocli
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||
cd /tmp/mokocli
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: "Detect platform"
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
|
||||
|
||||
- name: "Determine version bump level"
|
||||
id: bump
|
||||
run: |
|
||||
# Fix/patch branches: version was already bumped by pre-release, just strip suffix
|
||||
# Feature/dev branches: bump minor for the new stable release
|
||||
HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||
case "$HEAD_REF" in
|
||||
fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;;
|
||||
*) BUMP="minor" ;;
|
||||
esac
|
||||
echo "level=${BUMP}" >> "$GITHUB_OUTPUT"
|
||||
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})"
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
BUMP_FLAG=""
|
||||
if [ "${{ steps.bump.outputs.level }}" != "none" ]; then
|
||||
BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}"
|
||||
fi
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability stable ${BUMP_FLAG} --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: "Read published version"
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [[ "$PLATFORM" == joomla* ]]; then
|
||||
echo "tag=stable" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
echo "branch=main" >> "$GITHUB_OUTPUT"
|
||||
echo "Published version: ${VERSION}"
|
||||
|
||||
- name: "Create semver tag for non-Joomla repos"
|
||||
id: semver
|
||||
if: |
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
SEMVER_TAG="v${VERSION}"
|
||||
|
||||
echo "Creating semver tag: ${SEMVER_TAG}"
|
||||
|
||||
# Create the git tag via API
|
||||
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
|
||||
-X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/tags" \
|
||||
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
|
||||
|
||||
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "Created semver tag: ${SEMVER_TAG}"
|
||||
elif [ "$HTTP_CODE" = "409" ]; then
|
||||
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
|
||||
else
|
||||
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
|
||||
fi
|
||||
|
||||
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Update release notes and promote changelog
|
||||
run: |
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Get the stable release info (version and ID)
|
||||
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
|
||||
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
|
||||
VERSION=$(python3 -c "
|
||||
import json, sys, re
|
||||
r = json.load(sys.stdin)
|
||||
name = r.get('name', '')
|
||||
m = re.search(r'(\d+\.\d+\.\d+)', name)
|
||||
print(m.group(1) if m else '')
|
||||
" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
NOTES=""
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
fi
|
||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||
|
||||
# Update release body via API
|
||||
if [ -n "$RELEASE_ID" ]; then
|
||||
python3 -c "
|
||||
import json, urllib.request
|
||||
body = open('/dev/stdin').read()
|
||||
payload = json.dumps({'body': body}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/releases/${RELEASE_ID}',
|
||||
data=payload, method='PATCH',
|
||||
headers={
|
||||
'Authorization': 'token ${TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
|
||||
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
python3 -c "
|
||||
import sys
|
||||
version, date = sys.argv[1], sys.argv[2]
|
||||
content = open('CHANGELOG.md').read()
|
||||
old = '## [Unreleased]'
|
||||
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
|
||||
content = content.replace(old, new, 1)
|
||||
open('CHANGELOG.md', 'w').write(content)
|
||||
" "$VERSION" "$DATE"
|
||||
git add CHANGELOG.md
|
||||
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
|
||||
git push origin main || true
|
||||
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
|
||||
fi
|
||||
|
||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||
- name: "Step 9: Mirror release to GitHub"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_mirror.php \
|
||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||
--branch main 2>&1 || true
|
||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||
- name: "Step 10: Push main to GitHub mirror"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
secrets.GH_MIRROR_TOKEN != ''
|
||||
continue-on-error: true
|
||||
run: |
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||
git fetch origin main --depth=1
|
||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||
&& echo "main branch pushed to GitHub mirror" \
|
||||
|| echo "WARNING: GitHub mirror push failed"
|
||||
|
||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Delete rc branch (ephemeral — created by promote-rc)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||
|
||||
# Delete dev branch
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||
|
||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API_BASE}/branches" \
|
||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||
|
||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: "Step 12: Create version branch from main"
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
BRANCH_NAME="version/${VERSION}"
|
||||
MAIN_SHA=$(git rev-parse HEAD)
|
||||
|
||||
# Delete old version branch if it exists (same version re-release)
|
||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||
|
||||
# Create version/XX.YY.ZZ from main
|
||||
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'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/version_reset_dev.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||
--branch dev --path . 2>&1 || true
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
@@ -33,7 +33,8 @@ jobs:
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
||||
# URL-encode the branch name's slashes (no PHP dependency on the runner)
|
||||
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
|
||||
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
|
||||
@@ -6,13 +6,19 @@
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||
# PATH: /.gitea/workflows/ci-generic.yml
|
||||
# PATH: /.mokogitea/workflows/ci-generic.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
|
||||
|
||||
name: "Generic: Project CI"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev/**
|
||||
- rc/**
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
@@ -26,6 +32,8 @@ jobs:
|
||||
lint:
|
||||
name: Lint & Validate
|
||||
runs-on: ubuntu-latest
|
||||
# Skip on template repos (Template-*) — they hold placeholder scaffolding, not buildable source.
|
||||
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -124,6 +132,9 @@ jobs:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint
|
||||
# Run only when lint succeeded; always() forces evaluation so a skipped
|
||||
# lint (e.g. template repos) skips this job cleanly instead of hanging.
|
||||
if: ${{ always() && needs.lint.result == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
|
||||
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
||||
|
||||
name: "Universal: CI Issue Reporter"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
gate:
|
||||
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
||||
required: true
|
||||
type: string
|
||||
details:
|
||||
description: "Human-readable failure description"
|
||||
required: true
|
||||
type: string
|
||||
severity:
|
||||
description: "error or warning"
|
||||
required: false
|
||||
type: string
|
||||
default: "error"
|
||||
workflow:
|
||||
description: "Workflow name for the issue title"
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
MOKOGITEA_TOKEN:
|
||||
required: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
report:
|
||||
name: "Report: ${{ inputs.gate }}"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone MokoCLI
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
|
||||
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
||||
|
||||
- name: Report CI failure
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
||||
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
||||
--gate "${{ inputs.gate }}" \
|
||||
--details "${{ inputs.details }}" \
|
||||
--severity "${{ inputs.severity }}" \
|
||||
--workflow "${{ inputs.workflow }}"
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
|
||||
- name: "PHPCS (PSR-12)"
|
||||
run: |
|
||||
vendor/bin/phpcs --standard=phpcs.xml --report=summary --warning-severity=0 lib/ validate/ automation/ 2>&1 || {
|
||||
vendor/bin/phpcs --standard=phpcs.xml --report=summary --warning-severity=0 lib/ validate/ 2>&1 || {
|
||||
echo "::error::PHPCS found coding standard violations"
|
||||
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Coding standard violations detected. Run \`composer phpcs\` locally." >> $GITHUB_STEP_SUMMARY
|
||||
@@ -453,8 +453,8 @@ jobs:
|
||||
needs.governance.result == 'failure' ||
|
||||
needs.templates.result == 'failure')
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
chmod +x automation/ci-issue-reporter.sh
|
||||
REPORTER="./automation/ci-issue-reporter.sh"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/cleanup.yml
|
||||
# PATH: /.mokogitea/workflows/cleanup.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
@@ -33,17 +33,17 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Delete merged branches
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Merged Branch Cleanup ==="
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
|
||||
# List branches via API
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||
|
||||
DELETED=0
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Check if branch is merged into main
|
||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||
echo " Deleting merged branch: ${BRANCH}"
|
||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
fi
|
||||
@@ -66,20 +66,20 @@ jobs:
|
||||
|
||||
- name: Clean old workflow runs
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Workflow Run Cleanup ==="
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Get old completed runs
|
||||
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/actions/runs?status=completed&limit=50" | \
|
||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||
|
||||
DELETED=0
|
||||
for RUN_ID in $RUNS; do
|
||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
done
|
||||
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Verify Gitea registry
|
||||
run: |
|
||||
echo "Gitea Composer registry auto-publishes from tags."
|
||||
echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer"
|
||||
echo "Package available at: ${MOKOGITEA_URL}/api/packages/MokoConsulting/composer"
|
||||
echo "Install: composer require mokoconsulting/mokocli"
|
||||
|
||||
# Packagist — notify of new version (points to GitHub mirror which Packagist can access)
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Deploy
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
||||
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
||||
# VERSION: 04.07.00
|
||||
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
||||
|
||||
name: "Universal: Deploy to Dev (Manual)"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
clear_remote:
|
||||
description: 'Delete all remote files before uploading'
|
||||
required: false
|
||||
default: 'false'
|
||||
type: boolean
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: SFTP Deploy to Dev
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
php -v && composer --version
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||
/tmp/mokostandards-api 2>/dev/null || true
|
||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
||||
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Check FTP configuration
|
||||
id: check
|
||||
env:
|
||||
HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
||||
PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
run: |
|
||||
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
||||
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
||||
|
||||
REMOTE="${PATH_VAR%/}"
|
||||
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
[ -z "$PORT" ] && PORT="22"
|
||||
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Deploy via SFTP
|
||||
if: steps.check.outputs.skip != 'true'
|
||||
env:
|
||||
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
run: |
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
||||
> /tmp/sftp-config.json
|
||||
|
||||
if [ -n "$SFTP_KEY" ]; then
|
||||
echo "$SFTP_KEY" > /tmp/deploy_key
|
||||
chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
||||
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
||||
|
||||
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
||||
else
|
||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
||||
fi
|
||||
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
||||
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Automation
|
||||
# VERSION: 09.40.00
|
||||
# VERSION: 09.42.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
@@ -19,7 +19,7 @@ permissions:
|
||||
issues: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
create-branch:
|
||||
@@ -28,8 +28,8 @@ jobs:
|
||||
steps:
|
||||
- name: Create branch and comment
|
||||
run: |
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "Created branch: ${BRANCH}"
|
||||
|
||||
# Comment on issue with branch link
|
||||
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
||||
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
|
||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||
|
||||
curl -sf -X POST \
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Notifications
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/notify.yml
|
||||
# PATH: /.mokogitea/workflows/notify.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
||||
|
||||
|
||||
+528
-534
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# VERSION: 05.02.00
|
||||
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
@@ -48,9 +48,13 @@ jobs:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
||||
runs-on: release
|
||||
# Skip on template repos (Template-*) — they scaffold other repos and do not release.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'push'
|
||||
!startsWith(github.event.repository.name, 'Template-') &&
|
||||
(
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event_name == 'push'
|
||||
)
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -59,6 +63,11 @@ jobs:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
ref: ${{ github.ref_name }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Update submodules to main
|
||||
run: |
|
||||
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
|
||||
@@ -29,12 +29,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Rename branch
|
||||
env:
|
||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
REPO: ${{ github.repository }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
set -euo pipefail
|
||||
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||
fi
|
||||
SUFFIX="${BRANCH#rc/}"
|
||||
DEV_BRANCH="dev/${SUFFIX}"
|
||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
|
||||
|
||||
# Create dev/ branch from rc/ branch
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||
@@ -42,25 +50,22 @@ jobs:
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||
"${API}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "201" ]; then
|
||||
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
|
||||
exit 1
|
||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||
fi
|
||||
|
||||
# Delete rc/ branch
|
||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
||||
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "204" ]; then
|
||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
|
||||
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,7 @@ permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Sync Workflows to Repos
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '.mokogitea/workflows/**'
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout mokocli
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: MokoConsulting/mokocli
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: https://git.mokoconsulting.tech/MokoConsulting/.mokogitea/raw/branch/main/actions/setup-php@v1
|
||||
with:
|
||||
php-version: '8.1'
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-dev --no-interaction
|
||||
|
||||
- name: Sync workflows to generic repos
|
||||
run: php automation/bulk_sync.php --platform generic --org MokoConsulting --workflows-only --auto-merge --token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
name: "Universal: Workflow Sync Trigger"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
@@ -26,8 +27,9 @@ jobs:
|
||||
name: Sync workflows to live repos
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == true &&
|
||||
!contains(github.event.pull_request.title, '[skip sync]')
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true &&
|
||||
!contains(github.event.pull_request.title, '[skip sync]'))
|
||||
|
||||
steps:
|
||||
- name: Determine platform from repo name
|
||||
@@ -49,8 +51,14 @@ jobs:
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||
|
||||
- name: Install PHP
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
+10
-5
@@ -12,12 +12,17 @@ BRIEF: Release changelog
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [09.40.00] --- 2026-06-25
|
||||
## [09.42.00] --- 2026-07-04
|
||||
|
||||
## [09.39.00] --- 2026-06-25
|
||||
## [09.41.00] --- 2026-07-04
|
||||
|
||||
## [09.39.00] --- 2026-06-25
|
||||
## [09.41.00] --- 2026-07-04
|
||||
|
||||
## [09.39.00] --- 2026-06-23
|
||||
### Fixed
|
||||
- RecoverySuggestion.php: remove stray shebang and wrap an over-length line to satisfy PSR-12 (Gate 1 phpcs)
|
||||
- phpstan-baseline.neon: baseline pre-existing PHPStan level-6 findings across `cli/`, `lib/`, and `validate/` so Gate 1 static analysis passes (type-annotation debt tracked, not silently ignored)
|
||||
- client_provision.php: rename private `printSummary()` (and its dispatch entry) to `printProvisioningSummary()` to stop it colliding with `CliFramework::printSummary()` — a non-baselineable PHPStan override/covariance error
|
||||
|
||||
## [09.39.00] --- 2026-06-23
|
||||
## [09.41.00] --- 2026-07-04
|
||||
|
||||
## [09.41.00] --- 2026-07-04
|
||||
|
||||
@@ -6,7 +6,7 @@ DEFGROUP: MokoPlatform.Root
|
||||
INGROUP: MokoPlatform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
PATH: /README.md
|
||||
VERSION: 09.40.00
|
||||
VERSION: 09.42.00
|
||||
BRIEF: Project overview and documentation
|
||||
-->
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/branch_rename.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old)
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/bulk_workflow_push.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Push a workflow file to all governed repos via the Gitea Contents API
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/bulk_workflow_trigger.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Trigger a workflow across multiple repos at once
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Automation.CI
|
||||
# INGROUP: mokocli.CLI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCLI
|
||||
# PATH: /cli/ci_issue_reporter.sh
|
||||
# VERSION: 10.00.00
|
||||
# BRIEF: Creates or updates a Gitea issue when a CI gate fails.
|
||||
# Deduplicates by searching open issues with the "ci-auto" label
|
||||
# whose title matches the gate. If a matching issue exists, a comment
|
||||
# is appended instead of opening a duplicate.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Defaults ────────────────────────────────────────────────────────────────
|
||||
MOKOGITEA_URL="${MOKOGITEA_URL:-https://git.mokoconsulting.tech}"
|
||||
MOKOGITEA_TOKEN="${MOKOGITEA_TOKEN:-}"
|
||||
REPO="${GITHUB_REPOSITORY:-}"
|
||||
RUN_URL="${GITHUB_SERVER_URL:-${MOKOGITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
|
||||
LABEL_NAME="ci-auto"
|
||||
LABEL_COLOR="#e11d48"
|
||||
|
||||
GATE=""
|
||||
DETAILS=""
|
||||
SEVERITY="error"
|
||||
WORKFLOW=""
|
||||
|
||||
# ── Parse arguments ─────────────────────────────────────────────────────────
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: ci_issue_reporter.sh --gate NAME --details TEXT [OPTIONS]
|
||||
|
||||
Required:
|
||||
--gate CI gate name (e.g. "Code Quality", "Self-Health")
|
||||
--details Human-readable failure description
|
||||
|
||||
Optional:
|
||||
--severity "error" (default) or "warning"
|
||||
--workflow Workflow name for the issue title
|
||||
--repo owner/repo (default: \$GITHUB_REPOSITORY)
|
||||
--run-url URL to the CI run (auto-detected from env)
|
||||
--token Gitea API token (default: \$MOKOGITEA_TOKEN)
|
||||
--url Gitea base URL (default: \$MOKOGITEA_URL)
|
||||
EOF
|
||||
exit 1
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--gate) GATE="$2"; shift 2 ;;
|
||||
--details) DETAILS="$2"; shift 2 ;;
|
||||
--severity) SEVERITY="$2"; shift 2 ;;
|
||||
--workflow) WORKFLOW="$2"; shift 2 ;;
|
||||
--repo) REPO="$2"; shift 2 ;;
|
||||
--run-url) RUN_URL="$2"; shift 2 ;;
|
||||
--token) MOKOGITEA_TOKEN="$2"; shift 2 ;;
|
||||
--url) MOKOGITEA_URL="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
*) echo "Unknown option: $1"; usage ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
|
||||
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
|
||||
[[ -z "$MOKOGITEA_TOKEN" ]] && { echo "ERROR: MOKOGITEA_TOKEN not set"; exit 1; }
|
||||
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
|
||||
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${REPO}"
|
||||
|
||||
# ── Build title ─────────────────────────────────────────────────────────────
|
||||
if [[ -n "$WORKFLOW" ]]; then
|
||||
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
|
||||
else
|
||||
TITLE="[CI] ${GATE} failed"
|
||||
fi
|
||||
|
||||
# ── Ensure label exists ─────────────────────────────────────────────────────
|
||||
ensure_label() {
|
||||
local exists
|
||||
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/labels" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$exists" == "200" ]]; then
|
||||
local found
|
||||
found=$(curl -sf \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/labels" 2>/dev/null \
|
||||
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
|
||||
|
||||
if [[ -z "$found" ]]; then
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/labels" \
|
||||
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
|
||||
> /dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Search for existing open issue ──────────────────────────────────────────
|
||||
find_existing_issue() {
|
||||
local query
|
||||
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
|
||||
|
||||
local response
|
||||
response=$(curl -sf \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
|
||||
2>/dev/null || echo "[]")
|
||||
|
||||
echo "$response" \
|
||||
| grep -oP '"number":\s*\K[0-9]+' \
|
||||
| head -1
|
||||
}
|
||||
|
||||
# ── Build issue body ────────────────────────────────────────────────────────
|
||||
build_body() {
|
||||
local severity_badge
|
||||
if [[ "$SEVERITY" == "error" ]]; then
|
||||
severity_badge="**Severity:** Error"
|
||||
else
|
||||
severity_badge="**Severity:** Warning"
|
||||
fi
|
||||
|
||||
cat <<BODY
|
||||
## CI Gate Failure: ${GATE}
|
||||
|
||||
${severity_badge}
|
||||
**Workflow:** ${WORKFLOW:-unknown}
|
||||
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||
**Run:** [View CI run](${RUN_URL})
|
||||
|
||||
### Details
|
||||
|
||||
${DETAILS}
|
||||
|
||||
### Resolution
|
||||
|
||||
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
|
||||
|
||||
---
|
||||
*Auto-created by [ci-issue-reporter](${MOKOGITEA_URL}/MokoConsulting/MokoCLI/src/branch/main/cli/ci_issue_reporter.sh)*
|
||||
BODY
|
||||
}
|
||||
|
||||
# ── Build comment body (for existing issues) ────────────────────────────────
|
||||
build_comment() {
|
||||
cat <<COMMENT
|
||||
### CI failure recurrence
|
||||
|
||||
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
||||
**Commit:** \`${GITHUB_SHA:0:8}\`
|
||||
**Run:** [View CI run](${RUN_URL})
|
||||
|
||||
${DETAILS}
|
||||
COMMENT
|
||||
}
|
||||
|
||||
# ── Main ────────────────────────────────────────────────────────────────────
|
||||
ensure_label
|
||||
|
||||
EXISTING=$(find_existing_issue)
|
||||
|
||||
if [[ -n "$EXISTING" ]]; then
|
||||
COMMENT_BODY=$(build_comment)
|
||||
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
|
||||
import sys, json
|
||||
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
|
||||
|
||||
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${EXISTING}/comments" \
|
||||
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$HTTP" == "201" ]]; then
|
||||
echo "Commented on existing issue #${EXISTING}"
|
||||
else
|
||||
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
|
||||
fi
|
||||
else
|
||||
ISSUE_BODY=$(build_body)
|
||||
ISSUE_JSON=$(python3 -c "
|
||||
import sys, json
|
||||
body = sys.stdin.read()
|
||||
print(json.dumps({
|
||||
'title': sys.argv[1],
|
||||
'body': body,
|
||||
'labels': []
|
||||
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
|
||||
|
||||
RESPONSE=$(curl -sf -X POST \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues" \
|
||||
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
|
||||
|
||||
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
|
||||
|
||||
if [[ -n "$ISSUE_NUM" ]]; then
|
||||
LABEL_ID=$(curl -sf \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/labels" 2>/dev/null \
|
||||
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
|
||||
| head -1 || true)
|
||||
|
||||
if [[ -n "$LABEL_ID" ]]; then
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
"${API}/issues/${ISSUE_NUM}/labels" \
|
||||
-d "{\"labels\":[${LABEL_ID}]}" \
|
||||
> /dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
|
||||
else
|
||||
echo "WARNING: Failed to create issue"
|
||||
echo "Response: ${RESPONSE}"
|
||||
fi
|
||||
fi
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_dashboard.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Generate unified client dashboard HTML
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_inventory.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/client_provision.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Provision a new client environment end-to-end
|
||||
*/
|
||||
|
||||
@@ -104,7 +104,7 @@ class ClientProvisionCli extends CliFramework
|
||||
'variables' => 'setVariables',
|
||||
'secrets' => 'setSecrets',
|
||||
'monitoring' => 'setupMonitoring',
|
||||
'summary' => 'printSummary',
|
||||
'summary' => 'printProvisioningSummary',
|
||||
];
|
||||
|
||||
$exitCode = 0;
|
||||
@@ -383,7 +383,7 @@ class ClientProvisionCli extends CliFramework
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function printSummary(): int
|
||||
private function printProvisioningSummary(): int
|
||||
{
|
||||
$vars = $this->config['variables'] ?? [];
|
||||
$secrets = $this->config['secrets'] ?? [];
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/grafana_dashboard.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Manage Grafana dashboards via API
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/joomla_build.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
||||
* NOTE: Called by pre-release and auto-release workflows.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/joomla_metadata_validate.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Validate MokoGitea repo metadata against Joomla extension manifest XML
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_detect.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Auto-detect manifest fields from source files and optionally push to API
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_integrity.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Cross-check manifest API fields against repo contents across the org
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_licensing.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/manifest_read.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Read repo metadata from Gitea manifest API, auto-detect the rest
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/platform_detect.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Auto-detect repository platform type and optionally update manifest
|
||||
*/
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ class ReleaseCli extends CliFramework
|
||||
$bulkSyncFile = file_exists("{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml")
|
||||
? "{$repoRoot}/.mokogitea/workflows/bulk-repo-sync.yml"
|
||||
: "{$repoRoot}/.github/workflows/bulk-repo-sync.yml";
|
||||
$cleanupFile = "{$repoRoot}/templates/workflows/shared/repository-cleanup.yml.template";
|
||||
|
||||
// -- Step 1: Read current version --
|
||||
$readme = "{$repoRoot}/README.md";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/release_cascade.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Cascade release zip to all lower stability channels
|
||||
*/
|
||||
|
||||
|
||||
+79
-1
@@ -270,12 +270,90 @@ class ReleasePackageCli extends CliFramework
|
||||
}
|
||||
}
|
||||
|
||||
// Check if sub-source is itself a Joomla package (nested package)
|
||||
$nestedPkgManifests = glob("{$subSourceDir}/pkg_*.xml") ?: [];
|
||||
$isNestedPackage = !empty($nestedPkgManifests) && is_dir("{$subSourceDir}/packages");
|
||||
|
||||
$subZip = new \ZipArchive();
|
||||
if ($subZip->open($subZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||
$this->log('ERROR', "Failed to create sub-package ZIP: {$subZipPath}");
|
||||
continue;
|
||||
}
|
||||
$this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns);
|
||||
|
||||
if ($isNestedPackage) {
|
||||
// Build nested package: zip each sub-extension, then assemble
|
||||
echo " Building nested package: {$subName}\n";
|
||||
$nestedPkgDirs = glob("{$subSourceDir}/packages/*", GLOB_ONLYDIR) ?: [];
|
||||
|
||||
$nestedManifested = [];
|
||||
foreach ($nestedPkgManifests as $npmf) {
|
||||
$npmXml = @simplexml_load_file($npmf);
|
||||
if ($npmXml && isset($npmXml->files)) {
|
||||
foreach ($npmXml->files->file as $fn) {
|
||||
$nzn = pathinfo((string) $fn, PATHINFO_FILENAME);
|
||||
if (!empty($nzn)) {
|
||||
$nestedManifested[$nzn] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a subdirectory to avoid filename collisions with outer ZIPs
|
||||
$nestedOutputDir = "{$outputDir}/nested_{$subName}";
|
||||
@mkdir($nestedOutputDir, 0755, true);
|
||||
|
||||
foreach ($nestedPkgDirs as $npd) {
|
||||
$nestedSubName = basename($npd);
|
||||
if (!empty($nestedManifested) && !isset($nestedManifested[$nestedSubName])) {
|
||||
continue;
|
||||
}
|
||||
$nestedSubZipPath = "{$nestedOutputDir}/{$nestedSubName}.zip";
|
||||
$nsZip = new \ZipArchive();
|
||||
if ($nsZip->open($nestedSubZipPath, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) !== true) {
|
||||
continue;
|
||||
}
|
||||
$this->addDirToZip($nsZip, $npd, '', $this->excludePatterns);
|
||||
$nsZip->close();
|
||||
$subZip->addFile($nestedSubZipPath, "packages/{$nestedSubName}.zip");
|
||||
echo " Nested sub: {$nestedSubName}.zip\n";
|
||||
}
|
||||
|
||||
// Patch nested manifest: ensure folder="packages" so Joomla finds sub-ZIPs
|
||||
foreach ($nestedPkgManifests as $npmf) {
|
||||
$npmContent = file_get_contents($npmf);
|
||||
if ($npmContent !== false
|
||||
&& strpos($npmContent, '<files>') !== false
|
||||
&& strpos($npmContent, 'folder="packages"') === false
|
||||
) {
|
||||
$npmContent = str_replace('<files>', '<files folder="packages">', $npmContent);
|
||||
file_put_contents($npmf, $npmContent);
|
||||
echo " Fixed: added folder=\"packages\" to " . basename($npmf) . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Add top-level files (manifest, script, etc.) with exclude filter
|
||||
$nestedTopFiles = array_merge(
|
||||
glob("{$subSourceDir}/*.xml") ?: [],
|
||||
glob("{$subSourceDir}/*.php") ?: []
|
||||
);
|
||||
foreach ($nestedTopFiles as $ntf) {
|
||||
if (!$this->isExcluded(basename($ntf), $this->excludePatterns)) {
|
||||
$subZip->addFile($ntf, basename($ntf));
|
||||
}
|
||||
}
|
||||
|
||||
// Add all top-level directories except packages/
|
||||
$nestedTopDirs = glob("{$subSourceDir}/*", GLOB_ONLYDIR) ?: [];
|
||||
foreach ($nestedTopDirs as $ntd) {
|
||||
$ndName = basename($ntd);
|
||||
if ($ndName === 'packages') {
|
||||
continue;
|
||||
}
|
||||
$this->addDirToZip($subZip, $ntd, $ndName, $this->excludePatterns);
|
||||
}
|
||||
} else {
|
||||
$this->addDirToZip($subZip, $subSourceDir, '', $this->excludePatterns);
|
||||
}
|
||||
$subZip->close();
|
||||
|
||||
$zip->addFile($subZipPath, "packages/{$subName}.zip");
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/release_publish.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/scaffold_client.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: mokocli.CLI
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/theme_vars_check.php
|
||||
* BRIEF: Validate a client MokoOnyx theme package — required CSS variables
|
||||
* (derived dynamically from the MokoOnyx standard theme) are defined in
|
||||
* the client's light/dark custom CSS, required files exist, the manifest
|
||||
* is sane, and (optionally) the repo's Gitea metadata is set.
|
||||
*
|
||||
* Standalone (no CliFramework dependency) so it runs even when the shared
|
||||
* framework autoloader is unavailable.
|
||||
*
|
||||
* Usage:
|
||||
* php theme_vars_check.php --path . --reference /tmp/mokoonyx [--github-output]
|
||||
* [--api-base <url> --repo <owner/repo> --token <token>]
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
$opts = getopt('', ['path:', 'reference:', 'github-output', 'api-base:', 'repo:', 'token:']);
|
||||
$path = isset($opts['path']) && is_string($opts['path']) && $opts['path'] !== '' ? $opts['path'] : '.';
|
||||
$ref = isset($opts['reference']) && is_string($opts['reference']) ? $opts['reference'] : '';
|
||||
$gh = array_key_exists('github-output', $opts);
|
||||
|
||||
$root = realpath($path);
|
||||
if ($root === false) { $root = $path; }
|
||||
|
||||
$src = is_dir("$root/src") ? "$root/src" : (is_dir("$root/source") ? "$root/source" : null);
|
||||
if ($src === null) {
|
||||
fwrite(STDERR, "ERROR: no src/ or source/ directory under $root\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$errors = 0;
|
||||
$summary = [];
|
||||
$fail = function (string $msg) use (&$errors, &$summary): void {
|
||||
echo " [FAIL] $msg\n";
|
||||
$summary[] = "FAIL: $msg";
|
||||
$errors++;
|
||||
};
|
||||
$ok = function (string $msg): void { echo " [ok] $msg\n"; };
|
||||
$note = function (string $msg): void { echo " [note] $msg\n"; };
|
||||
|
||||
/** Extract the set of CSS custom-property names DEFINED in a CSS string. */
|
||||
$definedVars = static function (string $css): array {
|
||||
// Matches "--name:" at a declaration position (not var(--name) uses).
|
||||
preg_match_all('/(?:^|[\s;{])(--[a-z0-9_-]+)\s*:/i', $css, $m);
|
||||
return array_values(array_unique(array_map('strtolower', $m[1] ?? [])));
|
||||
};
|
||||
|
||||
/** Find the MokoOnyx standard theme file for a mode inside a reference checkout. */
|
||||
$findStandard = static function (string $ref, string $mode): ?string {
|
||||
if ($ref === '') { return null; }
|
||||
foreach ([
|
||||
"$ref/source/media/css/theme/$mode.standard.css",
|
||||
"$ref/media/templates/site/mokoonyx/css/theme/$mode.standard.css",
|
||||
"$ref/$mode.standard.css",
|
||||
] as $cand) {
|
||||
if (is_file($cand)) { return $cand; }
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
$cssDir = "$src/media/templates/site/mokoonyx/css";
|
||||
$themeDir = "$cssDir/theme";
|
||||
$manifest = "$src/templateDetails.xml";
|
||||
$customs = ['light' => "$themeDir/light.custom.css", 'dark' => "$themeDir/dark.custom.css"];
|
||||
|
||||
echo "MokoOnyx theme validation: $src\n\n";
|
||||
|
||||
// 1) Required files -------------------------------------------------------
|
||||
echo "=== Required files ===\n";
|
||||
$requiredFiles = [
|
||||
'templateDetails.xml' => $manifest,
|
||||
'theme/light.custom.css' => $customs['light'],
|
||||
'theme/dark.custom.css' => $customs['dark'],
|
||||
'user.css' => "$cssDir/user.css",
|
||||
];
|
||||
foreach ($requiredFiles as $label => $file) {
|
||||
is_file($file) ? $ok("$label present") : $fail("missing required file: $label");
|
||||
}
|
||||
|
||||
// 1b) Forbidden committed files ------------------------------------------
|
||||
echo "\n=== Forbidden files ===\n";
|
||||
$scan = static function (string $dir, array $skip) use (&$scan): array {
|
||||
$out = [];
|
||||
foreach (scandir($dir) ?: [] as $e) {
|
||||
if ($e === '.' || $e === '..') { continue; }
|
||||
$p = "$dir/$e";
|
||||
if (is_dir($p)) {
|
||||
if (in_array($e, $skip, true)) { continue; }
|
||||
$out = array_merge($out, $scan($p, $skip));
|
||||
} else {
|
||||
$out[] = $p;
|
||||
}
|
||||
}
|
||||
return $out;
|
||||
};
|
||||
$forbidden = [];
|
||||
if (is_dir("$root/.claude")) { $forbidden[] = '.claude/'; }
|
||||
foreach ($scan($root, ['.git', 'vendor', 'node_modules']) as $f) {
|
||||
$b = basename($f);
|
||||
$rel = ltrim(str_replace('\\', '/', substr($f, strlen($root))), '/');
|
||||
if ($b === '.mcp.json' || $b === 'TODO.md'
|
||||
|| fnmatch('.mcp_*.json', $b) || fnmatch('sftp-config*.json', $b)
|
||||
|| fnmatch('*.min.css', $b) || fnmatch('*.min.js', $b)) {
|
||||
$forbidden[] = $rel;
|
||||
}
|
||||
}
|
||||
$forbidden = array_values(array_unique($forbidden));
|
||||
if ($forbidden) {
|
||||
$shown = array_slice($forbidden, 0, 10);
|
||||
$more = count($forbidden) - count($shown);
|
||||
$fail('forbidden file(s) committed: ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||
} else {
|
||||
$ok('no forbidden files committed');
|
||||
}
|
||||
|
||||
// 2) CSS variables — required set derived from the MokoOnyx standard theme
|
||||
echo "\n=== CSS variables vs MokoOnyx standard theme ===\n";
|
||||
if ($ref === '') {
|
||||
$note('no --reference given; skipping variable parity check');
|
||||
} else {
|
||||
foreach ($customs as $mode => $customFile) {
|
||||
$std = $findStandard($ref, $mode);
|
||||
if ($std === null) {
|
||||
$fail("$mode: standard theme not found under reference '$ref'");
|
||||
continue;
|
||||
}
|
||||
if (!is_file($customFile)) {
|
||||
continue; // already reported missing
|
||||
}
|
||||
$required = $definedVars((string) file_get_contents($std));
|
||||
$defined = $definedVars((string) file_get_contents($customFile));
|
||||
$missing = array_values(array_diff($required, $defined));
|
||||
if ($missing) {
|
||||
$shown = array_slice($missing, 0, 15);
|
||||
$more = count($missing) - count($shown);
|
||||
$fail("$mode mode missing " . count($missing) . '/' . count($required)
|
||||
. ' standard variable(s): ' . implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||
} else {
|
||||
$ok("$mode mode defines all " . count($required) . ' standard variables');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Manifest sanity ------------------------------------------------------
|
||||
echo "\n=== Manifest (templateDetails.xml) ===\n";
|
||||
if (is_file($manifest)) {
|
||||
$xml = @simplexml_load_file($manifest);
|
||||
if ($xml === false) {
|
||||
$fail('templateDetails.xml is not well-formed XML');
|
||||
} else {
|
||||
$version = isset($xml->version) ? trim((string) $xml->version) : '';
|
||||
$version !== '' ? $ok("version $version") : $fail('<version> is missing or empty');
|
||||
|
||||
$server = isset($xml->updateservers->server) ? trim((string) $xml->updateservers->server) : '';
|
||||
if ($server === '') {
|
||||
$fail('<updateservers><server> is missing');
|
||||
} elseif (strpos($server, '/raw/branch/') !== false) {
|
||||
$fail('update server uses a legacy raw/branch URL; use the dynamic MokoGitea feed');
|
||||
} else {
|
||||
$ok("update server: $server");
|
||||
}
|
||||
|
||||
isset($xml->dlid) ? $ok('<dlid> license-key field present')
|
||||
: $fail('<dlid prefix="dlid=" suffix=""/> is missing');
|
||||
|
||||
// Element naming convention (MokoOnyx client theme file package)
|
||||
$type = trim((string) ($xml['type'] ?? ''));
|
||||
$element = isset($xml->element) ? trim((string) $xml->element) : '';
|
||||
if ($type === 'file' && $element !== '') {
|
||||
strpos($element, 'file_mokoonyx_') === 0
|
||||
? $ok("element naming ok ($element)")
|
||||
: $fail("element '$element' should start with 'file_mokoonyx_' for a MokoOnyx client theme package");
|
||||
}
|
||||
|
||||
// Required manifest fields
|
||||
foreach (['name', 'element', 'author', 'creationDate'] as $field) {
|
||||
(isset($xml->$field) && trim((string) $xml->$field) !== '')
|
||||
? $ok("<$field> present")
|
||||
: $fail("<$field> is missing or empty");
|
||||
}
|
||||
|
||||
// scriptfile (if declared) must exist
|
||||
if (isset($xml->scriptfile)) {
|
||||
$sf = trim((string) $xml->scriptfile);
|
||||
is_file("$src/$sf") ? $ok("scriptfile '$sf' present")
|
||||
: $fail("<scriptfile> references '$sf' which is not in src/");
|
||||
}
|
||||
|
||||
// Fileset integrity — every referenced file/folder must exist in src/
|
||||
if (isset($xml->fileset)) {
|
||||
$missingFs = [];
|
||||
foreach ($xml->fileset->files as $files) {
|
||||
$folder = trim((string) ($files['folder'] ?? ''));
|
||||
$baseDir = $folder !== '' ? "$src/$folder" : $src;
|
||||
foreach ($files->filename as $fn) {
|
||||
$rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fn);
|
||||
if (!is_file("$baseDir/" . trim((string) $fn))) { $missingFs[] = $rel; }
|
||||
}
|
||||
foreach ($files->folder as $fd) {
|
||||
$rel = ($folder !== '' ? "$folder/" : '') . trim((string) $fd) . '/';
|
||||
if (!is_dir("$baseDir/" . trim((string) $fd))) { $missingFs[] = $rel; }
|
||||
}
|
||||
}
|
||||
if ($missingFs) {
|
||||
$shown = array_slice($missingFs, 0, 10);
|
||||
$more = count($missingFs) - count($shown);
|
||||
$fail('fileset references ' . count($missingFs) . ' missing path(s): '
|
||||
. implode(', ', $shown) . ($more > 0 ? " (+$more more)" : ''));
|
||||
} else {
|
||||
$ok('all fileset paths exist in src/');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Repository metadata via Gitea API (optional) -------------------------
|
||||
$apiBase = isset($opts['api-base']) && is_string($opts['api-base']) ? rtrim($opts['api-base'], '/') : '';
|
||||
$repoSlug = isset($opts['repo']) && is_string($opts['repo']) ? trim($opts['repo']) : '';
|
||||
$token = isset($opts['token']) && is_string($opts['token']) ? trim($opts['token']) : '';
|
||||
if ($apiBase !== '' && $repoSlug !== '' && $token !== '') {
|
||||
echo "\n=== Repository metadata (Gitea API) ===\n";
|
||||
$url = "$apiBase/repos/$repoSlug";
|
||||
$ctx = stream_context_create(['http' => [
|
||||
'method' => 'GET',
|
||||
'header' => "Authorization: token $token\r\nAccept: application/json\r\n",
|
||||
'ignore_errors' => true,
|
||||
'timeout' => 15,
|
||||
]]);
|
||||
$resp = @file_get_contents($url, false, $ctx);
|
||||
$data = $resp !== false ? json_decode($resp, true) : null;
|
||||
if (!is_array($data) || !isset($data['name'])) {
|
||||
$fail("could not read repo metadata from Gitea API ($url)");
|
||||
} else {
|
||||
trim((string) ($data['description'] ?? '')) !== '' ? $ok('description set')
|
||||
: $fail('repo description is empty');
|
||||
trim((string) ($data['website'] ?? '')) !== '' ? $ok('website set: ' . $data['website'])
|
||||
: $fail('repo website is empty');
|
||||
$topics = $data['topics'] ?? [];
|
||||
(is_array($topics) && count($topics) > 0) ? $ok(count($topics) . ' topic(s) set')
|
||||
: $fail('repo has no topics');
|
||||
|
||||
// Required topics
|
||||
$requiredTopics = ['joomla', 'client-waas', 'mokoonyx'];
|
||||
$haveTopics = array_map('strtolower', is_array($topics) ? $topics : []);
|
||||
$missingTopics = array_values(array_diff($requiredTopics, $haveTopics));
|
||||
$missingTopics
|
||||
? $fail('missing required topic(s): ' . implode(', ', $missingTopics))
|
||||
: $ok('required topics present (' . implode(', ', $requiredTopics) . ')');
|
||||
|
||||
((string) ($data['default_branch'] ?? '')) === 'main' ? $ok('default branch is main')
|
||||
: $fail("default branch is '" . ($data['default_branch'] ?? '') . "' (expected main)");
|
||||
|
||||
// Branch protection on main
|
||||
$bresp = @file_get_contents("$apiBase/repos/$repoSlug/branches/main", false, $ctx);
|
||||
$bdata = $bresp !== false ? json_decode($bresp, true) : null;
|
||||
if (is_array($bdata) && array_key_exists('protected', $bdata)) {
|
||||
$bdata['protected'] ? $ok('main branch is protected')
|
||||
: $fail('main branch is not protected');
|
||||
} else {
|
||||
$note('could not read branch protection state for main');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
echo "\n=== Repository metadata (Gitea API) ===\n";
|
||||
$note('API args not provided; skipping repo metadata check');
|
||||
}
|
||||
|
||||
// Output / exit -----------------------------------------------------------
|
||||
echo "\n";
|
||||
if ($gh && ($f = getenv('GITHUB_STEP_SUMMARY'))) {
|
||||
$md = "## MokoOnyx Theme Validation\n\n";
|
||||
$md .= $errors === 0
|
||||
? "All checks passed.\n"
|
||||
: implode("\n", array_map(static fn ($l) => "- $l", $summary)) . "\n";
|
||||
@file_put_contents($f, $md, FILE_APPEND);
|
||||
}
|
||||
|
||||
if ($errors > 0) {
|
||||
echo "FAILED: $errors issue(s) found.\n";
|
||||
exit(1);
|
||||
}
|
||||
echo "All MokoOnyx theme validation checks passed.\n";
|
||||
exit(0);
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/updates_xml_sync.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Sync updates.xml to target branches via Gitea API
|
||||
* NOTE: Called by pre-release and auto-release workflows after updates.xml
|
||||
* is modified on the current branch. Pushes the file to other branches
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/version_auto_bump.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
||||
*/
|
||||
|
||||
|
||||
@@ -214,7 +214,8 @@ class VersionBumpCli extends CliFramework
|
||||
$newContent = preg_replace(
|
||||
$xmlPattern,
|
||||
'${1}' . $newBase . '</version>',
|
||||
$content
|
||||
$content,
|
||||
1
|
||||
);
|
||||
} else {
|
||||
$xmlPattern = '#<version>\d{2}\.\d{2}\.\d{2}'
|
||||
@@ -222,9 +223,13 @@ class VersionBumpCli extends CliFramework
|
||||
$newContent = preg_replace(
|
||||
$xmlPattern,
|
||||
"<version>{$newFull}</version>",
|
||||
$content
|
||||
$content,
|
||||
1
|
||||
);
|
||||
}
|
||||
if ($newContent !== null) {
|
||||
$newContent = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $newContent);
|
||||
}
|
||||
if ($newContent !== null && $newContent !== $content) {
|
||||
file_put_contents($xmlFile, $newContent);
|
||||
$updatedFiles[] = substr($xmlFile, strlen($root) + 1);
|
||||
@@ -384,7 +389,7 @@ class VersionBumpCli extends CliFramework
|
||||
/**
|
||||
* Scan git release tags for the highest version across all channels.
|
||||
*
|
||||
* Checks release names like "MokoSuiteClient (VERSION: 09.40.00)" in
|
||||
* Checks release names like "MokoSuiteClient (VERSION: 09.42.00)" in
|
||||
* git tags (stable, release-candidate, development, etc.) to find the
|
||||
* highest version that has been released on any channel.
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/version_check.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||
*/
|
||||
|
||||
@@ -131,9 +131,11 @@ class VersionCheckCli extends CliFramework
|
||||
$updated = preg_replace(
|
||||
$vPat,
|
||||
"<version>{$highestVersion}</version>",
|
||||
$content
|
||||
$content,
|
||||
1
|
||||
);
|
||||
if ($updated !== null) {
|
||||
$updated = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $updated);
|
||||
file_put_contents($mokoManifest, $updated);
|
||||
} echo " Fixed: .mokogitea/manifest.xml -> {$highestVersion}\n";
|
||||
}
|
||||
@@ -185,8 +187,9 @@ class VersionCheckCli extends CliFramework
|
||||
if (!file_exists($file)) {
|
||||
continue;
|
||||
} $content = file_get_contents($file);
|
||||
$updated = preg_replace('#<version>[^<]*</version>#', "<version>{$highestVersion}</version>", $content);
|
||||
$updated = preg_replace('#<version>[^<]*</version>#', "<version>{$highestVersion}</version>", $content, 1);
|
||||
if ($updated !== null) {
|
||||
$updated = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $updated);
|
||||
file_put_contents($file, $updated);
|
||||
} echo " Fixed: {$source} -> {$highestVersion}\n";
|
||||
}
|
||||
|
||||
@@ -164,8 +164,12 @@ class VersionSetPlatformCli extends CliFramework
|
||||
$updated = preg_replace(
|
||||
'|<version>[^<]*</version>|',
|
||||
"<version>{$version}</version>",
|
||||
$content
|
||||
$content,
|
||||
1
|
||||
);
|
||||
if ($updated !== null) {
|
||||
$updated = preg_replace('#(\s*<version>[^<]*</version>)\s*<version>[^<]*</version>#', '$1', $updated);
|
||||
}
|
||||
if ($updated !== null && $updated !== $content) {
|
||||
file_put_contents($file, $updated);
|
||||
$relPath = str_replace($root . '/', '', $file);
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/wiki_sync.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Sync select wiki pages from mokocli to all template repos
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: mokocli
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /cli/workflow_sync.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform
|
||||
*/
|
||||
|
||||
|
||||
+2
-2
@@ -71,8 +71,8 @@
|
||||
],
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"phpcs": "phpcs --standard=phpcs.xml lib/ validate/ automation/",
|
||||
"phpstan": "phpstan analyse -c phpstan.neon lib/ validate/ automation/",
|
||||
"phpcs": "phpcs --standard=phpcs.xml lib/ validate/",
|
||||
"phpstan": "phpstan analyse -c phpstan.neon lib/ validate/",
|
||||
"psalm": "psalm --config=psalm.xml",
|
||||
"check": [
|
||||
"@phpcs",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/backup-before-deploy.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/deploy-dolibarr.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/health-check.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Post-deploy health check — verify a Joomla site is responding correctly
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/rollback-joomla.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Rollback a Joomla deployment by restoring from a pre-deploy snapshot
|
||||
*/
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /deploy/sync-joomla.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Sync Joomla site directories between two servers via rsync over SSH
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
@@ -76,7 +75,9 @@ class RecoverySuggestion
|
||||
*/
|
||||
public static function forMissingHeader(string $file): string
|
||||
{
|
||||
return "Add SPDX license header to {$file}:\n /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>\n * SPDX-License-Identifier: GPL-3.0-or-later */";
|
||||
return "Add SPDX license header to {$file}:\n"
|
||||
. " /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>\n"
|
||||
. " * SPDX-License-Identifier: GPL-3.0-or-later */";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -930,10 +930,10 @@ class RepositorySynchronizer
|
||||
private const TEMPLATE_REPOS = [
|
||||
'joomla' => 'MokoConsulting/Template-Joomla',
|
||||
'waas-component' => 'MokoConsulting/Template-Joomla',
|
||||
'dolibarr' => 'MokoConsulting/Template-Dolibarr',
|
||||
'crm-module' => 'MokoConsulting/Template-Dolibarr',
|
||||
'generic' => 'MokoConsulting/Template-Generic',
|
||||
'mcp' => 'MokoConsulting/Template-Generic',
|
||||
'dolibarr' => 'MokoConsulting/Template-Generic',
|
||||
'crm-module' => 'MokoConsulting/Template-Generic',
|
||||
'client' => 'MokoConsulting/Template-Client-WaaS',
|
||||
];
|
||||
|
||||
@@ -951,7 +951,6 @@ class RepositorySynchronizer
|
||||
*/
|
||||
private const TEMPLATE_SYNC_TARGETS = [
|
||||
'MokoConsulting/Template-Joomla',
|
||||
'MokoConsulting/Template-Dolibarr',
|
||||
'MokoConsulting/Template-Client-WaaS',
|
||||
];
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
DEFGROUP: dolibarr-api-mcp.Documentation
|
||||
INGROUP: dolibarr-api-mcp
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
||||
VERSION: 09.40.00
|
||||
VERSION: 09.42.00
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: Contribution guidelines for the project
|
||||
-->
|
||||
|
||||
@@ -10,7 +10,7 @@ DEFGROUP: dolibarr-api-mcp.Documentation
|
||||
INGROUP: dolibarr-api-mcp
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/dolibarr-api-mcp
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 09.40.00
|
||||
VERSION: 09.42.00
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
Submodule mcp/servers/mokogitea_api deleted from 44e1259c3e
@@ -14,7 +14,7 @@
|
||||
DEFGROUP:
|
||||
INGROUP: Project.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoCli-Template-Generic
|
||||
VERSION: 09.40.00
|
||||
VERSION: 09.42.00
|
||||
PATH: ./CONTRIBUTING.md
|
||||
BRIEF: Contribution guidelines for the project
|
||||
-->
|
||||
|
||||
@@ -23,7 +23,7 @@ DEFGROUP: [PROJECT_NAME]
|
||||
INGROUP: [PROJECT_NAME].Documentation
|
||||
REPO: [REPOSITORY_URL]
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 09.40.00
|
||||
VERSION: 09.42.00
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
|
||||
Submodule mcp/servers/windows/mcp_windows deleted from 4ae12d459b
@@ -12,7 +12,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<!-- Files to check -->
|
||||
<file>lib</file>
|
||||
<file>validate</file>
|
||||
<file>automation</file>
|
||||
<file>cli</file>
|
||||
|
||||
<!-- Exclude vendor and other dependencies -->
|
||||
|
||||
@@ -2363,3 +2363,711 @@ parameters:
|
||||
identifier: deadCode.unreachable
|
||||
count: 2
|
||||
path: validate/scan_drift.php
|
||||
-
|
||||
message: '#^Function apiRequest\(\) has parameter \$body with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/branch_protect_org.php
|
||||
-
|
||||
message: '#^Function apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/branch_protect_org.php
|
||||
-
|
||||
message: '#^Method BranchRenameCli\:\:apiRequest\(\) has parameter \$body with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/branch_rename.php
|
||||
-
|
||||
message: '#^Method BranchRenameCli\:\:apiRequest\(\) has parameter \$headers with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/branch_rename.php
|
||||
-
|
||||
message: '#^Method BranchRenameCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/branch_rename.php
|
||||
-
|
||||
message: '#^Method BulkWorkflowPushCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/bulk_workflow_push.php
|
||||
-
|
||||
message: '#^Method BulkWorkflowPushCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/bulk_workflow_push.php
|
||||
-
|
||||
message: '#^Method BulkWorkflowTriggerCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/bulk_workflow_trigger.php
|
||||
-
|
||||
message: '#^Method BulkWorkflowTriggerCli\:\:buildRepoList\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/bulk_workflow_trigger.php
|
||||
-
|
||||
message: '#^Method ClientDashboardCli\:\:discoverClients\(\) never returns null so it can be removed from the return type\.$#'
|
||||
count: 1
|
||||
path: cli/client_dashboard.php
|
||||
-
|
||||
message: '#^Method ClientInventoryCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_inventory.php
|
||||
-
|
||||
message: '#^Method ClientInventoryCli\:\:checkVariables\(\) has parameter \$requiredVars with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_inventory.php
|
||||
-
|
||||
message: '#^Method ClientInventoryCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_inventory.php
|
||||
-
|
||||
message: '#^Method ClientInventoryCli\:\:fetchOrgs\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_inventory.php
|
||||
-
|
||||
message: '#^Method ClientProvisionCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Method ClientProvisionCli\:\:grafanaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Method ClientProvisionCli\:\:httpRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Method ClientProvisionCli\:\:printSummary\(\) overrides method MokoCli\\CliFramework\:\:printSummary\(\) but misses parameter \#1 \$passed\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Method ClientProvisionCli\:\:printSummary\(\) overrides method MokoCli\\CliFramework\:\:printSummary\(\) but misses parameter \#2 \$failed\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Method ClientProvisionCli\:\:printSummary\(\) overrides method MokoCli\\CliFramework\:\:printSummary\(\) but misses parameter \#3 \$elapsed\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Private method ClientProvisionCli\:\:printSummary\(\) overriding protected method MokoCli\\CliFramework\:\:printSummary\(\) should be protected or public\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Return type int of method ClientProvisionCli\:\:printSummary\(\) is not covariant with return type void of method MokoCli\\CliFramework\:\:printSummary\(\)\.$#'
|
||||
count: 1
|
||||
path: cli/client_provision.php
|
||||
-
|
||||
message: '#^Method CompletionCli\:\:generateBash\(\) has parameter \$commands with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/completion.php
|
||||
-
|
||||
message: '#^Method CompletionCli\:\:generateBash\(\) has parameter \$flags with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/completion.php
|
||||
-
|
||||
message: '#^Method CompletionCli\:\:generateZsh\(\) has parameter \$commands with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/completion.php
|
||||
-
|
||||
message: '#^Method CompletionCli\:\:generateZsh\(\) has parameter \$flags with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/completion.php
|
||||
-
|
||||
message: '#^Method CreateProjectCli\:\:createProject\(\) has parameter \$template with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/create_project.php
|
||||
-
|
||||
message: '#^Method CreateProjectCli\:\:graphql\(\) has parameter \$variables with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/create_project.php
|
||||
-
|
||||
message: '#^Method CreateProjectCli\:\:graphql\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/create_project.php
|
||||
-
|
||||
message: '#^Method CreateProjectCli\:\:parseTemplate\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 2
|
||||
path: cli/create_project.php
|
||||
-
|
||||
message: '#^Method CreateProjectCli\:\:restGet\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/create_project.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:buildDeployMap\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:dryRunWalk\(\) has parameter \$deployMap with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:firstExistingDir\(\) has parameter \$candidates with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:getManifestDestination\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:getManifestFilename\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapComponent\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapComponent\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapFile\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapFile\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapLanguage\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapLanguage\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapLibrary\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapLibrary\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapModule\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapModule\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapPackage\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapPackage\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapPlugin\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapPlugin\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapTemplate\(\) has parameter \$ext with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method DeployJoomla\:\:mapTemplate\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Parameter \#2 \.\.\.\$args of method phpseclib3\\Net\\SSH2\:\:login\(\) expects array\<array\>\|phpseclib3\\Crypt\\Common\\PrivateKey\|phpseclib3\\System\\SSH\\Agent\|string\|null, phpseclib3\\Crypt\\Common\\AsymmetricKey given\.$#'
|
||||
count: 1
|
||||
path: cli/deploy_joomla.php
|
||||
-
|
||||
message: '#^Method GrafanaDashboardCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/grafana_dashboard.php
|
||||
-
|
||||
message: '#^Method JoomlaBuildCli\:\:parseManifest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_build.php
|
||||
-
|
||||
message: '#^Method JoomlaBuildCli\:\:typePrefix\(\) has parameter \$meta with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_build.php
|
||||
-
|
||||
message: '#^Method JoomlaMetadataValidateCli\:\:compare\(\) has parameter \$joomlaXml with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_metadata_validate.php
|
||||
-
|
||||
message: '#^Method JoomlaMetadataValidateCli\:\:compare\(\) has parameter \$metadata with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_metadata_validate.php
|
||||
-
|
||||
message: '#^Method JoomlaMetadataValidateCli\:\:compare\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_metadata_validate.php
|
||||
-
|
||||
message: '#^Method JoomlaMetadataValidateCli\:\:findJoomlaManifest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_metadata_validate.php
|
||||
-
|
||||
message: '#^Method JoomlaMetadataValidateCli\:\:loadMetadata\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_metadata_validate.php
|
||||
-
|
||||
message: '#^Method JoomlaMetadataValidateCli\:\:printResults\(\) has parameter \$results with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/joomla_metadata_validate.php
|
||||
-
|
||||
message: '#^Call to an undefined method LicenseManage\:\:isDryRun\(\)\.$#'
|
||||
count: 7
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiDelete\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiGet\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiPatch\(\) has parameter \$data with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiPatch\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiPost\(\) has parameter \$data with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiPost\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiRequest\(\) has parameter \$data with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Method LicenseManage\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Parameter \#1 \$passed of method MokoCli\\CliFramework\:\:status\(\) expects bool, string given\.$#'
|
||||
count: 4
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Parameter \#2 \$label of method MokoCli\\CliFramework\:\:status\(\) expects string, false given\.$#'
|
||||
count: 1
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Parameter \#2 \$label of method MokoCli\\CliFramework\:\:status\(\) expects string, true given\.$#'
|
||||
count: 2
|
||||
path: cli/license_manage.php
|
||||
-
|
||||
message: '#^Call to an undefined method ManifestDetectCli\:\:sectionHeader\(\)\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:computeDiff\(\) has parameter \$current with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:computeDiff\(\) has parameter \$detected with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:computeDiff\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:detectAll\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:detectDolibarr\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:detectGeneric\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:detectGo\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:detectJoomla\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:detectNode\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:fetchManifest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:pushManifest\(\) has parameter \$current with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestDetectCli\:\:pushManifest\(\) has parameter \$update with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_detect.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:applyFixes\(\) has parameter \$current with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:applyFixes\(\) has parameter \$issues with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:fetchManifest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:printIssues\(\) has parameter \$issues with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:runDetect\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validate\(\) has parameter \$current with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validate\(\) has parameter \$detected with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validate\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validateManifestOnly\(\) has parameter \$manifest with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validateManifestOnly\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validatePlatformStructure\(\) has parameter \$manifest with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Method ManifestIntegrityCli\:\:validatePlatformStructure\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_integrity.php
|
||||
-
|
||||
message: '#^Constant ManifestReadCli\:\:JOOMLA_XML_ROOTS is unused\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:addAliases\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:addAliases\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:autoDetect\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:detectDolibarr\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:detectJoomla\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:fetchFromApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method ManifestReadCli\:\:outputFields\(\) has parameter \$fields with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/manifest_read.php
|
||||
-
|
||||
message: '#^Method PackageBuildCli\:\:addDirectoryToZip\(\) has parameter \$excludes with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/package_build.php
|
||||
-
|
||||
message: '#^Method PlatformDetectCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/platform_detect.php
|
||||
-
|
||||
message: '#^Method ReleaseCascadeCli\:\:cascadeToChannel\(\) has parameter \$downloads with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_cascade.php
|
||||
-
|
||||
message: '#^Method ReleaseCascadeCli\:\:getLowerChannels\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_cascade.php
|
||||
-
|
||||
message: '#^Method ReleaseCascadeCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_cascade.php
|
||||
-
|
||||
message: '#^Method ReleaseCreateCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_create.php
|
||||
-
|
||||
message: '#^Method ReleaseManageCli\:\:getReleaseByTag\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_manage.php
|
||||
-
|
||||
message: '#^Method ReleaseManageCli\:\:releaseGiteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_manage.php
|
||||
-
|
||||
message: '#^Method ReleaseMirrorCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_mirror.php
|
||||
-
|
||||
message: '#^Method ReleaseMirrorCli\:\:githubApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_mirror.php
|
||||
-
|
||||
message: '#^Strict comparison using \=\=\= between mixed~\(0\|0\.0\|''''\|''0''\|array\{\}\|false\|null\) and '''' will always evaluate to false\.$#'
|
||||
count: 1
|
||||
path: cli/release_notes.php
|
||||
-
|
||||
message: '#^Method ReleasePackageCli\:\:addDirToZip\(\) has parameter \$excludes with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_package.php
|
||||
-
|
||||
message: '#^Method ReleasePackageCli\:\:isExcluded\(\) has parameter \$patterns with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_package.php
|
||||
-
|
||||
message: '#^Method ReleasePromoteCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_promote.php
|
||||
-
|
||||
message: '#^Variable \$token in empty\(\) always exists and is not falsy\.$#'
|
||||
count: 1
|
||||
path: cli/release_publish.php
|
||||
-
|
||||
message: '#^Property ReleaseValidateCli\:\:\$results type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_validate.php
|
||||
-
|
||||
message: '#^Property ReleaseVerifyCli\:\:\$results type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/release_verify.php
|
||||
-
|
||||
message: '#^Call to an undefined method MokoCli\\GitPlatformAdapter\:\:createRepository\(\)\.$#'
|
||||
count: 1
|
||||
path: cli/repo_wizard.php
|
||||
-
|
||||
message: '#^Method RepoWizard\:\:choose\(\) has parameter \$options with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/repo_wizard.php
|
||||
-
|
||||
message: '#^Method RepoWizard\:\:generateFiles\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/repo_wizard.php
|
||||
-
|
||||
message: '#^Offset 0 on non\-empty\-list\<string\> on left side of \?\? always exists and is not nullable\.$#'
|
||||
count: 1
|
||||
path: cli/repo_wizard.php
|
||||
-
|
||||
message: '#^Property RepoWizard\:\:\$answers type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/repo_wizard.php
|
||||
-
|
||||
message: '#^Method ScaffoldClientCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/scaffold_client.php
|
||||
-
|
||||
message: '#^Method ThemeLintCli\:\:findFiles\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/theme_lint.php
|
||||
-
|
||||
message: '#^Offset 1 on array\{list\<string\>, list\<non\-falsy\-string\>\} on left side of \?\? always exists and is not nullable\.$#'
|
||||
count: 1
|
||||
path: cli/theme_vars_check.php
|
||||
-
|
||||
message: '#^Method UpdatesXmlSyncCli\:\:apiCall\(\) has parameter \$data with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/updates_xml_sync.php
|
||||
-
|
||||
message: '#^Method UpdatesXmlSyncCli\:\:apiCall\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/updates_xml_sync.php
|
||||
-
|
||||
message: '#^Offset ''alpha''\|''beta''\|''dev''\|''rc'' on array\{dev\: ''\-dev'', alpha\: ''\-alpha'', beta\: ''\-beta'', rc\: ''\-rc''\} on left side of \?\? always exists and is not nullable\.$#'
|
||||
count: 1
|
||||
path: cli/version_auto_bump.php
|
||||
-
|
||||
message: '#^Ternary operator condition is always true\.$#'
|
||||
count: 1
|
||||
path: cli/version_auto_bump.php
|
||||
-
|
||||
message: '#^Variable \$updatedFiles on left side of \?\? always exists and is not nullable\.$#'
|
||||
count: 1
|
||||
path: cli/version_bump.php
|
||||
-
|
||||
message: '#^Method VersionBumpRemoteCli\:\:giteaApi\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/version_bump_remote.php
|
||||
-
|
||||
message: '#^Method VersionResetDevCli\:\:giteaApiCall\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/version_reset_dev.php
|
||||
-
|
||||
message: '#^Method WikiSyncCli\:\:apiGet\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Method WikiSyncCli\:\:apiPatch\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Method WikiSyncCli\:\:apiPost\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Method WikiSyncCli\:\:apiWrite\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Method WikiSyncCli\:\:discoverTemplateRepos\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Method WikiSyncCli\:\:getStandardsPages\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Property WikiSyncCli\:\:\$pages type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Property WikiSyncCli\:\:\$targetRepos type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/wiki_sync.php
|
||||
-
|
||||
message: '#^Method WorkflowSyncCli\:\:apiRequest\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/workflow_sync.php
|
||||
-
|
||||
message: '#^Method WorkflowSyncCli\:\:deleteOrphanWorkflows\(\) has parameter \$templateNames with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/workflow_sync.php
|
||||
-
|
||||
message: '#^Method WorkflowSyncCli\:\:fetchOrgRepos\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/workflow_sync.php
|
||||
-
|
||||
message: '#^Method WorkflowSyncCli\:\:listWorkflows\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: cli/workflow_sync.php
|
||||
-
|
||||
message: '#^Strict comparison using \=\=\= between string and false will always evaluate to false\.$#'
|
||||
count: 1
|
||||
path: cli/workflow_sync.php
|
||||
-
|
||||
message: '#^Constructor of class MokoCli\\CLIApp has an unused parameter \$description\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/CliFramework.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:generate\(\) has parameter \$params with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parse\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 4
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseBuild\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseDeploy\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseGovernance\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseIdentity\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseLegacy\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseOverrides\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:parseScripts\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\ManifestParser\:\:tryParse\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/ManifestParser.php
|
||||
-
|
||||
message: '#^Method MokoCli\\Plugins\\DolibarrPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/Plugins/DolibarrPlugin.php
|
||||
-
|
||||
message: '#^Method MokoCli\\Plugins\\JoomlaPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/Plugins/JoomlaPlugin.php
|
||||
-
|
||||
message: '#^Variable \$content on left side of \?\? always exists and is not nullable\.$#'
|
||||
count: 2
|
||||
path: lib/Enterprise/Plugins/McpServerPlugin.php
|
||||
-
|
||||
message: '#^Method MokoCli\\Plugins\\NodeJsPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/Plugins/NodeJsPlugin.php
|
||||
-
|
||||
message: '#^Method MokoCli\\Plugins\\PythonPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/Plugins/PythonPlugin.php
|
||||
-
|
||||
message: '#^Method MokoCli\\Plugins\\WordPressPlugin\:\:getCommands\(\) return type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/Plugins/WordPressPlugin.php
|
||||
-
|
||||
message: '#^Call to static method platformPackageType\(\) on an unknown class MokoCli\\mokocliParser\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/RepositorySynchronizer.php
|
||||
-
|
||||
message: '#^Method MokoCli\\RepositorySynchronizer\:\:generateManifestXml\(\) has parameter \$repoInfo with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/RepositorySynchronizer.php
|
||||
-
|
||||
message: '#^Method MokoCli\\RepositorySynchronizer\:\:migrateManifest\(\) has parameter \$repoInfo with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/RepositorySynchronizer.php
|
||||
-
|
||||
message: '#^Method MokoCli\\RepositorySynchronizer\:\:migrateManifest\(\) has parameter \$summary with no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/RepositorySynchronizer.php
|
||||
-
|
||||
message: '#^Offset 1 on array\{non\-empty\-list\<array\{string, int\<\-1, max\>\}\>, non\-empty\-list\<array\{non\-empty\-string, int\<\-1, max\>\}\>\} in empty\(\) always exists and is not falsy\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/SecurityValidator.php
|
||||
-
|
||||
message: '#^Property MokoCli\\SourceResolver\:\:\$apiCache type has no value type specified in iterable type array\.$#'
|
||||
count: 1
|
||||
path: lib/Enterprise/SourceResolver.php
|
||||
-
|
||||
message: '#^Undefined variable\: \$defTotal$#'
|
||||
count: 3
|
||||
path: validate/check_version_consistency.php
|
||||
|
||||
@@ -10,7 +10,6 @@ parameters:
|
||||
paths:
|
||||
- lib
|
||||
- validate
|
||||
- automation
|
||||
- cli
|
||||
excludePaths:
|
||||
analyseAndScan:
|
||||
|
||||
@@ -63,7 +63,7 @@ class VersionBumpTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"<!-- VERSION: 09.40.00 -->\nSome content\n"
|
||||
"<!-- VERSION: 09.42.00 -->\nSome content\n"
|
||||
);
|
||||
|
||||
$this->execute();
|
||||
|
||||
@@ -34,7 +34,7 @@ class VersionReadTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"# Test\n<!-- VERSION: 09.40.00 -->\n"
|
||||
"# Test\n<!-- VERSION: 09.42.00 -->\n"
|
||||
);
|
||||
|
||||
$this->assertSame('02.03.04', trim($this->runScript()));
|
||||
@@ -68,7 +68,7 @@ class VersionReadTest extends TestCase
|
||||
{
|
||||
file_put_contents(
|
||||
"{$this->tmpDir}/README.md",
|
||||
"<!-- VERSION: 09.40.00 -->\n"
|
||||
"<!-- VERSION: 09.42.00 -->\n"
|
||||
);
|
||||
mkdir("{$this->tmpDir}/src", 0755, true);
|
||||
file_put_contents(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
* PATH: /validate/check_file_integrity.php
|
||||
* VERSION: 09.40.00
|
||||
* VERSION: 09.42.00
|
||||
* BRIEF: Compare deployed files on a remote server against the local repository to detect drift
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user