Public Access
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e37443774b | |||
| e6b9a3b4f6 | |||
| d0200c28f0 | |||
| 1f946c0b75 | |||
| d4e2d36301 | |||
| a2c1a61759 | |||
| 0d12bca60e | |||
| 7cabc6e5ef | |||
| b73c1eba25 | |||
| e5ab1a4cd5 | |||
| f81873cf37 | |||
| d9e15b4f4a | |||
| 31b4bbca2a | |||
| a7f07b77a2 | |||
| 3e84bc9a18 | |||
| 7e8c96b629 | |||
| 2bf88d1d17 | |||
| 2160cd1ce7 | |||
| 6b61d9a211 | |||
| 1fbffbcb57 | |||
| f910a8ad12 | |||
| 5439df3876 | |||
| ab0c209897 | |||
| 17a50851fb | |||
| ef3614d249 | |||
| 3d5d581883 | |||
| bd5f676e0a | |||
| 847263dd86 | |||
| 6e540f64c4 | |||
| cf02738930 | |||
| 455d4c8a19 | |||
| 8286d493b9 | |||
| b740152d67 | |||
| a59091e348 | |||
| ee3d58c20f |
@@ -1,4 +1,4 @@
|
||||
# moko-platform
|
||||
# mokoplatform
|
||||
|
||||
Enterprise automation, validation, sync, and governance engine for all Moko Consulting repositories.
|
||||
|
||||
@@ -9,7 +9,7 @@ Enterprise automation, validation, sync, and governance engine for all Moko Cons
|
||||
| **Language** | PHP 8.1+ |
|
||||
| **Version** | 09.01.00 |
|
||||
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
||||
| **Wiki** | [moko-platform Wiki](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki) |
|
||||
| **Wiki** | [mokoplatform Wiki](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki) |
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -73,4 +73,4 @@ PHPStan runs with `--memory-limit=512M`. CI enforces PHPCS errors; PHPStan is `c
|
||||
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
|
||||
- **Wiki**: documentation lives in the Gitea wiki, not `docs/` files
|
||||
- **New CLI tools**: extend `CliFramework`, not `CLIApp` (legacy)
|
||||
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
|
||||
- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform/wiki/Home)
|
||||
|
||||
@@ -7,8 +7,8 @@ contact_links:
|
||||
- name: 💬 Ask a Question
|
||||
url: https://mokoconsulting.tech/
|
||||
about: Get help or ask questions through our website
|
||||
- name: 📚 moko-platform Documentation
|
||||
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
- name: 📚 mokoplatform Documentation
|
||||
url: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
about: View our coding standards and best practices
|
||||
- name: 🔒 Report a Security Vulnerability
|
||||
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
|
||||
|
||||
@@ -42,7 +42,7 @@ Suggested text here
|
||||
<!-- Add any other context, screenshots, or references -->
|
||||
|
||||
## Standards Alignment
|
||||
- [ ] Follows moko-platform documentation guidelines
|
||||
- [ ] Follows mokoplatform documentation guidelines
|
||||
- [ ] Uses en_US/en_GB localization
|
||||
- [ ] Includes proper SPDX headers where applicable
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ If you have ideas about how this could be implemented, share them here:
|
||||
Add any other context, mockups, or screenshots about the feature request here.
|
||||
|
||||
## Relevant Standards
|
||||
Does this relate to any standards in [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform)?
|
||||
Does this relate to any standards in [mokoplatform](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform)?
|
||||
- [ ] Accessibility (WCAG 2.1 AA)
|
||||
- [ ] Localization (en_US/en_GB)
|
||||
- [ ] Security best practices
|
||||
|
||||
@@ -35,7 +35,7 @@ Use this template only for:
|
||||
<!-- Describe how this could be addressed -->
|
||||
|
||||
## Standards Reference
|
||||
Does this relate to security standards in [moko-platform](https://git.mokoconsulting.tech/MokoConsulting/moko-platform)?
|
||||
Does this relate to security standards in [mokoplatform](https://git.mokoconsulting.tech/MokoConsulting/mokoplatform)?
|
||||
- [ ] SPDX license identifiers
|
||||
- [ ] Secret management
|
||||
- [ ] Dependency security
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.gitea/workflows/branch-protection.yml
|
||||
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
||||
#
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
API="${GITEA_URL}/api/v1"
|
||||
|
||||
# Platform/standards/infra repos to exclude
|
||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private moko-platform MokoTesting"
|
||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokoplatform MokoTesting"
|
||||
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
||||
|
||||
if [ -n "${{ inputs.repos }}" ]; then
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.gitea/workflows/bulk-repo-sync.yml
|
||||
# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: moko-platform.CI
|
||||
# INGROUP: moko-platform
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# DEFGROUP: mokoplatform.CI
|
||||
# INGROUP: mokoplatform
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.gitea/workflows/pr-branch-check.yml
|
||||
# BRIEF: PR branch merge policy enforcement
|
||||
#
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Automation
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.gitea/workflows/renovate.yml
|
||||
# BRIEF: Run Renovate Bot across all governed repos for dependency updates
|
||||
#
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
run: |
|
||||
API="${GITEA_URL}/api/v1"
|
||||
|
||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private moko-platform MokoTesting"
|
||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private mokoplatform MokoTesting"
|
||||
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
||||
|
||||
if [ -n "${{ inputs.repos }}" ]; then
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.gitea/workflows/sync-wikis.yml
|
||||
# BRIEF: Daily sync of all Gitea wikis to consolidated GitHub wiki repo
|
||||
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.23.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 moko-platform tools
|
||||
run: |
|
||||
if [ -f "/opt/moko-platform/cli/version_bump.php" ] && [ -f "/opt/moko-platform/vendor/autoload.php" ]; then
|
||||
echo "Using pre-installed /opt/moko-platform"
|
||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
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
|
||||
rm -rf /tmp/moko-platform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
||||
/tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/moko-platform-api/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: mokoplatform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.23.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 mokoplatform tools
|
||||
run: |
|
||||
if [ -f "/opt/mokoplatform/cli/version_bump.php" ] && [ -f "/opt/mokoplatform/vendor/autoload.php" ]; then
|
||||
echo "Using pre-installed /opt/mokoplatform"
|
||||
echo "MOKO_CLI=/opt/mokoplatform/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
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
|
||||
rm -rf /tmp/mokoplatform-api
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokoplatform.git" \
|
||||
/tmp/mokoplatform-api
|
||||
cd /tmp/mokoplatform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/mokoplatform-api/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,324 +1,324 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# 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, closed]
|
||||
branches:
|
||||
- main
|
||||
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_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 moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/moko-platform
|
||||
echo MOKO_CLI=/opt/moko-platform/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/moko-platform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/moko-platform-api/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: 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 moko-platform 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/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/moko-platform
|
||||
echo MOKO_CLI=/opt/moko-platform/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/moko-platform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability stable --bump minor --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||
else
|
||||
NOTES="Stable release"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/stable" | 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 ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
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: mokoplatform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform
|
||||
# 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, closed]
|
||||
branches:
|
||||
- main
|
||||
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_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 mokoplatform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
if [ -f /opt/mokoplatform/cli/version_bump.php ] && [ -f /opt/mokoplatform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokoplatform
|
||||
echo MOKO_CLI=/opt/mokoplatform/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/mokoplatform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokoplatform-api
|
||||
cd /tmp/mokoplatform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokoplatform-api/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: 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 mokoplatform 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/mokoplatform/cli/version_bump.php ] && [ -f /opt/mokoplatform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokoplatform
|
||||
echo MOKO_CLI=/opt/mokoplatform/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/mokoplatform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokoplatform-api
|
||||
cd /tmp/mokoplatform-api
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokoplatform-api/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: "Publish stable release"
|
||||
run: |
|
||||
php ${MOKO_CLI}/release_publish.php \
|
||||
--path . --stability stable --bump minor --branch main \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||
else
|
||||
NOTES="Stable release"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/stable" | 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 ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
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
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoPlatform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Delete feature branches after PR merge
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||
# PATH: /.gitea/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:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev/**
|
||||
- rc/**
|
||||
- version/**
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev/**
|
||||
- rc/**
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
# ── Lint & Validate ───────────────────────────────────────────────────
|
||||
lint:
|
||||
name: Lint & Validate
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Detect toolchain
|
||||
id: detect
|
||||
run: |
|
||||
HAS_PHP=false
|
||||
HAS_NODE=false
|
||||
[ -f "composer.json" ] && HAS_PHP=true
|
||||
[ -f "package.json" ] && HAS_NODE=true
|
||||
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
||||
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
||||
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
|
||||
|
||||
- name: Setup PHP
|
||||
if: steps.detect.outputs.has_php == 'true'
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||
fi
|
||||
php -v
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.detect.outputs.has_node == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install PHP dependencies
|
||||
if: steps.detect.outputs.has_php == 'true'
|
||||
run: |
|
||||
if [ -f "composer.json" ]; then
|
||||
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
if: steps.detect.outputs.has_node == 'true'
|
||||
run: |
|
||||
if [ -f "package.json" ]; then
|
||||
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: PHP syntax check
|
||||
if: steps.detect.outputs.has_php == 'true'
|
||||
run: |
|
||||
ERRORS=0
|
||||
while IFS= read -r -d '' file; do
|
||||
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||
echo "::error file=${file}::PHP syntax error"
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
|
||||
|
||||
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$ERRORS" -eq 0 ]; then
|
||||
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: TypeScript/JavaScript lint
|
||||
if: steps.detect.outputs.has_node == 'true'
|
||||
run: |
|
||||
if [ -f "node_modules/.bin/eslint" ]; then
|
||||
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
|
||||
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
|
||||
echo "::warning::ESLint config found but eslint not installed"
|
||||
else
|
||||
echo "No ESLint configured — skipping"
|
||||
fi
|
||||
|
||||
- name: TypeScript compile check
|
||||
if: steps.detect.outputs.has_node == 'true'
|
||||
run: |
|
||||
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
|
||||
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
|
||||
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
|
||||
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: PHPStan static analysis
|
||||
if: steps.detect.outputs.has_php == 'true'
|
||||
run: |
|
||||
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
|
||||
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
|
||||
fi
|
||||
|
||||
# ── Tests ─────────────────────────────────────────────────────────────
|
||||
test:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Detect toolchain
|
||||
id: detect
|
||||
run: |
|
||||
HAS_PHP=false
|
||||
HAS_NODE=false
|
||||
[ -f "composer.json" ] && HAS_PHP=true
|
||||
[ -f "package.json" ] && HAS_NODE=true
|
||||
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
||||
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup PHP
|
||||
if: steps.detect.outputs.has_php == 'true'
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: Setup Node.js
|
||||
if: steps.detect.outputs.has_node == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
||||
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; }
|
||||
|
||||
- name: Run PHP tests
|
||||
if: steps.detect.outputs.has_php == 'true'
|
||||
run: |
|
||||
if [ -f "vendor/bin/phpunit" ]; then
|
||||
vendor/bin/phpunit --testdox 2>&1
|
||||
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
||||
echo "::warning::PHPUnit config found but phpunit not installed"
|
||||
else
|
||||
echo "No PHPUnit configured — skipping"
|
||||
fi
|
||||
|
||||
- name: Run Node.js tests
|
||||
if: steps.detect.outputs.has_node == 'true'
|
||||
run: |
|
||||
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
|
||||
npm test 2>&1
|
||||
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "No test script in package.json — skipping"
|
||||
fi
|
||||
|
||||
- name: Build check
|
||||
run: |
|
||||
if [ -f "Makefile" ]; then
|
||||
make build 2>&1 || echo "::warning::Build failed or not configured"
|
||||
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
|
||||
npm run build 2>&1 || echo "::warning::Build failed"
|
||||
fi
|
||||
@@ -4,18 +4,18 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.mokogitea/workflows/ci-platform.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: moko-platform CI — the standards engine validates itself
|
||||
# BRIEF: mokoplatform CI — the standards engine validates itself
|
||||
#
|
||||
# +========================================================================+
|
||||
# | MOKO-PLATFORM CI |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | This is NOT a generic CI workflow. This is the self-validation |
|
||||
# | pipeline for the central moko-platform enterprise engine. |
|
||||
# | pipeline for the central mokoplatform enterprise engine. |
|
||||
# | |
|
||||
# | It dogfoods every tool the platform ships to governed repos: |
|
||||
# | |
|
||||
@@ -29,7 +29,7 @@
|
||||
# | |
|
||||
# +========================================================================+
|
||||
|
||||
name: "Platform: moko-platform CI"
|
||||
name: "Platform: mokoplatform CI"
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -421,7 +421,7 @@ jobs:
|
||||
- name: Check gate results
|
||||
run: |
|
||||
{
|
||||
echo "# moko-platform CI"
|
||||
echo "# mokoplatform CI"
|
||||
echo ""
|
||||
echo "| Gate | Job | Status |"
|
||||
echo "|---|---|---|"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Maintenance
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.mokogitea/workflows/cleanup.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# 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
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Security
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# INGROUP: mokoplatform.Security
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform
|
||||
# PATH: /templates/workflows/gitleaks.yml.template
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Automation
|
||||
# VERSION: 09.25.02
|
||||
# INGROUP: mokoplatform.Automation
|
||||
# VERSION: 09.28.00
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Notifications
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Notifications
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.mokogitea/workflows/notify.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# INGROUP: mokoplatform.CI
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform
|
||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: PR gate — branch policy + code validation before merge
|
||||
@@ -172,8 +172,7 @@ jobs:
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
MISSING=0
|
||||
SOURCE_DIR="source"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="src"
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
while IFS= read -r dir; do
|
||||
if [ ! -f "${dir}/index.html" ]; then
|
||||
@@ -221,7 +220,7 @@ jobs:
|
||||
echo "joomla.asset.json: valid"
|
||||
fi
|
||||
|
||||
# Validate all XML files in source/src/ are well-formed
|
||||
# Validate all XML files in src/ are well-formed
|
||||
XML_ERRORS=0
|
||||
if command -v php &> /dev/null; then
|
||||
while IFS= read -r -d '' xmlfile; do
|
||||
@@ -452,11 +451,10 @@ jobs:
|
||||
|
||||
- name: Verify package source
|
||||
run: |
|
||||
SOURCE_DIR="source"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="src"
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
if [ ! -d "$SOURCE_DIR" ]; then
|
||||
echo "::warning::No source/, src/, or htdocs/ directory"
|
||||
echo "::warning::No src/ or htdocs/ directory"
|
||||
exit 0
|
||||
fi
|
||||
FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l)
|
||||
|
||||
@@ -1,243 +1,243 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
pull_request_target:
|
||||
types: [synchronize, opened, reopened]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
|
||||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
|
||||
if [ -f /opt/moko-platform/cli/version_bump.php ] && [ -f /opt/moko-platform/cli/manifest_element.php ] && [ -f /opt/moko-platform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/moko-platform
|
||||
echo MOKO_CLI=/opt/moko-platform/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/moko-platform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/moko-platform-api
|
||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/moko-platform-api/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
|
||||
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
|
||||
STABILITY="release-candidate"
|
||||
else
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
fi
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
|
||||
case "$STABILITY" in
|
||||
release-candidate) BUMP="minor" ;;
|
||||
*) BUMP="patch" ;;
|
||||
esac
|
||||
|
||||
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
|
||||
|
||||
# Set stability suffix and verify consistency
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
||||
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Append suffix for output
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
# Commit version bump
|
||||
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"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
else
|
||||
NOTES="Release ${VERSION}"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/${TAG}" | 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 ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
# updates.xml is generated dynamically by MokoGitea license server
|
||||
# No need to build, commit, or sync updates.xml from workflows
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokoplatform.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- dev
|
||||
pull_request_target:
|
||||
types: [synchronize, opened, reopened]
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Pre-release channel'
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- release-candidate
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
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 }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
|
||||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: Setup mokoplatform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
run: |
|
||||
# Use pre-installed /opt/mokoplatform if available (updated by cron every 6h)
|
||||
if [ -f /opt/mokoplatform/cli/version_bump.php ] && [ -f /opt/mokoplatform/cli/manifest_element.php ] && [ -f /opt/mokoplatform/vendor/autoload.php ]; then
|
||||
echo Using pre-installed /opt/mokoplatform
|
||||
echo MOKO_CLI=/opt/mokoplatform/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/mokoplatform-api
|
||||
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git
|
||||
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokoplatform-api
|
||||
cd /tmp/mokoplatform-api && composer install --no-dev --no-interaction --quiet
|
||||
echo MOKO_CLI=/tmp/mokoplatform-api/cli >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: |
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
run: |
|
||||
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
|
||||
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
|
||||
STABILITY="release-candidate"
|
||||
else
|
||||
STABILITY="${{ inputs.stability || 'development' }}"
|
||||
fi
|
||||
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
esac
|
||||
|
||||
# Bump version via CLI: patch for dev/alpha/beta, minor for RC
|
||||
case "$STABILITY" in
|
||||
release-candidate) BUMP="minor" ;;
|
||||
*) BUMP="patch" ;;
|
||||
esac
|
||||
|
||||
php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true
|
||||
|
||||
# Set stability suffix and verify consistency
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01")
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
||||
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Append suffix for output
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
# Commit version bump
|
||||
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"
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||
git push origin HEAD 2>&1
|
||||
}
|
||||
|
||||
# Auto-detect element via manifest_element.php
|
||||
php ${MOKO_CLI}/manifest_element.php \
|
||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||
--repo "${GITEA_REPO}" --github-output
|
||||
|
||||
# Read back element outputs
|
||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
|
||||
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
else
|
||||
NOTES="Release ${VERSION}"
|
||||
fi
|
||||
|
||||
# Update release body via API
|
||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/releases/tags/${TAG}" | 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 ${{ secrets.MOKOGITEA_TOKEN }}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
urllib.request.urlopen(req)
|
||||
" <<< "$NOTES"
|
||||
echo "Release notes updated from CHANGELOG.md"
|
||||
fi
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
# updates.xml is generated dynamically by MokoGitea license server
|
||||
# No need to build, commit, or sync updates.xml from workflows
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
php ${MOKO_CLI}/release_cascade.php \
|
||||
--stability "${{ steps.meta.outputs.stability }}" \
|
||||
--token "${TOKEN}" \
|
||||
--api-base "${API_BASE}"
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoPlatform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /.mokogitea/workflows/rc-revert.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
||||
|
||||
name: "RC Revert"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
revert:
|
||||
name: Rename rc/ back to dev/
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == false &&
|
||||
startsWith(github.event.pull_request.head.ref, 'rc/')
|
||||
|
||||
steps:
|
||||
- name: Rename branch
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
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 }}"
|
||||
|
||||
# Create dev/ branch from rc/ branch
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-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
|
||||
else
|
||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Delete rc/ branch
|
||||
ENCODED=$(php -r "echo rawurlencode('${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
|
||||
else
|
||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
|
||||
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -7,8 +7,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Validation
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
||||
# INGROUP: mokoplatform.Validation
|
||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform
|
||||
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
||||
@@ -296,19 +296,17 @@ jobs:
|
||||
missing_required=()
|
||||
missing_optional=()
|
||||
|
||||
# Source directory: source/, src/, or htdocs/ (any is valid for extension repos)
|
||||
# Source directory: src/ or htdocs/ (either is valid for extension repos)
|
||||
SOURCE_DIR=""
|
||||
if [ -d "source" ]; then
|
||||
SOURCE_DIR="source"
|
||||
elif [ -d "src" ]; then
|
||||
if [ -d "src" ]; then
|
||||
SOURCE_DIR="src"
|
||||
elif [ -d "htdocs" ]; then
|
||||
SOURCE_DIR="htdocs"
|
||||
elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then
|
||||
# Platform/tooling repos don't need source/
|
||||
# Platform/tooling repos don't need src/
|
||||
SOURCE_DIR=""
|
||||
else
|
||||
missing_required+=("source/ or src/ or htdocs/ (source directory required)")
|
||||
missing_required+=("src/ or htdocs/ (source directory required)")
|
||||
fi
|
||||
|
||||
for item in "${required_artifacts[@]}"; do
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# INGROUP: mokoplatform.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
# PATH: /.mokogitea/workflows/security-audit.yml
|
||||
# VERSION: 09.23.00
|
||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"metadata": {
|
||||
"generated_at": "2026-03-10T19:51:42.238134Z",
|
||||
"repository": "MokoConsulting/moko-platform",
|
||||
"repository": "MokoConsulting/mokoplatform",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"scripts": [
|
||||
|
||||
+17
-19
@@ -4,7 +4,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoStandards.Root
|
||||
INGROUP: MokoStandards
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
PATH: /CHANGELOG.md
|
||||
BRIEF: Release changelog
|
||||
-->
|
||||
@@ -12,6 +12,12 @@ BRIEF: Release changelog
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [09.28.00] --- 2026-06-07
|
||||
|
||||
## [09.27.00] --- 2026-06-07
|
||||
|
||||
## [09.26.00] --- 2026-06-07
|
||||
|
||||
### Added
|
||||
- `workflow_sync.php` — cascading workflow sync from Generic → platform templates → live repos based on manifest.platform
|
||||
- `platform_detect.php` — auto-detect repo platform type (joomla/dolibarr/go/mcp/platform/generic) from file structure, optionally update manifest
|
||||
@@ -24,22 +30,14 @@ BRIEF: Release changelog
|
||||
- `pre-release.yml` — triggers on push to dev, fix/**, patch/**, hotfix/**, bugfix/**, alpha, beta, rc branches
|
||||
- Version format standardized: `[prefix]XX.YY.ZZ` in source files, suffix (`-dev`, `-rc`) added by release system only
|
||||
|
||||
## [09.26.00] --- 2026-06-07
|
||||
|
||||
### Added
|
||||
- `cli/manifest_detect.php` — auto-detect manifest fields from source files (Joomla, Dolibarr, Go, MCP/Node, generic)
|
||||
- Supports `--json`, `--diff`, `--update`, `--github-output` modes
|
||||
- Warns on missing core fields (platform, name, version, package_type, language, entry_point)
|
||||
|
||||
### Removed
|
||||
- `mcp/servers/mokowaas_api/` — consolidated into mcp-mokowaas-api repo
|
||||
|
||||
## [09.25.00] --- 2026-06-04
|
||||
|
||||
## [09.23] --- 2026-05-31
|
||||
|
||||
## [09.22] --- 2026-05-31
|
||||
|
||||
### Changed
|
||||
- **refactor(cli):** migrate 64 legacy scripts to CliFramework (#235) — all tools in cli/, automation/, maintenance/, deploy/, release/ now extend CliFramework with free --help, --verbose, --quiet, --dry-run, --json, banners, and coloured logging
|
||||
|
||||
### Fixed
|
||||
- fix: auto-detect org/repo in updates_xml_build from manifest and git remote
|
||||
- fix: restore hyphen in version suffixes
|
||||
- fix: release names use standardized format
|
||||
- fix: remove lesser stream copies, each stream updates independently
|
||||
- fix: sort updates.xml entries dev first, stable last
|
||||
|
||||
## [09.21] --- 2026-05-30
|
||||
|
||||
## [09.20] --- 2026-05-30
|
||||
|
||||
+2
-2
@@ -4,14 +4,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoPlatform.Root
|
||||
INGROUP: MokoPlatform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
PATH: /PLUGIN_SCRIPTS.md
|
||||
BRIEF: Plugin system CLI documentation
|
||||
-->
|
||||
|
||||
# Plugin System CLI Scripts
|
||||
|
||||
Command-line scripts for validating, health checking, and managing projects using the moko-platform plugin system.
|
||||
Command-line scripts for validating, health checking, and managing projects using the mokoplatform plugin system.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
|
||||
@@ -4,17 +4,17 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoPlatform.Root
|
||||
INGROUP: MokoPlatform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
PATH: /README.md
|
||||
VERSION: 09.25.02
|
||||
VERSION: 09.28.00
|
||||
BRIEF: Project overview and documentation
|
||||
-->
|
||||
|
||||
# moko-platform Enterprise API
|
||||
# mokoplatform Enterprise API
|
||||
|
||||
  
|
||||
|
||||
PHP implementation of moko-platform — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
|
||||
PHP implementation of mokoplatform — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
|
||||
|
||||
> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API)
|
||||
> **Backup mirror**: [GitHub](https://github.com/MokoConsulting/MokoStandards-API) *(read-only mirror)*
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoPlatform.Index
|
||||
INGROUP: MokoPlatform.Analysis
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
PATH: /analysis/index.md
|
||||
BRIEF: Analysis directory index
|
||||
-->
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform.Scripts
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/bulk_joomla_template.php
|
||||
* BRIEF: Bulk scaffold and sync Joomla template repositories
|
||||
*
|
||||
@@ -42,7 +42,7 @@ use MokoEnterprise\{
|
||||
*
|
||||
* Provides three operations for Joomla template projects:
|
||||
* --scaffold: Create a new template repository with the full directory structure
|
||||
* --sync: Push moko-platform files to existing template repositories
|
||||
* --sync: Push mokoplatform files to existing template repositories
|
||||
* --list: List all repositories tagged as joomla-template
|
||||
*
|
||||
* Works with both GitHub and Gitea via the PlatformAdapterFactory.
|
||||
@@ -318,7 +318,7 @@ class BulkJoomlaTemplate extends CliFramework
|
||||
$name,
|
||||
$path,
|
||||
$content,
|
||||
"chore: update {$path} from moko-platform",
|
||||
"chore: update {$path} from mokoplatform",
|
||||
$existingSha,
|
||||
$branch
|
||||
);
|
||||
|
||||
+40
-40
@@ -11,7 +11,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform.Scripts
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/bulk_sync.php
|
||||
* BRIEF: Enterprise-grade bulk repository synchronization
|
||||
*/
|
||||
@@ -42,7 +42,7 @@ use MokoEnterprise\{
|
||||
/**
|
||||
* Bulk Repository Synchronization Tool
|
||||
*
|
||||
* Synchronizes moko-platform files across multiple repositories using
|
||||
* Synchronizes mokoplatform files across multiple repositories using
|
||||
* the Enterprise library for robust, audited operations.
|
||||
*/
|
||||
class BulkSync extends CliFramework
|
||||
@@ -95,7 +95,7 @@ class BulkSync extends CliFramework
|
||||
*/
|
||||
protected function run(): int
|
||||
{
|
||||
$this->log("🚀 moko-platform Bulk Synchronization v" . self::VERSION, 'INFO');
|
||||
$this->log("🚀 mokoplatform Bulk Synchronization v" . self::VERSION, 'INFO');
|
||||
|
||||
// Initialize enterprise components
|
||||
if (!$this->initializeComponents()) {
|
||||
@@ -180,7 +180,7 @@ class BulkSync extends CliFramework
|
||||
$results['health'] = $this->runHealthChecksAll($org, $repositories);
|
||||
}
|
||||
|
||||
// Create/update tracking issue in moko-platform
|
||||
// Create/update tracking issue in mokoplatform
|
||||
$this->createSyncIssue($org, $results);
|
||||
|
||||
// Create/update a failure issue when any repos failed
|
||||
@@ -244,7 +244,7 @@ class BulkSync extends CliFramework
|
||||
* Filter repositories based on include/exclude lists
|
||||
*/
|
||||
/** Repositories that are permanently excluded from bulk sync. */
|
||||
private const ALWAYS_EXCLUDE = ['moko-platform', '.github-private'];
|
||||
private const ALWAYS_EXCLUDE = ['mokoplatform', '.github-private'];
|
||||
|
||||
private function filterRepositories(array $repositories, array $include, array $exclude): array
|
||||
{
|
||||
@@ -426,7 +426,7 @@ class BulkSync extends CliFramework
|
||||
$this->log("", 'ERROR');
|
||||
$this->log("Required Implementation:", 'ERROR');
|
||||
$this->log(" 1. Clone/fetch target repository", 'ERROR');
|
||||
$this->log(" 2. Apply file updates based on moko-platform configuration", 'ERROR');
|
||||
$this->log(" 2. Apply file updates based on mokoplatform configuration", 'ERROR');
|
||||
$this->log(" 3. Create pull request with changes", 'ERROR');
|
||||
$this->log(" 4. Handle merge conflicts and validation", 'ERROR');
|
||||
$this->log("", 'ERROR');
|
||||
@@ -837,7 +837,7 @@ class BulkSync extends CliFramework
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure all standard moko-platform labels exist on a target repository.
|
||||
* Ensure all standard mokoplatform labels exist on a target repository.
|
||||
*
|
||||
* Fetches existing labels first (GET) and only POSTs the ones that are
|
||||
* missing. This avoids the 422 "already exists" responses that would
|
||||
@@ -872,7 +872,7 @@ class BulkSync extends CliFramework
|
||||
|
||||
// Workflow / Process
|
||||
['automation', '8B4513', 'Automated processes or scripts'],
|
||||
['moko-platform', 'B60205', 'moko-platform compliance'],
|
||||
['mokoplatform', 'B60205', 'mokoplatform compliance'],
|
||||
['needs-review', 'FBCA04', 'Awaiting code review'],
|
||||
['work-in-progress', 'D93F0B', 'Work in progress, not ready for merge'],
|
||||
['breaking-change', 'D73A4A', 'Breaking API or functionality change'],
|
||||
@@ -912,8 +912,8 @@ class BulkSync extends CliFramework
|
||||
['health: poor', 'FF6B6B', 'Health score below 50'],
|
||||
|
||||
// Sync / Automation (used by bulk_sync, scan_drift, check_repo_health)
|
||||
['standards-update', 'B60205', 'moko-platform sync update'],
|
||||
['standards-drift', 'FBCA04', 'Repository drifted from moko-platform'],
|
||||
['standards-update', 'B60205', 'mokoplatform sync update'],
|
||||
['standards-drift', 'FBCA04', 'Repository drifted from mokoplatform'],
|
||||
['sync-report', '0075CA', 'Bulk sync run report'],
|
||||
['sync-failure', 'D73A4A', 'Bulk sync failure requiring attention'],
|
||||
['push-failure', 'D73A4A', 'File push failure requiring attention'],
|
||||
@@ -925,10 +925,10 @@ class BulkSync extends CliFramework
|
||||
['type: version', '0E8A16', 'Version-related change'],
|
||||
];
|
||||
|
||||
// Quick check: if the repo already has the 'moko-platform' label, it was
|
||||
// Quick check: if the repo already has the 'mokoplatform' label, it was
|
||||
// provisioned previously — skip the expensive full label provisioning.
|
||||
try {
|
||||
$probe = $this->api->get("/repos/{$org}/{$repo}/labels/moko-platform");
|
||||
$probe = $this->api->get("/repos/{$org}/{$repo}/labels/mokoplatform");
|
||||
if (!empty($probe['name'])) {
|
||||
return; // already provisioned
|
||||
}
|
||||
@@ -1024,7 +1024,7 @@ class BulkSync extends CliFramework
|
||||
*/
|
||||
private function updateOpenBranches(string $org, string $repo): void
|
||||
{
|
||||
$syncBranchPrefix = 'chore/sync-moko-platform-';
|
||||
$syncBranchPrefix = 'chore/sync-mokoplatform-';
|
||||
|
||||
try {
|
||||
$defaultBranch = 'main';
|
||||
@@ -1055,7 +1055,7 @@ class BulkSync extends CliFramework
|
||||
$this->api->post("/repos/{$org}/{$repo}/merges", [
|
||||
'base' => $branch,
|
||||
'head' => $defaultBranch,
|
||||
'commit_message' => "chore: merge {$defaultBranch} into {$branch} (moko-platform sync)",
|
||||
'commit_message' => "chore: merge {$defaultBranch} into {$branch} (mokoplatform sync)",
|
||||
]);
|
||||
$this->log(" 🔀 Merged {$defaultBranch} → {$branch} (PR #{$prNum})", 'INFO');
|
||||
} catch (\Exception $e) {
|
||||
@@ -1076,7 +1076,7 @@ class BulkSync extends CliFramework
|
||||
|
||||
/**
|
||||
* Records which sync run touched the repo, the PR number, and the
|
||||
* moko-platform version that was applied — giving each repo a clear audit
|
||||
* mokoplatform version that was applied — giving each repo a clear audit
|
||||
* trail of what was changed and why.
|
||||
*/
|
||||
/**
|
||||
@@ -1119,16 +1119,16 @@ class BulkSync extends CliFramework
|
||||
$minor = self::VERSION_MINOR;
|
||||
$force = isset($this->options['force']) ? ' *(--force)*' : '';
|
||||
$prLink = $this->adapter->getPullRequestWebUrl($org, $repo, $prNumber);
|
||||
$source = $this->adapter->getRepoWebUrl($org, 'moko-platform');
|
||||
$branchName = 'chore/sync-moko-platform-v' . $minor;
|
||||
$source = $this->adapter->getRepoWebUrl($org, 'mokoplatform');
|
||||
$branchName = 'chore/sync-mokoplatform-v' . $minor;
|
||||
$branchLink = $this->adapter->getBranchWebUrl($org, $repo, $branchName);
|
||||
|
||||
$title = "chore: moko-platform v{$minor} sync tracking";
|
||||
$title = "chore: mokoplatform v{$minor} sync tracking";
|
||||
|
||||
$body = <<<MD
|
||||
## moko-platform Sync Applied
|
||||
## mokoplatform Sync Applied
|
||||
|
||||
A moko-platform bulk sync run has updated files in this repository.
|
||||
A mokoplatform bulk sync run has updated files in this repository.
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
@@ -1144,13 +1144,13 @@ class BulkSync extends CliFramework
|
||||
Protected files (README, CHANGELOG, GOVERNANCE, etc.) were not overwritten.
|
||||
|
||||
---
|
||||
*Updated automatically by [moko-platform]({$source}) `bulk_sync.php`*
|
||||
*Updated automatically by [mokoplatform]({$source}) `bulk_sync.php`*
|
||||
MD;
|
||||
|
||||
// Dedent heredoc
|
||||
$body = preg_replace('/^ /m', '', $body);
|
||||
|
||||
$labelNames = ['standards-update', 'moko-platform', 'type: chore', 'automation'];
|
||||
$labelNames = ['standards-update', 'mokoplatform', 'type: chore', 'automation'];
|
||||
$labels = $this->resolveLabelIds($org, $repo, $labelNames);
|
||||
|
||||
try {
|
||||
@@ -1213,7 +1213,7 @@ class BulkSync extends CliFramework
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tracking issue in moko-platform for this sync run.
|
||||
* Create a tracking issue in mokoplatform for this sync run.
|
||||
*/
|
||||
private function createSyncIssue(string $org, array $results): void
|
||||
{
|
||||
@@ -1232,7 +1232,7 @@ class BulkSync extends CliFramework
|
||||
$issues = $results['issues'] ?? [];
|
||||
|
||||
// Stable title — no timestamp so repeated runs update a single issue
|
||||
$title = "sync: moko-platform v" . self::VERSION_MINOR . " bulk sync report";
|
||||
$title = "sync: mokoplatform v" . self::VERSION_MINOR . " bulk sync report";
|
||||
|
||||
$protection = $results['protection'] ?? [];
|
||||
$hasProtect = !empty($protection);
|
||||
@@ -1281,7 +1281,7 @@ class BulkSync extends CliFramework
|
||||
: "|---|---|---|---|";
|
||||
|
||||
$body = <<<MD
|
||||
## moko-platform Bulk Sync Report
|
||||
## mokoplatform Bulk Sync Report
|
||||
|
||||
**Organisation:** `{$org}`
|
||||
**Triggered:** {$now}{$force}
|
||||
@@ -1301,7 +1301,7 @@ class BulkSync extends CliFramework
|
||||
|
||||
try {
|
||||
// Search for existing issue by label — any state so we can reopen closed ones
|
||||
$existing = $this->api->get("/repos/{$org}/moko-platform/issues", [
|
||||
$existing = $this->api->get("/repos/{$org}/mokoplatform/issues", [
|
||||
'labels' => 'sync-report',
|
||||
'state' => 'all',
|
||||
'per_page' => 1,
|
||||
@@ -1309,8 +1309,8 @@ class BulkSync extends CliFramework
|
||||
'direction' => 'desc',
|
||||
]);
|
||||
|
||||
$labelNames = ['sync-report', 'moko-platform', 'type: chore', 'automation'];
|
||||
$labels = $this->resolveLabelIds($org, 'moko-platform', $labelNames);
|
||||
$labelNames = ['sync-report', 'mokoplatform', 'type: chore', 'automation'];
|
||||
$labels = $this->resolveLabelIds($org, 'mokoplatform', $labelNames);
|
||||
$existing = array_values($existing);
|
||||
|
||||
if (!empty($existing) && isset($existing[0]['number'])) {
|
||||
@@ -1319,22 +1319,22 @@ class BulkSync extends CliFramework
|
||||
if (($existing[0]['state'] ?? 'open') === 'closed') {
|
||||
$patch['state'] = 'open';
|
||||
}
|
||||
$this->api->patch("/repos/{$org}/moko-platform/issues/{$issueNumber}", $patch);
|
||||
$this->api->patch("/repos/{$org}/mokoplatform/issues/{$issueNumber}", $patch);
|
||||
try {
|
||||
$this->api->post("/repos/{$org}/moko-platform/issues/{$issueNumber}/labels", ['labels' => $labels]);
|
||||
$this->api->post("/repos/{$org}/mokoplatform/issues/{$issueNumber}/labels", ['labels' => $labels]);
|
||||
} catch (\Exception $le) {
|
||||
/* non-fatal */
|
||||
}
|
||||
$this->log("📋 Sync report issue updated: {$org}/moko-platform#{$issueNumber}", 'INFO');
|
||||
$this->log("📋 Sync report issue updated: {$org}/mokoplatform#{$issueNumber}", 'INFO');
|
||||
} else {
|
||||
$issue = $this->api->post("/repos/{$org}/moko-platform/issues", [
|
||||
$issue = $this->api->post("/repos/{$org}/mokoplatform/issues", [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
'labels' => $labels,
|
||||
'assignees' => ['jmiller'],
|
||||
]);
|
||||
$issueNumber = $issue['number'] ?? '?';
|
||||
$this->log("📋 Sync report issue created: {$org}/moko-platform#{$issueNumber}", 'INFO');
|
||||
$this->log("📋 Sync report issue created: {$org}/mokoplatform#{$issueNumber}", 'INFO');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->log("⚠️ Failed to create/update sync report issue: " . $e->getMessage(), 'WARN');
|
||||
@@ -1342,7 +1342,7 @@ class BulkSync extends CliFramework
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a failure issue in moko-platform when repos fail to sync.
|
||||
* Create or update a failure issue in mokoplatform when repos fail to sync.
|
||||
* Uses the 'sync-failure' label so it is distinct from the run-report issue.
|
||||
* Reopens a closed issue rather than creating a duplicate.
|
||||
*/
|
||||
@@ -1388,7 +1388,7 @@ class BulkSync extends CliFramework
|
||||
$body = preg_replace('/^ /m', '', $body);
|
||||
|
||||
try {
|
||||
$existing = $this->api->get("/repos/{$org}/moko-platform/issues", [
|
||||
$existing = $this->api->get("/repos/{$org}/mokoplatform/issues", [
|
||||
'labels' => 'sync-failure',
|
||||
'state' => 'all',
|
||||
'per_page' => 1,
|
||||
@@ -1403,17 +1403,17 @@ class BulkSync extends CliFramework
|
||||
if (($existing[0]['state'] ?? 'open') === 'closed') {
|
||||
$patch['state'] = 'open';
|
||||
}
|
||||
$this->api->patch("/repos/{$org}/moko-platform/issues/{$num}", $patch);
|
||||
$this->log("🚨 Failure issue #{$num} updated: {$org}/moko-platform#{$num}", 'WARN');
|
||||
$this->api->patch("/repos/{$org}/mokoplatform/issues/{$num}", $patch);
|
||||
$this->log("🚨 Failure issue #{$num} updated: {$org}/mokoplatform#{$num}", 'WARN');
|
||||
} else {
|
||||
$issue = $this->api->post("/repos/{$org}/moko-platform/issues", [
|
||||
$issue = $this->api->post("/repos/{$org}/mokoplatform/issues", [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
'labels' => $this->resolveLabelIds($org, 'moko-platform', ['sync-failure']),
|
||||
'labels' => $this->resolveLabelIds($org, 'mokoplatform', ['sync-failure']),
|
||||
'assignees' => ['jmiller'],
|
||||
]);
|
||||
$num = $issue['number'] ?? '?';
|
||||
$this->log("🚨 Failure issue created: {$org}/moko-platform#{$num}", 'WARN');
|
||||
$this->log("🚨 Failure issue created: {$org}/mokoplatform#{$num}", 'WARN');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN');
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/enrich_manifest_xml.php
|
||||
* BRIEF: Enrich XML manifests with repo-specific build and deploy details
|
||||
*
|
||||
@@ -46,7 +46,7 @@ class EnrichManifestXmlCli extends CliFramework
|
||||
$parser = new MokoStandardsParser();
|
||||
$tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid();
|
||||
|
||||
echo "=== moko-platform XML Manifest Enrichment ===\n";
|
||||
echo "=== mokoplatform XML Manifest Enrichment ===\n";
|
||||
echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n";
|
||||
if (!empty($skipRepos)) {
|
||||
echo "Skipping: " . implode(', ', $skipRepos) . "\n";
|
||||
@@ -97,7 +97,7 @@ class EnrichManifestXmlCli extends CliFramework
|
||||
}
|
||||
|
||||
$manifestPath = "{$workDir}/.mokogitea/manifest.xml";
|
||||
if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), '<moko-platform')) {
|
||||
if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), '<mokoplatform')) {
|
||||
echo "SKIP (no XML manifest)\n";
|
||||
$stats['skipped']++;
|
||||
$this->rmTree($workDir);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/enrich_mokostandards_xml.php
|
||||
* BRIEF: Enrich XML manifests with repo-specific build and deploy details
|
||||
*
|
||||
@@ -46,7 +46,7 @@ class EnrichMokostandardsXmlCli extends CliFramework
|
||||
$parser = new MokoStandardsParser();
|
||||
$tmpBase = sys_get_temp_dir() . '/moko-enrich-' . getmypid();
|
||||
|
||||
echo "=== moko-platform XML Manifest Enrichment ===\n";
|
||||
echo "=== mokoplatform XML Manifest Enrichment ===\n";
|
||||
echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n";
|
||||
if (!empty($skipRepos)) {
|
||||
echo "Skipping: " . implode(', ', $skipRepos) . "\n";
|
||||
@@ -97,7 +97,7 @@ class EnrichMokostandardsXmlCli extends CliFramework
|
||||
}
|
||||
|
||||
$manifestPath = "{$workDir}/.mokogitea/manifest.xml";
|
||||
if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), '<moko-platform')) {
|
||||
if (!file_exists($manifestPath) || !str_contains(file_get_contents($manifestPath), '<mokoplatform')) {
|
||||
echo "SKIP (no XML manifest)\n";
|
||||
$stats['skipped']++;
|
||||
$this->rmTree($workDir);
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
FILE INFORMATION
|
||||
DEFGROUP: MokoPlatform.Index
|
||||
INGROUP: MokoPlatform.Automation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
PATH: /automation/index.md
|
||||
BRIEF: Automation directory index
|
||||
-->
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/migrate_to_gitea.php
|
||||
* BRIEF: Migrate repositories from GitHub to self-hosted Gitea instance
|
||||
*
|
||||
* USAGE
|
||||
* php automation/migrate_to_gitea.php --dry-run
|
||||
* php automation/migrate_to_gitea.php --repos MokoCRM MokoDoliMods
|
||||
* php automation/migrate_to_gitea.php --exclude moko-platform --skip-archived
|
||||
* php automation/migrate_to_gitea.php --exclude mokoplatform --skip-archived
|
||||
* php automation/migrate_to_gitea.php --resume
|
||||
*/
|
||||
|
||||
@@ -278,7 +278,7 @@ class MigrateToGitea extends CliFramework
|
||||
try {
|
||||
$this->gitea->createIssue(
|
||||
$giteaOrg,
|
||||
'moko-platform',
|
||||
'mokoplatform',
|
||||
'chore: GitHub → Gitea migration report — ' . count($results['migrated']) . ' repos migrated',
|
||||
$report,
|
||||
['labels' => ['automation', 'type: chore']]
|
||||
|
||||
+20
-20
@@ -11,7 +11,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform.Scripts
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/push_files.php
|
||||
* BRIEF: Push one or more specific files to one or more remote repositories
|
||||
*/
|
||||
@@ -35,7 +35,7 @@ use MokoEnterprise\{
|
||||
/**
|
||||
* Targeted File Push Tool
|
||||
*
|
||||
* Pushes one or more specific files from moko-platform templates to one or
|
||||
* Pushes one or more specific files from mokoplatform templates to one or
|
||||
* more remote repositories — without running a full sync.
|
||||
*
|
||||
* Files are specified by their destination path as they appear in the target
|
||||
@@ -81,7 +81,7 @@ class PushFiles extends CliFramework
|
||||
*/
|
||||
protected function run(): int
|
||||
{
|
||||
$this->log('📦 moko-platform File Push v' . self::VERSION, 'INFO');
|
||||
$this->log('📦 mokoplatform File Push v' . self::VERSION, 'INFO');
|
||||
|
||||
if (!$this->initializeComponents()) {
|
||||
return 1;
|
||||
@@ -337,7 +337,7 @@ class PushFiles extends CliFramework
|
||||
|
||||
$prNumber = null;
|
||||
if (!$direct) {
|
||||
$prTitle = "chore: push " . count($entries) . " file(s) from moko-platform";
|
||||
$prTitle = "chore: push " . count($entries) . " file(s) from mokoplatform";
|
||||
$prBody = $this->buildPRBody($entries);
|
||||
$pr = $this->adapter->createPullRequest(
|
||||
$org,
|
||||
@@ -414,7 +414,7 @@ class PushFiles extends CliFramework
|
||||
|
||||
$message = !empty($customMessage)
|
||||
? $customMessage
|
||||
: "chore: update {$destPath} from moko-platform";
|
||||
: "chore: update {$destPath} from mokoplatform";
|
||||
|
||||
// Fetch existing file SHA (needed for updates)
|
||||
$existingSha = null;
|
||||
@@ -457,9 +457,9 @@ class PushFiles extends CliFramework
|
||||
): void {
|
||||
$now = gmdate('Y-m-d H:i:s') . ' UTC';
|
||||
$version = self::VERSION;
|
||||
$source = $this->adapter->getRepoWebUrl($org, 'moko-platform');
|
||||
$source = $this->adapter->getRepoWebUrl($org, 'mokoplatform');
|
||||
|
||||
$title = "chore: moko-platform file push tracking";
|
||||
$title = "chore: mokoplatform file push tracking";
|
||||
|
||||
$deliveryLine = $prNumber !== null
|
||||
? "| **Pull request** | [#{$prNumber}](" . $this->adapter->getPullRequestWebUrl($org, $repo, $prNumber) . ") |"
|
||||
@@ -471,9 +471,9 @@ class PushFiles extends CliFramework
|
||||
));
|
||||
|
||||
$body = <<<MD
|
||||
## moko-platform File Push
|
||||
## mokoplatform File Push
|
||||
|
||||
One or more files were pushed to this repository from moko-platform.
|
||||
One or more files were pushed to this repository from mokoplatform.
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
@@ -487,12 +487,12 @@ class PushFiles extends CliFramework
|
||||
{$fileRows}
|
||||
|
||||
---
|
||||
*Generated automatically by [moko-platform]({$source}) `push_files.php`*
|
||||
*Generated automatically by [mokoplatform]({$source}) `push_files.php`*
|
||||
MD;
|
||||
|
||||
$body = preg_replace('/^ /m', '', $body);
|
||||
|
||||
$labels = ['standards-update', 'moko-platform', 'type: chore', 'automation'];
|
||||
$labels = ['standards-update', 'mokoplatform', 'type: chore', 'automation'];
|
||||
|
||||
try {
|
||||
$existing = $this->api->get("/repos/{$org}/{$repo}/issues", [
|
||||
@@ -550,7 +550,7 @@ class PushFiles extends CliFramework
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a failure issue in moko-platform when repos fail to receive files.
|
||||
* Create or update a failure issue in mokoplatform when repos fail to receive files.
|
||||
* Uses the 'push-failure' label. Reopens a closed issue rather than creating a duplicate.
|
||||
*/
|
||||
private function createFailureIssue(string $org, array $results): void
|
||||
@@ -598,7 +598,7 @@ class PushFiles extends CliFramework
|
||||
$body = preg_replace('/^ /m', '', $body);
|
||||
|
||||
try {
|
||||
$existing = $this->api->get("/repos/{$org}/moko-platform/issues", [
|
||||
$existing = $this->api->get("/repos/{$org}/mokoplatform/issues", [
|
||||
'labels' => 'push-failure',
|
||||
'state' => 'all',
|
||||
'per_page' => 1,
|
||||
@@ -613,17 +613,17 @@ class PushFiles extends CliFramework
|
||||
if (($existing[0]['state'] ?? 'open') === 'closed') {
|
||||
$patch['state'] = 'open';
|
||||
}
|
||||
$this->api->patch("/repos/{$org}/moko-platform/issues/{$num}", $patch);
|
||||
$this->log("🚨 Failure issue #{$num} updated: {$org}/moko-platform#{$num}", 'WARN');
|
||||
$this->api->patch("/repos/{$org}/mokoplatform/issues/{$num}", $patch);
|
||||
$this->log("🚨 Failure issue #{$num} updated: {$org}/mokoplatform#{$num}", 'WARN');
|
||||
} else {
|
||||
$issue = $this->api->post("/repos/{$org}/moko-platform/issues", [
|
||||
$issue = $this->api->post("/repos/{$org}/mokoplatform/issues", [
|
||||
'title' => $title,
|
||||
'body' => $body,
|
||||
'labels' => ['push-failure'],
|
||||
'assignees' => ['jmiller'],
|
||||
]);
|
||||
$num = $issue['number'] ?? '?';
|
||||
$this->log("🚨 Failure issue created: {$org}/moko-platform#{$num}", 'WARN');
|
||||
$this->log("🚨 Failure issue created: {$org}/mokoplatform#{$num}", 'WARN');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->log("⚠️ Could not create/update failure issue: " . $e->getMessage(), 'WARN');
|
||||
@@ -638,14 +638,14 @@ class PushFiles extends CliFramework
|
||||
private function buildPRBody(array $entries): string
|
||||
{
|
||||
$now = gmdate('Y-m-d H:i:s') . ' UTC';
|
||||
$lines = ["## moko-platform File Push\n", "**Pushed:** {$now}\n", '### Files\n'];
|
||||
$lines = ["## mokoplatform File Push\n", "**Pushed:** {$now}\n", '### Files\n'];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$lines[] = "- `{$entry['destination']}`";
|
||||
}
|
||||
|
||||
$sourceUrl = $this->adapter->getRepoWebUrl(self::DEFAULT_ORG, 'moko-platform');
|
||||
$lines[] = "\n---\n*Generated by [moko-platform]({$sourceUrl}) `push_files.php`*";
|
||||
$sourceUrl = $this->adapter->getRepoWebUrl(self::DEFAULT_ORG, 'mokoplatform');
|
||||
$lines[] = "\n---\n*Generated by [mokoplatform]({$sourceUrl}) `push_files.php`*";
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/push_manifest_xml.php
|
||||
* BRIEF: Push XML manifests to all governed repositories
|
||||
*/
|
||||
@@ -47,7 +47,7 @@ class PushManifestXmlCli extends CliFramework
|
||||
$parser = new MokoStandardsParser();
|
||||
$tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid();
|
||||
|
||||
echo "=== moko-platform XML Manifest Push ===\n";
|
||||
echo "=== mokoplatform XML Manifest Push ===\n";
|
||||
echo "Org: {$giteaOrg}\n";
|
||||
echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n";
|
||||
if ($repoFilter) {
|
||||
@@ -125,7 +125,7 @@ class PushManifestXmlCli extends CliFramework
|
||||
|
||||
// Check if already XML and up-to-date
|
||||
$manifestPath = "{$workDir}/.mokogitea/manifest.xml";
|
||||
$existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), '<moko-platform');
|
||||
$existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), '<mokoplatform');
|
||||
if ($existingIsXml && !$force) {
|
||||
$existingPlatform = $parser->extractPlatform(file_get_contents($manifestPath));
|
||||
if ($existingPlatform === $platform) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/push_mokostandards_xml.php
|
||||
* BRIEF: Push XML manifests to all governed repositories
|
||||
*/
|
||||
@@ -47,7 +47,7 @@ class PushMokostandardsXmlCli extends CliFramework
|
||||
$parser = new MokoStandardsParser();
|
||||
$tmpBase = sys_get_temp_dir() . '/moko-manifest-push-' . getmypid();
|
||||
|
||||
echo "=== moko-platform XML Manifest Push ===\n";
|
||||
echo "=== mokoplatform XML Manifest Push ===\n";
|
||||
echo "Org: {$giteaOrg}\n";
|
||||
echo "Mode: " . ($this->dryRun ? "DRY RUN" : "LIVE") . "\n";
|
||||
if ($repoFilter) {
|
||||
@@ -125,7 +125,7 @@ class PushMokostandardsXmlCli extends CliFramework
|
||||
|
||||
// Check if already XML and up-to-date
|
||||
$manifestPath = "{$workDir}/.mokogitea/manifest.xml";
|
||||
$existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), '<moko-platform');
|
||||
$existingIsXml = file_exists($manifestPath) && str_contains(file_get_contents($manifestPath), '<mokoplatform');
|
||||
if ($existingIsXml && !$force) {
|
||||
$existingPlatform = $parser->extractPlatform(file_get_contents($manifestPath));
|
||||
if ($existingPlatform === $platform) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Automation
|
||||
* INGROUP: MokoPlatform.Scripts
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /automation/repo_cleanup.php
|
||||
* BRIEF: Enterprise repository cleanup — branches, PRs, issues, workflows, labels, logs
|
||||
*/
|
||||
@@ -39,14 +39,14 @@ use MokoEnterprise\{ApiClient, AuditLogger, CliFramework, Config, GitPlatformAda
|
||||
class RepoCleanup extends CliFramework
|
||||
{
|
||||
private const VERSION = '09.23.00';
|
||||
private const SYNC_PREFIX = 'chore/sync-moko-platform-';
|
||||
private const CURRENT_BRANCH = 'chore/sync-moko-platform-v04.02.00';
|
||||
private const SYNC_PREFIX = 'chore/sync-mokoplatform-';
|
||||
private const CURRENT_BRANCH = 'chore/sync-mokoplatform-v04.02.00';
|
||||
|
||||
/** Workflow files that have been retired and should be deleted from governed repos. */
|
||||
private const RETIRED_WORKFLOWS = [
|
||||
'build.yml', 'code-quality.yml', 'release-cycle.yml', 'release-pipeline.yml',
|
||||
'branch-cleanup.yml', 'auto-update-changelog.yml', 'enterprise-issue-manager.yml',
|
||||
'flush-actions-cache.yml', 'moko-platform-script-runner.yml', 'unified-ci.yml',
|
||||
'flush-actions-cache.yml', 'mokoplatform-script-runner.yml', 'unified-ci.yml',
|
||||
'unified-platform-testing.yml', 'reusable-build.yml', 'reusable-ci-validation.yml',
|
||||
'reusable-deploy.yml', 'reusable-php-quality.yml', 'reusable-platform-testing.yml',
|
||||
'reusable-project-detector.yml', 'reusable-release.yml', 'reusable-script-executor.yml',
|
||||
@@ -98,7 +98,7 @@ class RepoCleanup extends CliFramework
|
||||
}
|
||||
|
||||
|
||||
$this->logMsg("🧹 moko-platform Repository Cleanup v" . self::VERSION);
|
||||
$this->logMsg("🧹 mokoplatform Repository Cleanup v" . self::VERSION);
|
||||
$this->logMsg("Organization: {$org}");
|
||||
$this->logMsg("Current sync branch: " . self::CURRENT_BRANCH);
|
||||
if ($this->dryRun) {
|
||||
@@ -225,7 +225,7 @@ class RepoCleanup extends CliFramework
|
||||
}
|
||||
|
||||
$allRepos = $this->adapter->listOrgRepos($org, $skipArchived);
|
||||
return array_filter($allRepos, fn($r) => !in_array($r['name'], ['moko-platform', '.github-private'], true));
|
||||
return array_filter($allRepos, fn($r) => !in_array($r['name'], ['mokoplatform', '.github-private'], true));
|
||||
}
|
||||
|
||||
// ─── Cleanup operations ──────────────────────────────────────────────
|
||||
@@ -463,9 +463,9 @@ class RepoCleanup extends CliFramework
|
||||
private function checkLabels(string $org, string $repo, array &$results): void
|
||||
{
|
||||
try {
|
||||
$this->api->get("/repos/{$org}/{$repo}/labels/moko-platform");
|
||||
$this->api->get("/repos/{$org}/{$repo}/labels/mokoplatform");
|
||||
} catch (\Exception $e) {
|
||||
$this->logMsg(" ⚠️ Missing 'moko-platform' label");
|
||||
$this->logMsg(" ⚠️ Missing 'mokoplatform' label");
|
||||
$results['labels_missing']++;
|
||||
$this->api->resetCircuitBreaker();
|
||||
}
|
||||
@@ -479,7 +479,7 @@ class RepoCleanup extends CliFramework
|
||||
if (preg_match('/^\s*VERSION:\s*(\d{2}\.\d{2}\.\d{2})/m', $content, $m)) {
|
||||
$version = $m[1];
|
||||
|
||||
// Check manifest.xml for the tracked moko-platform version
|
||||
// Check manifest.xml for the tracked mokoplatform version
|
||||
try {
|
||||
$mokoFile = $this->api->get("/repos/{$org}/{$repo}/contents/.mokogitea/manifest.xml");
|
||||
$mokoContent = base64_decode($mokoFile['content'] ?? '');
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/archive_repo.php
|
||||
* BRIEF: Gracefully retire a governed repository — archive, close issues/PRs, remove sync def
|
||||
*/
|
||||
@@ -135,7 +135,7 @@ class ArchiveRepoCli extends CliFramework
|
||||
try {
|
||||
$issue = $adapter->createIssue(
|
||||
$org,
|
||||
'moko-platform',
|
||||
'mokoplatform',
|
||||
"chore: archived repository {$repoName}",
|
||||
"## Repository Archived\n\n"
|
||||
. "**Repository:** `{$org}/{$repoName}`\n"
|
||||
@@ -150,7 +150,7 @@ class ArchiveRepoCli extends CliFramework
|
||||
]
|
||||
);
|
||||
if (isset($issue['number'])) {
|
||||
echo " Archival record: moko-platform#{$issue['number']}\n";
|
||||
echo " Archival record: mokoplatform#{$issue['number']}\n";
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
echo " Warning: could not create archival record: " . $e->getMessage() . "\n";
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Enterprise.CLI
|
||||
* INGROUP: MokoPlatform.Enterprise
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/audit_query.php
|
||||
* BRIEF: Search, filter, and export audit logs
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/badge_update.php
|
||||
* BRIEF: Update [VERSION: XX.XX.XX] badges in all markdown files
|
||||
*/
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/branch_rename.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Rename a git branch via Gitea API (create new, update PR, delete old)
|
||||
*/
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/bulk_workflow_push.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Push a workflow file to all governed repos via the Gitea Contents API
|
||||
*/
|
||||
|
||||
@@ -154,7 +154,7 @@ class BulkWorkflowPushCli extends CliFramework
|
||||
'content' => $encodedContent,
|
||||
'sha' => $remoteSha,
|
||||
'message' => "chore: sync {$destPath} "
|
||||
. "from moko-platform [skip ci]",
|
||||
. "from mokoplatform [skip ci]",
|
||||
'branch' => $branch,
|
||||
]);
|
||||
|
||||
@@ -184,7 +184,7 @@ class BulkWorkflowPushCli extends CliFramework
|
||||
$payload = json_encode([
|
||||
'content' => $encodedContent,
|
||||
'message' => "chore: add {$destPath} "
|
||||
. "from moko-platform [skip ci]",
|
||||
. "from mokoplatform [skip ci]",
|
||||
'branch' => $branch,
|
||||
]);
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/bulk_workflow_trigger.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Trigger a workflow across multiple repos at once
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/changelog_promote.php
|
||||
* BRIEF: Promote [Unreleased] section in CHANGELOG.md to a versioned entry
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/changelog_prune.php
|
||||
* BRIEF: Prune old CHANGELOG.md entries — keeps [Unreleased] + last N releases
|
||||
*/
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/client_dashboard.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Generate unified client dashboard HTML
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/client_health_check.php
|
||||
* BRIEF: Verify a client site's update server, installed version, and release availability
|
||||
*/
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/client_inventory.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Discover and list all client-waas repos with their server configuration status
|
||||
*/
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/client_provision.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Provision a new client environment end-to-end
|
||||
*/
|
||||
|
||||
|
||||
+3
-3
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/completion.php
|
||||
* BRIEF: Generate bash/zsh tab completion scripts for bin/moko
|
||||
*/
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/create_project.php
|
||||
* BRIEF: Create baseline GitHub Projects for repositories with standard fields and views
|
||||
*/
|
||||
@@ -24,7 +24,7 @@ use MokoEnterprise\CliFramework;
|
||||
class CreateProjectCli extends CliFramework
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $ALWAYS_EXCLUDE = ['moko-platform', '.github-private'];
|
||||
private array $ALWAYS_EXCLUDE = ['mokoplatform', '.github-private'];
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $PLATFORM_TO_TYPE = [
|
||||
@@ -183,7 +183,7 @@ class CreateProjectCli extends CliFramework
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Authorization: bearer ' . $token,
|
||||
'Content-Type: application/json',
|
||||
'User-Agent: moko-platform-CreateProject',
|
||||
'User-Agent: mokoplatform-CreateProject',
|
||||
],
|
||||
]);
|
||||
$body = (string) curl_exec($ch);
|
||||
@@ -422,14 +422,14 @@ class CreateProjectCli extends CliFramework
|
||||
updateProjectV2(input: {
|
||||
projectId: $projectId,
|
||||
shortDescription: $shortDescription,
|
||||
readme: "Managed by moko-platform. Run `php cli/create_project.php` to regenerate."
|
||||
readme: "Managed by mokoplatform. Run `php cli/create_project.php` to regenerate."
|
||||
}) {
|
||||
projectV2 { id }
|
||||
}
|
||||
}',
|
||||
[
|
||||
'projectId' => $projectId,
|
||||
'shortDescription' => "Standard project board for {$repo}. Auto-created by moko-platform.",
|
||||
'shortDescription' => "Standard project board for {$repo}. Auto-created by mokoplatform.",
|
||||
],
|
||||
$token
|
||||
);
|
||||
|
||||
+17
-17
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/create_repo.php
|
||||
* BRIEF: Scaffold a new governed repository with full moko-platform baseline
|
||||
* BRIEF: Scaffold a new governed repository with full mokoplatform baseline
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
@@ -28,7 +28,7 @@ class CreateRepoCli extends CliFramework
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Scaffold a new governed repository with full moko-platform baseline');
|
||||
$this->setDescription('Scaffold a new governed repository with full mokoplatform baseline');
|
||||
$this->addArgument('--name', 'Repository name', null);
|
||||
$this->addArgument('--type', 'Project type', null);
|
||||
$this->addArgument('--description', 'Repository description', '');
|
||||
@@ -60,16 +60,16 @@ class CreateRepoCli extends CliFramework
|
||||
'generic' => 'generic',
|
||||
];
|
||||
$TYPE_TO_TOPICS = [
|
||||
'dolibarr' => ['dolibarr', 'erp', 'crm', 'php', 'moko-platform'],
|
||||
'joomla' => ['joomla', 'cms', 'php', 'moko-platform'],
|
||||
'nodejs' => ['nodejs', 'javascript', 'typescript', 'moko-platform'],
|
||||
'terraform' => ['terraform', 'infrastructure', 'iac', 'moko-platform'],
|
||||
'python' => ['python', 'moko-platform'],
|
||||
'wordpress' => ['wordpress', 'php', 'cms', 'moko-platform'],
|
||||
'generic' => ['moko-platform'],
|
||||
'dolibarr' => ['dolibarr', 'erp', 'crm', 'php', 'mokoplatform'],
|
||||
'joomla' => ['joomla', 'cms', 'php', 'mokoplatform'],
|
||||
'nodejs' => ['nodejs', 'javascript', 'typescript', 'mokoplatform'],
|
||||
'terraform' => ['terraform', 'infrastructure', 'iac', 'mokoplatform'],
|
||||
'python' => ['python', 'mokoplatform'],
|
||||
'wordpress' => ['wordpress', 'php', 'cms', 'mokoplatform'],
|
||||
'generic' => ['mokoplatform'],
|
||||
];
|
||||
$platform = $TYPE_TO_PLATFORM[$type] ?? 'generic';
|
||||
$topics = $TYPE_TO_TOPICS[$type] ?? ['moko-platform'];
|
||||
$topics = $TYPE_TO_TOPICS[$type] ?? ['mokoplatform'];
|
||||
$platformName = $adapter->getPlatformName();
|
||||
$vis = $private ? 'private' : 'public';
|
||||
echo "Scaffolding new repository: {$org}/{$name}"
|
||||
@@ -84,7 +84,7 @@ class CreateRepoCli extends CliFramework
|
||||
if (!$this->dryRun) {
|
||||
try {
|
||||
$data = $adapter->createOrgRepo($org, $name, [
|
||||
'description' => $description ?: "Managed by moko-platform ({$type})",
|
||||
'description' => $description ?: "Managed by mokoplatform ({$type})",
|
||||
'private' => $private,
|
||||
'has_issues' => true,
|
||||
'has_projects' => true,
|
||||
@@ -143,7 +143,7 @@ class CreateRepoCli extends CliFramework
|
||||
. "Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>\n"
|
||||
. "SPDX-License-Identifier: GPL-3.0-or-later\n"
|
||||
. "DEFGROUP: {$name}\n"
|
||||
. "INGROUP: moko-platform\n"
|
||||
. "INGROUP: mokoplatform\n"
|
||||
. "REPO: {$repoUrl}\n"
|
||||
. "PATH: /README.md\n"
|
||||
. "BRIEF: {$description}\n"
|
||||
@@ -152,7 +152,7 @@ class CreateRepoCli extends CliFramework
|
||||
. "{$description}\n\n"
|
||||
. "## Getting Started\n\n"
|
||||
. "This repository is governed by"
|
||||
. " [moko-platform]({$standardsUrl}).\n\n"
|
||||
. " [mokoplatform]({$standardsUrl}).\n\n"
|
||||
. "## License\n\n"
|
||||
. "GPL-3.0-or-later. See [LICENSE](LICENSE)"
|
||||
. " for details.\n";
|
||||
@@ -169,7 +169,7 @@ class CreateRepoCli extends CliFramework
|
||||
$name,
|
||||
'README.md',
|
||||
$readmeContent,
|
||||
'docs: initialize README with moko-platform header [skip ci]',
|
||||
'docs: initialize README with mokoplatform header [skip ci]',
|
||||
$sha
|
||||
);
|
||||
echo " README.md created\n";
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.CLI
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/deploy_joomla.php
|
||||
* BRIEF: Smart Joomla deploy — routes files to correct server directories by extension type
|
||||
*
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/dev_branch_reset.php
|
||||
* BRIEF: Delete and recreate dev branch from main via Gitea API
|
||||
*/
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/grafana_dashboard.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Manage Grafana dashboards via API
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/joomla_build.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Build a Joomla extension ZIP from manifest — all types supported
|
||||
* NOTE: Called by pre-release and auto-release workflows.
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/joomla_compat_check.php
|
||||
* BRIEF: Check if extension targetplatform regex matches the latest Joomla version
|
||||
*/
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/joomla_release.php
|
||||
* BRIEF: Joomla release pipeline — build ZIP+tar.gz, upload to GitHub Release, update updates.xml
|
||||
*
|
||||
@@ -407,7 +407,7 @@ class JoomlaRelease extends CliFramework
|
||||
$this->api->post("/repos/{$repo}/releases", [
|
||||
'tag_name' => $tag,
|
||||
'name' => $releaseName,
|
||||
'body' => "## {$version}\n\nCreated by moko-platform release pipeline.",
|
||||
'body' => "## {$version}\n\nCreated by mokoplatform release pipeline.",
|
||||
'prerelease' => ($stability !== 'stable'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/license_manage.php
|
||||
* BRIEF: Manage license packages and keys via MokoGitea licensing API
|
||||
*
|
||||
|
||||
@@ -0,0 +1,716 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/manifest_detect.php
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Auto-detect manifest fields from source files and optionally push to API
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
||||
|
||||
use MokoEnterprise\{CliFramework, SourceResolver};
|
||||
|
||||
class ManifestDetectCli extends CliFramework
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Auto-detect manifest fields from source files');
|
||||
$this->addArgument('--path', 'Repository root path', '.');
|
||||
$this->addArgument('--json', 'Output as JSON', false);
|
||||
$this->addArgument('--diff', 'Show diff against current manifest API values', false);
|
||||
$this->addArgument('--update', 'Push detected fields to manifest API', false);
|
||||
$this->addArgument('--token', 'Gitea API token (or GITEA_TOKEN env)', '');
|
||||
$this->addArgument('--api-base', 'Gitea API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
||||
$this->addArgument('--org', 'Gitea org', 'MokoConsulting');
|
||||
$this->addArgument('--repo', 'Gitea repo name (auto-detected from remote if empty)', '');
|
||||
$this->addArgument('--github-output', 'Append fields to $GITHUB_OUTPUT', false);
|
||||
}
|
||||
|
||||
protected function run(): int
|
||||
{
|
||||
$path = $this->getArgument('--path');
|
||||
$jsonMode = (bool) $this->getArgument('--json');
|
||||
$diffMode = (bool) $this->getArgument('--diff');
|
||||
$updateMode = (bool) $this->getArgument('--update');
|
||||
$ghOutput = (bool) $this->getArgument('--github-output');
|
||||
$token = $this->getArgument('--token') ?: getenv('GITEA_TOKEN') ?: '';
|
||||
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
||||
$org = $this->getArgument('--org');
|
||||
$repoName = $this->getArgument('--repo');
|
||||
|
||||
$root = realpath($path) ?: $path;
|
||||
|
||||
if (!is_dir($root)) {
|
||||
$this->log('ERROR', "Path does not exist: {$path}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Auto-detect repo name from git remote
|
||||
if ($repoName === '') {
|
||||
$repoName = $this->detectRepoName($root);
|
||||
}
|
||||
|
||||
// ── Detect all fields ───────────────────────────────────────
|
||||
$detected = $this->detectAll($root, $repoName);
|
||||
|
||||
// ── Warn about missing fields ────────────────────────────────
|
||||
$expected = ['platform', 'name', 'version', 'package_type', 'language', 'entry_point'];
|
||||
foreach ($expected as $field) {
|
||||
if (!isset($detected[$field]) || $detected[$field] === '') {
|
||||
$this->log('WARN', "Could not detect: {$field}");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Output ──────────────────────────────────────────────────
|
||||
if ($diffMode || $updateMode) {
|
||||
if ($token === '') {
|
||||
$this->log('ERROR', 'API token required for --diff/--update (use --token or GITEA_TOKEN env)');
|
||||
return 1;
|
||||
}
|
||||
if ($repoName === '') {
|
||||
$this->log('ERROR', 'Could not determine repo name (use --repo)');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$current = $this->fetchManifest($apiBase, $org, $repoName, $token);
|
||||
if ($current === null) {
|
||||
$this->log('ERROR', 'Failed to fetch current manifest from API');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$changes = $this->computeDiff($current, $detected);
|
||||
|
||||
if ($diffMode) {
|
||||
if (empty($changes)) {
|
||||
$this->log('INFO', 'No differences — manifest matches source');
|
||||
} else {
|
||||
$this->sectionHeader('Manifest Drift');
|
||||
foreach ($changes as $field => $info) {
|
||||
$this->log('WARN', sprintf(
|
||||
'%-20s API: %-30s Detected: %s',
|
||||
$field,
|
||||
$info['current'] === '' ? '(empty)' : $info['current'],
|
||||
$info['detected']
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($updateMode) {
|
||||
if (empty($changes)) {
|
||||
$this->log('INFO', 'Nothing to update');
|
||||
} else {
|
||||
$update = array_map(fn($i) => $i['detected'], $changes);
|
||||
$ok = $this->pushManifest($apiBase, $org, $repoName, $token, $current, $update);
|
||||
if ($ok) {
|
||||
$this->log('OK', 'Updated ' . count($update) . ' field(s): ' . implode(', ', array_keys($update)));
|
||||
} else {
|
||||
$this->log('ERROR', 'Failed to push manifest update');
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($ghOutput) {
|
||||
$outputFile = getenv('GITHUB_OUTPUT');
|
||||
$lines = [];
|
||||
foreach ($detected as $k => $v) {
|
||||
$envKey = str_replace('-', '_', $k);
|
||||
$lines[] = "{$envKey}={$v}";
|
||||
}
|
||||
if ($outputFile !== false && $outputFile !== '') {
|
||||
file_put_contents($outputFile, implode("\n", $lines) . "\n", FILE_APPEND);
|
||||
$this->log('INFO', 'Wrote ' . count($detected) . ' fields to GITHUB_OUTPUT');
|
||||
} else {
|
||||
$this->log('WARN', 'GITHUB_OUTPUT not set — printing to stdout instead');
|
||||
echo implode("\n", $lines) . "\n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($jsonMode) {
|
||||
echo json_encode($detected, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
||||
} else {
|
||||
foreach ($detected as $k => $v) {
|
||||
echo "{$k}={$v}\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Detection engine
|
||||
// =====================================================================
|
||||
|
||||
private function detectAll(string $root, string $repoName): array
|
||||
{
|
||||
$platform = $this->detectPlatform($root);
|
||||
|
||||
$fields = [
|
||||
'platform' => $platform,
|
||||
'name' => '',
|
||||
'description' => '',
|
||||
'version' => '',
|
||||
'element_name' => '',
|
||||
'package_type' => '',
|
||||
'language' => '',
|
||||
'entry_point' => '',
|
||||
'license_spdx' => '',
|
||||
];
|
||||
|
||||
switch ($platform) {
|
||||
case 'joomla':
|
||||
$this->detectJoomla($root, $repoName, $fields);
|
||||
break;
|
||||
case 'dolibarr':
|
||||
$this->detectDolibarr($root, $repoName, $fields);
|
||||
break;
|
||||
case 'go':
|
||||
$this->detectGo($root, $repoName, $fields);
|
||||
break;
|
||||
case 'mcp':
|
||||
$this->detectNode($root, $repoName, $fields);
|
||||
break;
|
||||
case 'node':
|
||||
$this->detectNode($root, $repoName, $fields);
|
||||
$fields['platform'] = 'node';
|
||||
break;
|
||||
default:
|
||||
$this->detectGeneric($root, $repoName, $fields);
|
||||
break;
|
||||
}
|
||||
|
||||
// Fallbacks
|
||||
if ($fields['name'] === '') {
|
||||
$fields['name'] = $repoName ?: basename($root);
|
||||
}
|
||||
if ($fields['entry_point'] === '') {
|
||||
$fields['entry_point'] = $this->detectEntryPoint($root);
|
||||
}
|
||||
if ($fields['license_spdx'] === '') {
|
||||
$fields['license_spdx'] = $this->detectLicense($root);
|
||||
}
|
||||
// description: only from platform-specific source, never guessed
|
||||
|
||||
// Strip empty values
|
||||
return array_filter($fields, fn($v) => $v !== '');
|
||||
}
|
||||
|
||||
// ── Platform detection ──────────────────────────────────────────
|
||||
|
||||
private function detectPlatform(string $root): string
|
||||
{
|
||||
// Joomla: look for pkg_*.xml or extension XML in source dirs
|
||||
$joomlaXmls = array_merge(
|
||||
SourceResolver::globSource($root, 'pkg_*.xml'),
|
||||
glob("{$root}/pkg_*.xml") ?: []
|
||||
);
|
||||
if (!empty($joomlaXmls)) {
|
||||
return 'joomla';
|
||||
}
|
||||
|
||||
// Check source dirs for any Joomla extension XML
|
||||
foreach (SourceResolver::globSource($root, '*.xml') as $xmlFile) {
|
||||
$content = file_get_contents($xmlFile);
|
||||
if (strpos($content, '<extension') !== false) {
|
||||
return 'joomla';
|
||||
}
|
||||
}
|
||||
|
||||
// Dolibarr: mod*.class.php with DolibarrModules
|
||||
$modFiles = array_merge(
|
||||
SourceResolver::globSource($root, 'core/modules/mod*.class.php'),
|
||||
glob("{$root}/core/modules/mod*.class.php") ?: []
|
||||
);
|
||||
foreach ($modFiles as $file) {
|
||||
if (strpos(file_get_contents($file), 'DolibarrModules') !== false) {
|
||||
return 'dolibarr';
|
||||
}
|
||||
}
|
||||
|
||||
// Go
|
||||
if (file_exists("{$root}/go.mod")) {
|
||||
return 'go';
|
||||
}
|
||||
|
||||
// MCP: package.json with mcp-related content
|
||||
if (file_exists("{$root}/package.json")) {
|
||||
$pkg = json_decode(file_get_contents("{$root}/package.json"), true) ?? [];
|
||||
$deps = array_merge(
|
||||
array_keys($pkg['dependencies'] ?? []),
|
||||
array_keys($pkg['devDependencies'] ?? [])
|
||||
);
|
||||
foreach ($deps as $dep) {
|
||||
if (strpos($dep, '@modelcontextprotocol/') === 0 || $dep === '@anthropic/mcp-sdk') {
|
||||
return 'mcp';
|
||||
}
|
||||
}
|
||||
return 'node';
|
||||
}
|
||||
|
||||
// Python
|
||||
if (file_exists("{$root}/pyproject.toml") || file_exists("{$root}/setup.py")) {
|
||||
return 'python';
|
||||
}
|
||||
|
||||
return 'generic';
|
||||
}
|
||||
|
||||
// ── Joomla ──────────────────────────────────────────────────────
|
||||
|
||||
private function detectJoomla(string $root, string $repoName, array &$fields): void
|
||||
{
|
||||
$fields['language'] = 'PHP';
|
||||
|
||||
// Find the primary extension manifest XML
|
||||
$extManifest = $this->findJoomlaManifest($root);
|
||||
if ($extManifest === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xml = file_get_contents($extManifest);
|
||||
|
||||
// Type
|
||||
$extType = '';
|
||||
if (preg_match('/type="([^"]*)"/', $xml, $m)) {
|
||||
$extType = $m[1];
|
||||
}
|
||||
$fields['package_type'] = $extType;
|
||||
|
||||
// Element name
|
||||
$element = '';
|
||||
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) {
|
||||
$element = $m[1];
|
||||
}
|
||||
if ($element === '' && preg_match('/module="([^"]*)"/', $xml, $m)) {
|
||||
$element = $m[1];
|
||||
}
|
||||
if ($element === '' && preg_match('/plugin="([^"]*)"/', $xml, $m)) {
|
||||
$element = $m[1];
|
||||
}
|
||||
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $m)) {
|
||||
$element = $m[1];
|
||||
}
|
||||
if ($element === '') {
|
||||
$element = strtolower(basename($extManifest, '.xml'));
|
||||
}
|
||||
|
||||
// Ensure element has type prefix (API stores full element_name like pkg_mokosuite)
|
||||
$prefixMap = [
|
||||
'package' => 'pkg_', 'component' => 'com_', 'module' => 'mod_',
|
||||
'template' => 'tpl_', 'library' => 'lib_', 'file' => 'file_',
|
||||
];
|
||||
if (isset($prefixMap[$extType])) {
|
||||
$prefix = $prefixMap[$extType];
|
||||
if (strpos($element, $prefix) !== 0 && strpos($element, '_') === false) {
|
||||
$element = $prefix . $element;
|
||||
}
|
||||
} elseif ($extType === 'plugin') {
|
||||
$folder = '';
|
||||
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
|
||||
$folder = $gm[1];
|
||||
}
|
||||
if ($folder !== '' && strpos($element, 'plg_') !== 0) {
|
||||
$element = "plg_{$folder}_" . $element;
|
||||
}
|
||||
}
|
||||
$fields['element_name'] = $element;
|
||||
|
||||
// Name
|
||||
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $m)) {
|
||||
$fields['name'] = trim($m[1]);
|
||||
}
|
||||
|
||||
// Version
|
||||
if (preg_match('/<version>([^<]+)<\/version>/', $xml, $m)) {
|
||||
$fields['version'] = trim($m[1]);
|
||||
}
|
||||
|
||||
// Description
|
||||
if (preg_match('/<description>([^<]+)<\/description>/', $xml, $m)) {
|
||||
$desc = trim($m[1]);
|
||||
// Skip language string keys like COM_MOKOSUITE_DESCRIPTION
|
||||
if (strpos($desc, '_') === false || strlen($desc) > 60) {
|
||||
$fields['description'] = $desc;
|
||||
}
|
||||
}
|
||||
|
||||
// License
|
||||
if (preg_match('/<license>([^<]+)<\/license>/', $xml, $m)) {
|
||||
$fields['license_spdx'] = $this->normalizeLicense(trim($m[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private function findJoomlaManifest(string $root): ?string
|
||||
{
|
||||
// Priority: pkg_*.xml (package manifest)
|
||||
$pkgXmls = array_merge(
|
||||
SourceResolver::globSource($root, 'pkg_*.xml'),
|
||||
glob("{$root}/pkg_*.xml") ?: []
|
||||
);
|
||||
if (!empty($pkgXmls)) {
|
||||
return $pkgXmls[0];
|
||||
}
|
||||
|
||||
// Any extension XML in source dir
|
||||
foreach (SourceResolver::globSource($root, '*.xml') as $file) {
|
||||
$content = file_get_contents($file);
|
||||
if (strpos($content, '<extension') !== false) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
// Root level
|
||||
foreach (glob("{$root}/*.xml") ?: [] as $file) {
|
||||
$content = file_get_contents($file);
|
||||
if (strpos($content, '<extension') !== false) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Dolibarr ────────────────────────────────────────────────────
|
||||
|
||||
private function detectDolibarr(string $root, string $repoName, array &$fields): void
|
||||
{
|
||||
$fields['language'] = 'PHP';
|
||||
$fields['package_type'] = 'dolibarr-module';
|
||||
|
||||
$modFile = $this->findDolibarrModule($root);
|
||||
if ($modFile === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = file_get_contents($modFile);
|
||||
|
||||
// Element name from class file
|
||||
$modBasename = basename($modFile, '.class.php');
|
||||
$fields['element_name'] = strtolower(preg_replace('/^mod/', '', $modBasename));
|
||||
|
||||
// Name
|
||||
if (preg_match('/\$this->name\s*=\s*[\'"]([^\'"]+)[\'"]/', $content, $m)) {
|
||||
$fields['name'] = $m[1];
|
||||
}
|
||||
|
||||
// Version
|
||||
if (preg_match('/\$this->version\s*=\s*[\'"]([^\'"]+)[\'"]/', $content, $m)) {
|
||||
$fields['version'] = $m[1];
|
||||
}
|
||||
|
||||
// Description
|
||||
if (preg_match('/\$this->description\s*=\s*[\'"]([^\'"]+)[\'"]/', $content, $m)) {
|
||||
$desc = $m[1];
|
||||
if (strpos($desc, '$') === false) {
|
||||
$fields['description'] = $desc;
|
||||
}
|
||||
}
|
||||
|
||||
// License
|
||||
if (preg_match('/SPDX-License-Identifier:\s*(\S+)/', $content, $m)) {
|
||||
$fields['license_spdx'] = $m[1];
|
||||
}
|
||||
}
|
||||
|
||||
private function findDolibarrModule(string $root): ?string
|
||||
{
|
||||
$candidates = array_merge(
|
||||
SourceResolver::globSource($root, 'core/modules/mod*.class.php'),
|
||||
glob("{$root}/core/modules/mod*.class.php") ?: []
|
||||
);
|
||||
foreach ($candidates as $file) {
|
||||
if (strpos(file_get_contents($file), 'DolibarrModules') !== false) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ── Go ──────────────────────────────────────────────────────────
|
||||
|
||||
private function detectGo(string $root, string $repoName, array &$fields): void
|
||||
{
|
||||
$fields['language'] = 'Go';
|
||||
$fields['package_type'] = 'application';
|
||||
$fields['entry_point'] = './';
|
||||
|
||||
$goMod = "{$root}/go.mod";
|
||||
if (!file_exists($goMod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = file_get_contents($goMod);
|
||||
|
||||
// Module path → name
|
||||
if (preg_match('/^module\s+(\S+)/m', $content, $m)) {
|
||||
$modulePath = $m[1];
|
||||
$parts = explode('/', $modulePath);
|
||||
$fields['name'] = end($parts);
|
||||
}
|
||||
|
||||
// Go version
|
||||
if (preg_match('/^go\s+(\S+)/m', $content, $m)) {
|
||||
// This is Go language version, not the project version
|
||||
// Project version comes from git tags or source files
|
||||
}
|
||||
|
||||
// License
|
||||
$fields['license_spdx'] = $this->detectLicense($root);
|
||||
}
|
||||
|
||||
// ── Node / MCP ──────────────────────────────────────────────────
|
||||
|
||||
private function detectNode(string $root, string $repoName, array &$fields): void
|
||||
{
|
||||
$pkgFile = "{$root}/package.json";
|
||||
if (!file_exists($pkgFile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pkg = json_decode(file_get_contents($pkgFile), true) ?? [];
|
||||
|
||||
$fields['name'] = $pkg['name'] ?? '';
|
||||
// Strip npm scope
|
||||
if (strpos($fields['name'], '/') !== false) {
|
||||
$fields['name'] = explode('/', $fields['name'])[1];
|
||||
}
|
||||
|
||||
$fields['version'] = $pkg['version'] ?? '';
|
||||
$fields['description'] = $pkg['description'] ?? '';
|
||||
$fields['license_spdx'] = $pkg['license'] ?? '';
|
||||
|
||||
// Language detection
|
||||
if (file_exists("{$root}/tsconfig.json")) {
|
||||
$fields['language'] = 'TypeScript';
|
||||
} else {
|
||||
$fields['language'] = 'JavaScript';
|
||||
}
|
||||
|
||||
// Package type
|
||||
$deps = array_merge(
|
||||
array_keys($pkg['dependencies'] ?? []),
|
||||
array_keys($pkg['devDependencies'] ?? [])
|
||||
);
|
||||
$isMcp = false;
|
||||
foreach ($deps as $dep) {
|
||||
if (strpos($dep, '@modelcontextprotocol/') === 0 || $dep === '@anthropic/mcp-sdk') {
|
||||
$isMcp = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fields['package_type'] = $isMcp ? 'mcp-server' : 'application';
|
||||
|
||||
// Entry point
|
||||
if (file_exists("{$root}/dist")) {
|
||||
$fields['entry_point'] = 'dist/';
|
||||
} elseif (file_exists("{$root}/src")) {
|
||||
$fields['entry_point'] = 'src/';
|
||||
} else {
|
||||
$fields['entry_point'] = './';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Generic ─────────────────────────────────────────────────────
|
||||
|
||||
private function detectGeneric(string $root, string $repoName, array &$fields): void
|
||||
{
|
||||
$fields['package_type'] = 'generic';
|
||||
|
||||
// Try to detect language from file extensions
|
||||
$fields['language'] = $this->detectLanguageFromFiles($root);
|
||||
$fields['license_spdx'] = $this->detectLicense($root);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Shared detection helpers
|
||||
// =====================================================================
|
||||
|
||||
private function detectEntryPoint(string $root): string
|
||||
{
|
||||
$abs = SourceResolver::resolveAbsolute($root);
|
||||
if ($abs !== null) {
|
||||
return basename($abs) . '/';
|
||||
}
|
||||
if (is_dir("{$root}/dist")) return 'dist/';
|
||||
if (is_dir("{$root}/src")) return 'src/';
|
||||
return './';
|
||||
}
|
||||
|
||||
private function detectLicense(string $root): string
|
||||
{
|
||||
// Check LICENSE file
|
||||
foreach (['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'] as $name) {
|
||||
$file = "{$root}/{$name}";
|
||||
if (!file_exists($file)) continue;
|
||||
$content = file_get_contents($file);
|
||||
|
||||
// SPDX header
|
||||
if (preg_match('/SPDX-License-Identifier:\s*(\S+)/', $content, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
// Common license patterns
|
||||
if (strpos($content, 'GNU GENERAL PUBLIC LICENSE') !== false) {
|
||||
if (strpos($content, 'Version 3') !== false) return 'GPL-3.0-or-later';
|
||||
if (strpos($content, 'Version 2') !== false) return 'GPL-2.0-or-later';
|
||||
}
|
||||
if (strpos($content, 'MIT License') !== false) return 'MIT';
|
||||
if (strpos($content, 'Apache License') !== false && strpos($content, 'Version 2.0') !== false) return 'Apache-2.0';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
private function detectLanguageFromFiles(string $root): string
|
||||
{
|
||||
$counts = ['PHP' => 0, 'Go' => 0, 'TypeScript' => 0, 'JavaScript' => 0, 'Python' => 0, 'Shell' => 0];
|
||||
|
||||
$extensions = [
|
||||
'php' => 'PHP', 'go' => 'Go', 'ts' => 'TypeScript',
|
||||
'js' => 'JavaScript', 'py' => 'Python', 'sh' => 'Shell',
|
||||
];
|
||||
|
||||
// Quick scan: only check top two levels
|
||||
foreach (glob("{$root}/*") ?: [] as $item) {
|
||||
$ext = pathinfo($item, PATHINFO_EXTENSION);
|
||||
if (isset($extensions[$ext])) {
|
||||
$counts[$extensions[$ext]]++;
|
||||
}
|
||||
if (is_dir($item) && basename($item)[0] !== '.') {
|
||||
foreach (glob("{$item}/*") ?: [] as $subItem) {
|
||||
$ext = pathinfo($subItem, PATHINFO_EXTENSION);
|
||||
if (isset($extensions[$ext])) {
|
||||
$counts[$extensions[$ext]]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
arsort($counts);
|
||||
$top = key($counts);
|
||||
return $counts[$top] > 0 ? $top : '';
|
||||
}
|
||||
|
||||
private function normalizeLicense(string $license): string
|
||||
{
|
||||
$lower = strtolower($license);
|
||||
$isGpl = strpos($lower, 'gpl') !== false || strpos($lower, 'general public license') !== false;
|
||||
if ($isGpl && strpos($lower, '3') !== false) return 'GPL-3.0-or-later';
|
||||
if ($isGpl && strpos($lower, '2') !== false) return 'GPL-2.0-or-later';
|
||||
if ($lower === 'mit' || strpos($lower, 'mit license') !== false) return 'MIT';
|
||||
if (strpos($lower, 'apache') !== false) return 'Apache-2.0';
|
||||
return $license;
|
||||
}
|
||||
|
||||
private function detectRepoName(string $root): string
|
||||
{
|
||||
$gitConfig = "{$root}/.git/config";
|
||||
if (!file_exists($gitConfig)) {
|
||||
return basename($root);
|
||||
}
|
||||
|
||||
$content = file_get_contents($gitConfig);
|
||||
if (preg_match('/url\s*=\s*.*\/([^\/\s]+?)(?:\.git)?\s*$/m', $content, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
return basename($root);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// API interaction
|
||||
// =====================================================================
|
||||
|
||||
private function fetchManifest(string $apiBase, string $org, string $repo, string $token): ?array
|
||||
{
|
||||
$url = "{$apiBase}/repos/{$org}/{$repo}/manifest";
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'header' => "Authorization: token {$token}\r\nAccept: application/json\r\n",
|
||||
'timeout' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
if ($body === false) return null;
|
||||
|
||||
return json_decode($body, true);
|
||||
}
|
||||
|
||||
private function computeDiff(array $current, array $detected): array
|
||||
{
|
||||
// Map detected keys to API keys (underscores match)
|
||||
$changes = [];
|
||||
|
||||
foreach ($detected as $key => $value) {
|
||||
$apiKey = $key;
|
||||
$currentVal = $current[$apiKey] ?? '';
|
||||
|
||||
// Only flag as changed if detected value is non-empty and differs
|
||||
if ($value !== '' && $value !== $currentVal) {
|
||||
// Don't overwrite a non-empty API value with a detected value
|
||||
// unless the API value is actually empty
|
||||
if ($currentVal === '' || $this->shouldOverride($key, $currentVal, $value)) {
|
||||
$changes[$key] = [
|
||||
'current' => $currentVal,
|
||||
'detected' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
private function shouldOverride(string $field, string $current, string $detected): bool
|
||||
{
|
||||
// Version: detected from source is authoritative
|
||||
if ($field === 'version') return true;
|
||||
|
||||
// These fields: source files are authoritative
|
||||
if (in_array($field, ['element_name', 'package_type', 'language', 'entry_point'], true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For other fields, only fill empty — don't overwrite manual edits
|
||||
return false;
|
||||
}
|
||||
|
||||
private function pushManifest(string $apiBase, string $org, string $repo, string $token, array $current, array $update): bool
|
||||
{
|
||||
$merged = array_merge($current, $update);
|
||||
$url = "{$apiBase}/repos/{$org}/{$repo}/manifest";
|
||||
$payload = json_encode($merged);
|
||||
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'PUT',
|
||||
'header' => "Authorization: token {$token}\r\nContent-Type: application/json\r\nAccept: application/json\r\n",
|
||||
'content' => $payload,
|
||||
'timeout' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
return $body !== false;
|
||||
}
|
||||
}
|
||||
|
||||
$app = new ManifestDetectCli();
|
||||
exit($app->execute());
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/manifest_element.php
|
||||
* BRIEF: Extract element name, type, type prefix, and ZIP name from manifest
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,564 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/manifest_integrity.php
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Cross-check manifest API fields against repo contents across the org
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../lib/Enterprise/CliFramework.php';
|
||||
|
||||
use MokoEnterprise\CliFramework;
|
||||
|
||||
class ManifestIntegrityCli extends CliFramework
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Cross-check manifest fields against repo contents across the org');
|
||||
$this->addArgument('--path', 'Single repo path (local mode)', '');
|
||||
$this->addArgument('--org', 'Gitea org (bulk mode)', 'MokoConsulting');
|
||||
$this->addArgument('--repo', 'Single repo name (remote mode)', '');
|
||||
$this->addArgument('--token', 'Gitea API token (or GITEA_TOKEN env)', '');
|
||||
$this->addArgument('--api-base', 'Gitea API base URL', 'https://git.mokoconsulting.tech/api/v1');
|
||||
$this->addArgument('--fix', 'Push fixes for detected drift', false);
|
||||
$this->addArgument('--json', 'Output as JSON', false);
|
||||
$this->addArgument('--quiet', 'Only show repos with issues', false);
|
||||
}
|
||||
|
||||
protected function run(): int
|
||||
{
|
||||
$path = $this->getArgument('--path');
|
||||
$org = $this->getArgument('--org');
|
||||
$repoName = $this->getArgument('--repo');
|
||||
$token = $this->getArgument('--token') ?: getenv('GITEA_TOKEN') ?: '';
|
||||
$apiBase = rtrim($this->getArgument('--api-base'), '/');
|
||||
$fixMode = (bool) $this->getArgument('--fix');
|
||||
$jsonMode = (bool) $this->getArgument('--json');
|
||||
$quiet = (bool) $this->getArgument('--quiet');
|
||||
|
||||
if ($token === '') {
|
||||
$this->log('ERROR', 'API token required (use --token or GITEA_TOKEN env)');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ── Mode selection ──────────────────────────────────────────
|
||||
if ($path !== '') {
|
||||
// Local mode: detect from source + compare to API
|
||||
return $this->checkLocal($path, $org, $repoName, $token, $apiBase, $fixMode, $jsonMode);
|
||||
}
|
||||
|
||||
if ($repoName !== '') {
|
||||
// Single remote repo
|
||||
return $this->checkRemoteRepo($org, $repoName, $token, $apiBase, $fixMode, $jsonMode);
|
||||
}
|
||||
|
||||
// Bulk mode: all repos in org
|
||||
return $this->checkOrg($org, $token, $apiBase, $fixMode, $jsonMode, $quiet);
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Local mode — detect from source, compare to API
|
||||
// =====================================================================
|
||||
|
||||
private function checkLocal(string $path, string $org, string $repoName, string $token, string $apiBase, bool $fix, bool $json): int
|
||||
{
|
||||
$root = realpath($path) ?: $path;
|
||||
if (!is_dir($root)) {
|
||||
$this->log('ERROR', "Path does not exist: {$path}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($repoName === '') {
|
||||
$repoName = $this->detectRepoName($root);
|
||||
}
|
||||
|
||||
// Run manifest_detect logic
|
||||
$detected = $this->runDetect($root, $repoName);
|
||||
$current = $this->fetchManifest($apiBase, $org, $repoName, $token);
|
||||
|
||||
if ($current === null) {
|
||||
$this->log('ERROR', "Failed to fetch manifest for {$org}/{$repoName}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$issues = $this->validate($current, $detected, $repoName);
|
||||
|
||||
if ($json) {
|
||||
echo json_encode(['repo' => $repoName, 'issues' => $issues], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
||||
} else {
|
||||
$this->printIssues($repoName, $issues);
|
||||
}
|
||||
|
||||
if ($fix && !empty($issues)) {
|
||||
return $this->applyFixes($apiBase, $org, $repoName, $token, $current, $issues);
|
||||
}
|
||||
|
||||
return empty($issues) ? 0 : 1;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Remote single repo mode — fetch source files via API
|
||||
// =====================================================================
|
||||
|
||||
private function checkRemoteRepo(string $org, string $repoName, string $token, string $apiBase, bool $fix, bool $json): int
|
||||
{
|
||||
$current = $this->fetchManifest($apiBase, $org, $repoName, $token);
|
||||
if ($current === null) {
|
||||
$this->log('ERROR', "Failed to fetch manifest for {$org}/{$repoName}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$issues = $this->validateManifestOnly($current, $repoName);
|
||||
|
||||
if ($json) {
|
||||
echo json_encode(['repo' => $repoName, 'issues' => $issues], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
||||
} else {
|
||||
$this->printIssues($repoName, $issues);
|
||||
}
|
||||
|
||||
if ($fix && !empty($issues)) {
|
||||
return $this->applyFixes($apiBase, $org, $repoName, $token, $current, $issues);
|
||||
}
|
||||
|
||||
return empty($issues) ? 0 : 1;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Bulk org mode — check all repos
|
||||
// =====================================================================
|
||||
|
||||
private function checkOrg(string $org, string $token, string $apiBase, bool $fix, bool $json, bool $quiet): int
|
||||
{
|
||||
$repos = $this->fetchOrgRepos($apiBase, $org, $token);
|
||||
if ($repos === null) {
|
||||
$this->log('ERROR', "Failed to fetch repos for org {$org}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->log('INFO', "Manifest Integrity Check — {$org} (" . count($repos) . " repos)");
|
||||
|
||||
$allResults = [];
|
||||
$totalIssues = 0;
|
||||
$reposWithIssues = 0;
|
||||
|
||||
foreach ($repos as $repo) {
|
||||
$name = $repo['name'];
|
||||
$manifest = $this->fetchManifest($apiBase, $org, $name, $token);
|
||||
|
||||
if ($manifest === null) {
|
||||
if (!$quiet) {
|
||||
$this->log('WARN', "{$name}: no manifest");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$issues = $this->validateManifestOnly($manifest, $name);
|
||||
|
||||
if (!empty($issues)) {
|
||||
$reposWithIssues++;
|
||||
$totalIssues += count($issues);
|
||||
|
||||
if ($json) {
|
||||
$allResults[] = ['repo' => $name, 'issues' => $issues];
|
||||
} else {
|
||||
$this->printIssues($name, $issues);
|
||||
}
|
||||
|
||||
if ($fix) {
|
||||
$this->applyFixes($apiBase, $org, $name, $token, $manifest, $issues);
|
||||
}
|
||||
} elseif (!$quiet && !$json) {
|
||||
$this->log('OK', "{$name}: clean");
|
||||
}
|
||||
}
|
||||
|
||||
if ($json) {
|
||||
echo json_encode($allResults, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
|
||||
} else {
|
||||
echo "\n";
|
||||
$level = $reposWithIssues > 0 ? 'WARN' : 'OK';
|
||||
$this->log($level, sprintf(
|
||||
'Summary: %d repos checked, %d with issues (%d total issues)',
|
||||
count($repos),
|
||||
$reposWithIssues,
|
||||
$totalIssues
|
||||
));
|
||||
}
|
||||
|
||||
return $reposWithIssues > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Validation rules
|
||||
// =====================================================================
|
||||
|
||||
/**
|
||||
* Full validation: compare API manifest against locally-detected fields.
|
||||
*/
|
||||
private function validate(array $current, array $detected, string $repoName): array
|
||||
{
|
||||
$issues = [];
|
||||
|
||||
// Required fields that should never be empty
|
||||
$required = ['platform', 'name', 'version', 'package_type', 'language', 'entry_point'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($current[$field])) {
|
||||
$fix = $detected[$field] ?? null;
|
||||
$issues[] = [
|
||||
'field' => $field,
|
||||
'severity' => 'error',
|
||||
'message' => 'Missing required field',
|
||||
'current' => '',
|
||||
'fix' => $fix,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Drift detection: detected value differs from API
|
||||
foreach ($detected as $field => $detectedValue) {
|
||||
$currentValue = $current[$field] ?? '';
|
||||
if ($detectedValue !== '' && $currentValue !== '' && $detectedValue !== $currentValue) {
|
||||
// Version drift is expected on dev branches (suffix)
|
||||
if ($field === 'version' && strpos($detectedValue, $currentValue) === 0) {
|
||||
continue; // e.g., detected "02.34.50-dev" vs API "02.34.50"
|
||||
}
|
||||
if ($field === 'version' && strpos($currentValue, $detectedValue) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$issues[] = [
|
||||
'field' => $field,
|
||||
'severity' => 'warn',
|
||||
'message' => 'Drift: source differs from manifest',
|
||||
'current' => $currentValue,
|
||||
'fix' => $detectedValue,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Platform-specific structure validation
|
||||
$platform = $current['platform'] ?? '';
|
||||
$issues = array_merge($issues, $this->validatePlatformStructure($platform, $current, $repoName));
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* API-only validation: check manifest fields for completeness and consistency
|
||||
* without access to source files.
|
||||
*/
|
||||
private function validateManifestOnly(array $manifest, string $repoName): array
|
||||
{
|
||||
$issues = [];
|
||||
|
||||
// Required fields
|
||||
$required = ['platform', 'name', 'version', 'language'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($manifest[$field])) {
|
||||
$issues[] = [
|
||||
'field' => $field,
|
||||
'severity' => 'error',
|
||||
'message' => 'Missing required field',
|
||||
'current' => '',
|
||||
'fix' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Recommended fields
|
||||
$recommended = ['package_type', 'entry_point', 'license_spdx', 'description'];
|
||||
foreach ($recommended as $field) {
|
||||
if (empty($manifest[$field])) {
|
||||
$issues[] = [
|
||||
'field' => $field,
|
||||
'severity' => 'info',
|
||||
'message' => 'Recommended field is empty',
|
||||
'current' => '',
|
||||
'fix' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Platform-specific checks
|
||||
$platform = $manifest['platform'] ?? '';
|
||||
$issues = array_merge($issues, $this->validatePlatformStructure($platform, $manifest, $repoName));
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Platform-specific validation rules.
|
||||
*/
|
||||
private function validatePlatformStructure(string $platform, array $manifest, string $repoName): array
|
||||
{
|
||||
$issues = [];
|
||||
|
||||
switch ($platform) {
|
||||
case 'joomla':
|
||||
case 'waas-component':
|
||||
// Joomla repos must have element_name
|
||||
if (empty($manifest['element_name'])) {
|
||||
$issues[] = [
|
||||
'field' => 'element_name',
|
||||
'severity' => 'error',
|
||||
'message' => 'Joomla repos require element_name',
|
||||
'current' => '',
|
||||
'fix' => null,
|
||||
];
|
||||
}
|
||||
// Language should be PHP
|
||||
if (!empty($manifest['language']) && $manifest['language'] !== 'PHP') {
|
||||
$issues[] = [
|
||||
'field' => 'language',
|
||||
'severity' => 'warn',
|
||||
'message' => 'Joomla repos should have language=PHP',
|
||||
'current' => $manifest['language'],
|
||||
'fix' => 'PHP',
|
||||
];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'dolibarr':
|
||||
case 'crm-module':
|
||||
if (!empty($manifest['language']) && $manifest['language'] !== 'PHP') {
|
||||
$issues[] = [
|
||||
'field' => 'language',
|
||||
'severity' => 'warn',
|
||||
'message' => 'Dolibarr repos should have language=PHP',
|
||||
'current' => $manifest['language'],
|
||||
'fix' => 'PHP',
|
||||
];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'go':
|
||||
if (!empty($manifest['language']) && $manifest['language'] !== 'Go') {
|
||||
$issues[] = [
|
||||
'field' => 'language',
|
||||
'severity' => 'warn',
|
||||
'message' => 'Go repos should have language=Go',
|
||||
'current' => $manifest['language'],
|
||||
'fix' => 'Go',
|
||||
];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mcp':
|
||||
if (!empty($manifest['language']) && !in_array($manifest['language'], ['TypeScript', 'JavaScript'], true)) {
|
||||
$issues[] = [
|
||||
'field' => 'language',
|
||||
'severity' => 'warn',
|
||||
'message' => 'MCP repos should have language=TypeScript or JavaScript',
|
||||
'current' => $manifest['language'],
|
||||
'fix' => null,
|
||||
];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Version format check: should be XX.YY.ZZ
|
||||
$version = $manifest['version'] ?? '';
|
||||
if ($version !== '' && !preg_match('/^\d{2}\.\d{2}\.\d{2}/', $version)) {
|
||||
// Allow semver for node/go repos
|
||||
if (!in_array($platform, ['mcp', 'node', 'go'], true)) {
|
||||
$issues[] = [
|
||||
'field' => 'version',
|
||||
'severity' => 'info',
|
||||
'message' => 'Version does not match XX.YY.ZZ format',
|
||||
'current' => $version,
|
||||
'fix' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Output
|
||||
// =====================================================================
|
||||
|
||||
private function printIssues(string $repoName, array $issues): void
|
||||
{
|
||||
if (empty($issues)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$errors = count(array_filter($issues, fn($i) => $i['severity'] === 'error'));
|
||||
$warns = count(array_filter($issues, fn($i) => $i['severity'] === 'warn'));
|
||||
$infos = count($issues) - $errors - $warns;
|
||||
|
||||
echo "\n";
|
||||
$summary = [];
|
||||
if ($errors > 0) $summary[] = "{$errors} error(s)";
|
||||
if ($warns > 0) $summary[] = "{$warns} warning(s)";
|
||||
if ($infos > 0) $summary[] = "{$infos} info";
|
||||
$this->log($errors > 0 ? 'ERROR' : 'WARN', "{$repoName} — " . implode(', ', $summary));
|
||||
|
||||
foreach ($issues as $issue) {
|
||||
$icon = match ($issue['severity']) {
|
||||
'error' => 'ERROR',
|
||||
'warn' => 'WARN',
|
||||
default => 'INFO',
|
||||
};
|
||||
$msg = sprintf(' %-18s %s', $issue['field'], $issue['message']);
|
||||
if ($issue['current'] !== '') {
|
||||
$msg .= " (current: {$issue['current']})";
|
||||
}
|
||||
if ($issue['fix'] !== null) {
|
||||
$msg .= " → fix: {$issue['fix']}";
|
||||
}
|
||||
$this->log($icon, $msg);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Fix application
|
||||
// =====================================================================
|
||||
|
||||
private function applyFixes(string $apiBase, string $org, string $repo, string $token, array $current, array $issues): int
|
||||
{
|
||||
$fixes = [];
|
||||
foreach ($issues as $issue) {
|
||||
if ($issue['fix'] !== null && $issue['fix'] !== '') {
|
||||
$fixes[$issue['field']] = $issue['fix'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fixes)) {
|
||||
$this->log('INFO', "{$repo}: no auto-fixable issues");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$merged = array_merge($current, $fixes);
|
||||
$url = "{$apiBase}/repos/{$org}/{$repo}/manifest";
|
||||
$payload = json_encode($merged);
|
||||
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'PUT',
|
||||
'header' => "Authorization: token {$token}\r\nContent-Type: application/json\r\nAccept: application/json\r\n",
|
||||
'content' => $payload,
|
||||
'timeout' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
if ($body === false) {
|
||||
$this->log('ERROR', "{$repo}: failed to push fixes");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->log('OK', "{$repo}: fixed " . implode(', ', array_keys($fixes)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// API helpers
|
||||
// =====================================================================
|
||||
|
||||
private function fetchManifest(string $apiBase, string $org, string $repo, string $token): ?array
|
||||
{
|
||||
$url = "{$apiBase}/repos/{$org}/{$repo}/manifest";
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'header' => "Authorization: token {$token}\r\nAccept: application/json\r\n",
|
||||
'timeout' => 10,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
if ($body === false) return null;
|
||||
|
||||
$data = json_decode($body, true);
|
||||
return is_array($data) ? $data : null;
|
||||
}
|
||||
|
||||
private function fetchOrgRepos(string $apiBase, string $org, string $token): ?array
|
||||
{
|
||||
$allRepos = [];
|
||||
$page = 1;
|
||||
$limit = 50;
|
||||
|
||||
while (true) {
|
||||
$url = "{$apiBase}/orgs/{$org}/repos?page={$page}&limit={$limit}";
|
||||
$ctx = stream_context_create([
|
||||
'http' => [
|
||||
'header' => "Authorization: token {$token}\r\nAccept: application/json\r\n",
|
||||
'timeout' => 15,
|
||||
],
|
||||
]);
|
||||
|
||||
$body = @file_get_contents($url, false, $ctx);
|
||||
if ($body === false) return null;
|
||||
|
||||
$repos = json_decode($body, true);
|
||||
if (!is_array($repos) || empty($repos)) break;
|
||||
|
||||
$allRepos = array_merge($allRepos, $repos);
|
||||
|
||||
if (count($repos) < $limit) break;
|
||||
$page++;
|
||||
}
|
||||
|
||||
// Filter out archived and empty repos
|
||||
return array_filter($allRepos, fn($r) => !($r['archived'] ?? false) && !($r['empty'] ?? false));
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Detection (delegates to manifest_detect logic)
|
||||
// =====================================================================
|
||||
|
||||
private function runDetect(string $root, string $repoName): array
|
||||
{
|
||||
$script = __DIR__ . '/manifest_detect.php';
|
||||
$redirect = PHP_OS_FAMILY === 'Windows' ? '2>NUL' : '2>/dev/null';
|
||||
$cmd = sprintf(
|
||||
'php %s --path %s --repo %s --json --quiet %s',
|
||||
escapeshellarg($script),
|
||||
escapeshellarg($root),
|
||||
escapeshellarg($repoName),
|
||||
$redirect
|
||||
);
|
||||
|
||||
$output = shell_exec($cmd) ?? '';
|
||||
|
||||
// Extract JSON object from output (skip banner/log lines)
|
||||
if (preg_match('/\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}/s', $output, $m)) {
|
||||
$data = json_decode($m[0], true);
|
||||
if (is_array($data)) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private function detectRepoName(string $root): string
|
||||
{
|
||||
$gitConfig = "{$root}/.git/config";
|
||||
if (!file_exists($gitConfig)) {
|
||||
return basename($root);
|
||||
}
|
||||
|
||||
$content = file_get_contents($gitConfig);
|
||||
if (preg_match('/url\s*=\s*.*\/([^\/\s]+?)(?:\.git)?\s*$/m', $content, $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
return basename($root);
|
||||
}
|
||||
}
|
||||
|
||||
$app = new ManifestIntegrityCli();
|
||||
exit($app->execute());
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/manifest_licensing.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/manifest_read.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Parse .manifest.xml and output requested field(s) for CI consumption
|
||||
*/
|
||||
|
||||
@@ -59,7 +59,7 @@ class ManifestReadCli extends CliFramework
|
||||
$candidates = [
|
||||
"{$root}/.mokogitea/manifest.xml",
|
||||
"{$root}/.mokogitea/.manifest.xml", // legacy (dot-prefixed)
|
||||
"{$root}/.mokogitea/.moko-platform", // legacy v4
|
||||
"{$root}/.mokogitea/.mokoplatform", // legacy v4
|
||||
];
|
||||
|
||||
foreach ($candidates as $candidate) {
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/package_build.php
|
||||
* BRIEF: Build ZIP and tar.gz install packages for Joomla/Dolibarr/generic projects
|
||||
*
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/platform_detect.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Auto-detect repository platform type and optionally update manifest
|
||||
*/
|
||||
|
||||
|
||||
+5
-5
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release.php
|
||||
* BRIEF: Automate the moko-platform version branch release flow
|
||||
* BRIEF: Automate the mokoplatform version branch release flow
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
@@ -23,7 +23,7 @@ class ReleaseCli extends CliFramework
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Automate the moko-platform version branch release flow');
|
||||
$this->setDescription('Automate the mokoplatform version branch release flow');
|
||||
$this->addArgument('--bump', 'Bump type: patch, minor, or major', '');
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_body_update.php
|
||||
* BRIEF: Update Gitea release body with changelog extract and checksums
|
||||
*/
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_cascade.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: DEPRECATED — cascade behavior removed. Each release stream is independent.
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_create.php
|
||||
* BRIEF: Create or overwrite a Gitea release with proper naming
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_manage.php
|
||||
* BRIEF: Create/update Gitea releases, upload assets, update release body
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_mirror.php
|
||||
* BRIEF: Mirror a Gitea release (with assets) to a GitHub repository
|
||||
*/
|
||||
@@ -201,7 +201,7 @@ class ReleaseMirrorCli extends CliFramework
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: token {$token}",
|
||||
'Accept: application/vnd.github+json',
|
||||
'User-Agent: moko-platform',
|
||||
'User-Agent: mokoplatform',
|
||||
'Content-Type: application/json',
|
||||
],
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
@@ -229,7 +229,7 @@ class ReleaseMirrorCli extends CliFramework
|
||||
CURLOPT_HTTPHEADER => [
|
||||
"Authorization: token {$token}",
|
||||
'Accept: application/vnd.github+json',
|
||||
'User-Agent: moko-platform',
|
||||
'User-Agent: mokoplatform',
|
||||
'Content-Type: application/octet-stream',
|
||||
],
|
||||
CURLOPT_POSTFIELDS => file_get_contents($filePath),
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_notes.php
|
||||
* BRIEF: Extract release notes from CHANGELOG.md for a given version
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_package.php
|
||||
* BRIEF: Build packages (ZIP + tar.gz) with SHA-256 and upload to Gitea release
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_promote.php
|
||||
* BRIEF: Promote a Gitea release from one channel to another (rename release, tag, assets)
|
||||
*/
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_publish.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Publish a release and create copies for all lesser stability streams.
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_validate.php
|
||||
* BRIEF: Pre-release validation -- version consistency, required files, manifest checks
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/release_verify.php
|
||||
* BRIEF: Verify a built release artifact — version, SHA256, disallowed files
|
||||
*/
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/scaffold_client.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Scaffold a new client-waas repo from Template-Client-WaaS with pre-configured settings
|
||||
*/
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/sync_rulesets.php
|
||||
* BRIEF: Apply branch protection rules to all repos via platform adapter
|
||||
*/
|
||||
@@ -46,7 +46,7 @@ class SyncRulesetsCli extends CliFramework
|
||||
);
|
||||
|
||||
$platformName = $adapter->getPlatformName();
|
||||
$ALWAYS_EXCLUDE = ['moko-platform', '.github-private'];
|
||||
$ALWAYS_EXCLUDE = ['mokoplatform', '.github-private'];
|
||||
|
||||
// -- Protection rules (platform-agnostic format) --
|
||||
$PROTECTIONS = [
|
||||
|
||||
+3
-3
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/theme_lint.php
|
||||
* BRIEF: Lint theme files -- CSS syntax, image sizes, hardcoded URLs
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/updates_xml_build.php
|
||||
* BRIEF: Generate Joomla updates.xml from extension manifest metadata
|
||||
*/
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/updates_xml_sync.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.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
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_auto_bump.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Auto patch-bump, set stability suffix, and commit — single CLI replacing inline workflow bash
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_bump.php
|
||||
* BRIEF: Auto-increment version -- manifest.xml is canonical, cascades to all XML and MD files
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_bump_remote.php
|
||||
* BRIEF: Bump version in manifest XML and CHANGELOG.md on a remote branch via Gitea API
|
||||
*/
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_check.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Validate version consistency across README, manifests, and sub-packages
|
||||
*/
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_read.php
|
||||
* BRIEF: Read version — manifest.xml is canonical, falls back to README.md and Joomla XML
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_reset_dev.php
|
||||
* BRIEF: Reset platform version to 'development' on a branch via Gitea API
|
||||
*/
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/version_set_platform.php
|
||||
* BRIEF: Set version in platform-specific files (Dolibarr $this->version, Joomla <version>)
|
||||
*/
|
||||
|
||||
+8
-8
@@ -6,12 +6,12 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: moko-platform.CLI
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* DEFGROUP: mokoplatform.CLI
|
||||
* INGROUP: mokoplatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /cli/wiki_sync.php
|
||||
* VERSION: 09.25.02
|
||||
* BRIEF: Sync select wiki pages from moko-platform to all template repos
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Sync select wiki pages from mokoplatform to all template repos
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
@@ -25,7 +25,7 @@ class WikiSyncCli extends CliFramework
|
||||
private string $giteaUrl = 'https://git.mokoconsulting.tech';
|
||||
private string $token = '';
|
||||
private string $org = 'MokoConsulting';
|
||||
private string $sourceRepo = 'moko-platform';
|
||||
private string $sourceRepo = 'mokoplatform';
|
||||
private array $targetRepos = [];
|
||||
private array $pages = [];
|
||||
private bool $allTemplates = false;
|
||||
@@ -38,10 +38,10 @@ class WikiSyncCli extends CliFramework
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Sync wiki pages from moko-platform to template repos');
|
||||
$this->setDescription('Sync wiki pages from mokoplatform to template repos');
|
||||
$this->addArgument('--token', 'Gitea API token (required)', '');
|
||||
$this->addArgument('--org', 'Organization (default: MokoConsulting)', 'MokoConsulting');
|
||||
$this->addArgument('--source', 'Source repo (default: moko-platform)', 'moko-platform');
|
||||
$this->addArgument('--source', 'Source repo (default: mokoplatform)', 'mokoplatform');
|
||||
$this->addArgument('--target', 'Target repo (can repeat)', '');
|
||||
$this->addArgument('--page', 'Page to sync (can repeat)', '');
|
||||
$this->addArgument('--all-standards', 'Sync all UPPERCASE standards pages', false);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* INGROUP: moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* PATH: /cli/workflow_sync.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Sync workflows from Generic → platform templates → live repos based on manifest.platform
|
||||
*/
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mokoconsulting-tech/enterprise",
|
||||
"description": "moko-platform Enterprise API \u2014 PHP implementation",
|
||||
"description": "mokoplatform Enterprise API \u2014 PHP implementation",
|
||||
"type": "library",
|
||||
"version": "09.23.00",
|
||||
"license": "GPL-3.0-or-later",
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /deploy/backup-before-deploy.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Snapshot Joomla directories before deployment for rollback capability
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /deploy/deploy-dolibarr.php
|
||||
* VERSION: 09.25.02
|
||||
* VERSION: 09.28.00
|
||||
* BRIEF: Deploy Dolibarr module files to a remote server via SFTP/rsync
|
||||
*/
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /deploy/deploy-joomla.php
|
||||
* BRIEF: Smart Joomla deploy — routes files to correct Joomla directories based on XML manifest
|
||||
*
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: MokoPlatform.Scripts.Deploy
|
||||
* INGROUP: MokoPlatform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform
|
||||
* PATH: /deploy/deploy-sftp.php
|
||||
* BRIEF: Deploy a repository src/ directory to a remote web server via SFTP
|
||||
*/
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user