diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml index ddf579f..34953b1 100644 --- a/.mokogitea/workflows/auto-bump.yml +++ b/.mokogitea/workflows/auto-bump.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +# INGROUP: moko-platform.Release +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # PATH: /.mokogitea/workflows/auto-bump.yml -# VERSION: 09.23.00 +# VERSION: 09.02.00 # BRIEF: Auto patch-bump version on every push to dev (skips merge commits) name: "Universal: Auto Version Bump" @@ -43,19 +43,19 @@ jobs: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 - - name: Setup mokoplatform tools + - name: Setup moko-platform tools run: | if ! command -v composer &> /dev/null; then sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 fi - if [ -d "/opt/mokoplatform/cli" ]; then - echo "MOKO_CLI=/opt/mokoplatform/cli" >> "$GITHUB_ENV" + if [ -d "/opt/moko-platform/cli" ]; then + echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV" else 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" + "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 diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml index 850b53c..bec445b 100644 --- a/.mokogitea/workflows/auto-release.yml +++ b/.mokogitea/workflows/auto-release.yml @@ -4,8 +4,8 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Release -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform +# 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 @@ -17,7 +17,7 @@ # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | | # | Platform-specific: | -# | joomla: XML manifest, updates.xml, type-prefixed packages | +# | joomla: XML manifest, type-prefixed packages | # | dolibarr: mod*.class.php, update.txt, dev version reset | # | generic: README-only, no update stream | # | | @@ -66,25 +66,30 @@ jobs: token: ${{ secrets.MOKOGITEA_TOKEN }} fetch-depth: 1 - - name: Setup mokoplatform tools + - name: Setup moko-platform tools env: MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting run: | - if ! command -v composer &> /dev/null; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 + 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 - # Always fetch latest CLI tools — never use stale cache from previous runs - rm -rf /tmp/mokoplatform-api - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git" \ - /tmp/mokoplatform-api - cd /tmp/mokoplatform-api - composer install --no-dev --no-interaction --quiet - name: Rename branch to rc run: | - php /tmp/mokoplatform-api/cli/branch_rename.php \ + 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}" \ @@ -100,16 +105,15 @@ jobs: - name: Publish RC release run: | - php /tmp/mokoplatform-api/cli/release_publish.php \ + php ${MOKO_CLI}/release_publish.php \ --path . --stability rc --bump minor --branch rc \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --skip-update-stream + --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 (updates.xml managed by Gitea Pages)" >> $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: @@ -145,31 +149,83 @@ jobs: fi echo "No conflict markers found" - - name: Setup mokoplatform tools + - 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: | - # Ensure PHP + Composer are available - 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 + 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 - # Always fetch latest CLI tools — never use stale cache from previous runs - rm -rf /tmp/mokoplatform-api - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokoplatform.git" \ - /tmp/mokoplatform-api - cd /tmp/mokoplatform-api - composer install --no-dev --no-interaction --quiet + - name: "Determine version bump level" + id: bump + run: | + # Fix/patch branches: version was already bumped by pre-release, just strip suffix + # Feature/dev branches: bump minor for the new stable release + HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}" + case "$HEAD_REF" in + fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;; + *) BUMP="minor" ;; + esac + echo "level=${BUMP}" >> "$GITHUB_OUTPUT" + echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})" - name: "Publish stable release" run: | - php /tmp/mokoplatform-api/cli/release_publish.php \ - --path . --stability stable --bump minor --branch main \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --skip-update-stream + BUMP_FLAG="" + if [ "${{ steps.bump.outputs.level }}" != "none" ]; then + BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}" + fi + php ${MOKO_CLI}/release_publish.php \ + --path . --stability stable ${BUMP_FLAG} --branch main \ + --token "${{ secrets.MOKOGITEA_TOKEN }}" + + - name: 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" @@ -182,7 +238,7 @@ jobs: 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 /tmp/mokoplatform-api/cli/release_mirror.php \ + 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" \ @@ -256,7 +312,7 @@ jobs: continue-on-error: true run: | API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php /tmp/mokoplatform-api/cli/version_reset_dev.php \ + php ${MOKO_CLI}/version_reset_dev.php \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ --branch dev --path . 2>&1 || true diff --git a/.mokogitea/workflows/branch-cleanup.yml b/.mokogitea/workflows/branch-cleanup.yml index fc0f4c3..e0ba128 100644 --- a/.mokogitea/workflows/branch-cleanup.yml +++ b/.mokogitea/workflows/branch-cleanup.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: MokoPlatform.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +# INGROUP: MokoStandards.Universal +# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform # PATH: /.mokogitea/workflows/branch-cleanup.yml -# VERSION: 09.23.00 +# VERSION: 01.00.00 # BRIEF: Delete feature branches after PR merge name: "Branch Cleanup" diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml new file mode 100644 index 0000000..5f7c1d7 --- /dev/null +++ b/.mokogitea/workflows/cascade-dev.yml @@ -0,0 +1,10 @@ +# DISABLED — auto-release Step 11 recreates dev from main after every release. +# Cascade-dev is redundant and causes version conflicts when both main and dev +# have different version numbers in templateDetails.xml / manifest.xml. +name: "Cascade Main → Dev (DISABLED)" +on: workflow_dispatch +jobs: + noop: + runs-on: ubuntu-latest + steps: + - run: echo "Cascade disabled — auto-release handles dev recreation" diff --git a/.mokogitea/workflows/ci-generic.yml b/.mokogitea/workflows/ci-generic.yml new file mode 100644 index 0000000..87fd059 --- /dev/null +++ b/.mokogitea/workflows/ci-generic.yml @@ -0,0 +1,204 @@ +# Copyright (C) 2026 Moko Consulting +# +# 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 diff --git a/.mokogitea/workflows/ci-joomla.yml b/.mokogitea/workflows/ci-joomla.yml index e67987b..0c6f5ea 100644 --- a/.mokogitea/workflows/ci-joomla.yml +++ b/.mokogitea/workflows/ci-joomla.yml @@ -35,25 +35,32 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@v4 - name: Setup PHP 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 php-zip php-curl composer >/dev/null 2>&1 + fi php -v && composer --version - - name: Clone MokoStandards + - name: Setup moko-platform tools env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} - MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} - MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} + MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || github.token }} + MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }} run: | - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \ - /tmp/mokostandards-api + if [ -d "/tmp/moko-platform" ] || [ -d "/opt/moko-platform" ]; then + echo "moko-platform already available on runner — skipping clone" + else + git clone --depth 1 --branch main --quiet \ + "https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \ + /tmp/moko-platform 2>/dev/null || echo "moko-platform clone skipped — continuing without it" + fi - name: Install dependencies env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN || github.token }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}' run: | if [ -f "composer.json" ]; then composer install \ @@ -67,7 +74,7 @@ jobs: - name: PHP syntax check run: | ERRORS=0 - for DIR in source/ src/ htdocs/; do + for DIR in src/ htdocs/; do if [ -d "$DIR" ]; then FOUND=1 while IFS= read -r -d '' FILE; do @@ -124,13 +131,8 @@ jobs: echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY fi - # Check required tags - REQUIRED_TAGS="name version author" - # namespace is only required for non-package extensions - if ! grep -q 'type="package"' "$MANIFEST" 2>/dev/null; then - REQUIRED_TAGS="$REQUIRED_TAGS namespace" - fi - for TAG in $REQUIRED_TAGS; do + # Check required tags: name, version, author + for TAG in name version author; do if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY ERRORS=$((ERRORS + 1)) @@ -138,6 +140,19 @@ jobs: echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY fi done + + # Namespace is required for components/plugins but not packages + EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) + if [ "$EXT_TYPE" != "package" ]; then + if ! grep -q "/dev/null; then + echo "Missing required tag: \`\` (required for Joomla 5+ ${EXT_TYPE} extensions)" >> $GITHUB_STEP_SUMMARY + ERRORS=$((ERRORS + 1)) + else + echo "Found required tag: \`\`" >> $GITHUB_STEP_SUMMARY + fi + else + echo "Package extension — \`\` not required." >> $GITHUB_STEP_SUMMARY + fi fi if [ "${ERRORS}" -gt 0 ]; then @@ -202,44 +217,13 @@ jobs: echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY fi - - name: Check en-GB and en-US language directories exist - run: | - echo "### Language Directory Check" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - for DIR in source/ src/ htdocs/; do - [ -d "$DIR" ] || continue - # Find all language directories - while IFS= read -r -d '' LANG_DIR; do - HAS_GB=false - HAS_US=false - [ -d "${LANG_DIR}/en-GB" ] && HAS_GB=true - [ -d "${LANG_DIR}/en-US" ] && HAS_US=true - if [ "$HAS_GB" = false ]; then - echo "Missing \`en-GB\` in: \`${LANG_DIR}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - if [ "$HAS_US" = false ]; then - echo "Missing \`en-US\` in: \`${LANG_DIR}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done < <(find "$DIR" -type d -name "language" -print0) - done - - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} missing language director(ies).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "All language directories have en-GB and en-US." >> $GITHUB_STEP_SUMMARY - fi - - name: Check index.html files in directories run: | echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY MISSING=0 CHECKED=0 - for DIR in source/ src/ htdocs/; do + for DIR in src/ htdocs/; do if [ -d "$DIR" ]; then while IFS= read -r -d '' SUBDIR; do CHECKED=$((CHECKED + 1)) @@ -252,7 +236,7 @@ jobs: done if [ "${CHECKED}" -eq 0 ]; then - echo "No source/, src/, or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY + echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY elif [ "${MISSING}" -gt 0 ]; then echo "" >> $GITHUB_STEP_SUMMARY echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY @@ -268,7 +252,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@v4 - name: Validate release readiness run: | @@ -276,8 +260,8 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY ERRORS=0 - # Extract version from README.md (supports both FILE INFORMATION block and HTML comment format) - README_VERSION=$(sed -n 's/.*VERSION:\s*\([0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\).*/\1/p' README.md | head -1) + # Extract version from README.md + README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1) if [ -z "$README_VERSION" ]; then echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY ERRORS=$((ERRORS + 1)) @@ -301,7 +285,7 @@ jobs: echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY # Check matches README VERSION - MANIFEST_VERSION=$(sed -n 's/.*\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1) + MANIFEST_VERSION=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) if [ -z "$MANIFEST_VERSION" ]; then echo "No \`\` tag in manifest." >> $GITHUB_STEP_SUMMARY ERRORS=$((ERRORS + 1)) @@ -374,15 +358,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@v4 - name: Setup PHP ${{ matrix.php }} 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 php-zip php-curl composer >/dev/null 2>&1 + fi php -v && composer --version - name: Install dependencies env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN || github.token }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}' run: | if [ -f "composer.json" ]; then composer install \ @@ -420,14 +408,19 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + uses: actions/checkout@v4 - name: Setup PHP - run: php -v && composer --version + 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 php-zip php-curl composer >/dev/null 2>&1 + fi + php -v && composer --version - name: Install dependencies env: - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN || github.token }}"}}' + COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}' run: | if [ -f "composer.json" ]; then composer install --no-interaction --prefer-dist --optimize-autoloader @@ -450,7 +443,7 @@ jobs: # Determine source directory SRC_DIR="" - for DIR in source/ src/ htdocs/ lib/; do + for DIR in src/ htdocs/ lib/; do if [ -d "$DIR" ]; then SRC_DIR="$DIR" break @@ -458,7 +451,7 @@ jobs: done if [ -z "$SRC_DIR" ]; then - echo "No source directory found (source/, src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY + echo "No source directory found (src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY exit 0 fi @@ -484,3 +477,24 @@ jobs: echo '```' >> $GITHUB_STEP_SUMMARY fi exit $EXIT + + pre-release: + name: Build RC Pre-Release + runs-on: ubuntu-latest + needs: [lint-and-validate, test] + if: github.event_name == 'pull_request' + + steps: + - name: Trigger pre-release build + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.head_ref }} + run: | + curl -s -X POST \ + "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" \ + -H "Authorization: token ${GA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" + echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY + echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml index b30beb7..3a81856 100644 --- a/.mokogitea/workflows/cleanup.yml +++ b/.mokogitea/workflows/cleanup.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Maintenance -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +# INGROUP: MokoStandards.Maintenance +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # PATH: /.gitea/workflows/cleanup.yml -# VERSION: 09.23.00 +# VERSION: 01.00.00 # BRIEF: Scheduled cleanup — delete merged branches and old workflow runs name: "Universal: Repository Cleanup" @@ -33,17 +33,17 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - token: ${{ secrets.MOKOGITEA_TOKEN }} + token: ${{ secrets.GA_TOKEN }} - name: Delete merged branches env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GA_TOKEN: ${{ secrets.GA_TOKEN }} run: | echo "=== Merged Branch Cleanup ===" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" # List branches via API - BRANCHES=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ + BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ "${API}/branches?limit=50" | jq -r '.[].name') DELETED=0 @@ -56,7 +56,7 @@ jobs: # Check if branch is merged into main if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then echo " Deleting merged branch: ${BRANCH}" - curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ "${API}/branches/${BRANCH}" 2>/dev/null || true DELETED=$((DELETED + 1)) fi @@ -66,20 +66,20 @@ jobs: - name: Clean old workflow runs env: - GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} + GA_TOKEN: ${{ secrets.GA_TOKEN }} run: | echo "=== Workflow Run Cleanup ===" API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) # Get old completed runs - RUNS=$(curl -sS -H "Authorization: token ${GITEA_TOKEN}" \ + RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ "${API}/actions/runs?status=completed&limit=50" | \ jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) DELETED=0 for RUN_ID in $RUNS; do - curl -sS -X DELETE -H "Authorization: token ${GITEA_TOKEN}" \ + curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true DELETED=$((DELETED + 1)) done diff --git a/.mokogitea/workflows/deploy-manual.yml b/.mokogitea/workflows/deploy-manual.yml new file mode 100644 index 0000000..bb133ed --- /dev/null +++ b/.mokogitea/workflows/deploy-manual.yml @@ -0,0 +1,126 @@ +# Copyright (C) 2026 Moko Consulting +# +# 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 diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml index c2951e2..0c07612 100644 --- a/.mokogitea/workflows/gitleaks.yml +++ b/.mokogitea/workflows/gitleaks.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Security -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform +# INGROUP: MokoStandards.Security +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API # PATH: /templates/workflows/gitleaks.yml.template -# VERSION: 09.23.00 +# VERSION: 01.00.00 # BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens # # +========================================================================+ diff --git a/.mokogitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml index 7d86fe6..51dfcb5 100644 --- a/.mokogitea/workflows/notify.yml +++ b/.mokogitea/workflows/notify.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Notifications -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +# INGROUP: MokoStandards.Notifications +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # PATH: /.gitea/workflows/notify.yml -# VERSION: 09.23.00 +# VERSION: 01.00.00 # BRIEF: Push notifications via ntfy on release success or workflow failure name: "Universal: Notifications" diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml index 5e391db..6625857 100644 --- a/.mokogitea/workflows/pr-check.yml +++ b/.mokogitea/workflows/pr-check.yml @@ -4,8 +4,8 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.CI -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform +# INGROUP: moko-platform.CI +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # PATH: /templates/workflows/universal/pr-check.yml.template # VERSION: 09.23.00 # BRIEF: PR gate — branch policy + code validation before merge diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml index b2b9263..d0538d5 100644 --- a/.mokogitea/workflows/repo-health.yml +++ b/.mokogitea/workflows/repo-health.yml @@ -7,8 +7,8 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Validation -# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokoplatform +# INGROUP: moko-platform.Validation +# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform # 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. diff --git a/.mokogitea/workflows/security-audit.yml b/.mokogitea/workflows/security-audit.yml index 0d040e8..789325a 100644 --- a/.mokogitea/workflows/security-audit.yml +++ b/.mokogitea/workflows/security-audit.yml @@ -4,10 +4,10 @@ # # FILE INFORMATION # DEFGROUP: Gitea.Workflow -# INGROUP: mokoplatform.Security -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoplatform +# INGROUP: MokoStandards.Security +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards # PATH: /.gitea/workflows/security-audit.yml -# VERSION: 09.23.00 +# VERSION: 01.00.00 # BRIEF: Dependency vulnerability scanning for composer and npm packages name: "Universal: Security Audit" @@ -80,19 +80,3 @@ jobs: -H "Priority: high" \ -d "Security audit found vulnerabilities. Review dependency updates." \ "${NTFY_URL}/${NTFY_TOPIC}" || true - - - - name: Joomla version audit - if: always() - run: | - if [ -f "monitoring/joomla-version-audit.php" ] && [ -n "$JOOMLA_SITES" ]; then - echo "$JOOMLA_SITES" > /tmp/sites.json - php monitoring/joomla-version-audit.php --sites /tmp/sites.json || true - echo "### Joomla Version Audit" >> $GITHUB_STEP_SUMMARY - rm -f /tmp/sites.json - else - echo "Joomla audit skipped (no script or JOOMLA_SITES_JSON not configured)" - fi - env: - JOOMLA_SITES: ${{ vars.JOOMLA_SITES_JSON }} - diff --git a/mokosuitebackup.php b/mokosuitebackup.php new file mode 100644 index 0000000..2954662 --- /dev/null +++ b/mokosuitebackup.php @@ -0,0 +1,11 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +defined('_JEXEC') or die; diff --git a/mokosuitebackup.xml b/mokosuitebackup.xml new file mode 100644 index 0000000..29eca03 --- /dev/null +++ b/mokosuitebackup.xml @@ -0,0 +1,31 @@ + + + + Web Services - MokoSuiteBackup + 01.10.00-rc + 2026-06-02 + Moko Consulting + hello@mokoconsulting.tech + https://mokoconsulting.tech + Copyright (C) 2026 Moko Consulting. All rights reserved. + GPL-3.0-or-later + PLG_WEBSERVICES_MOKOJOOMBACKUP_DESCRIPTION + + Joomla\Plugin\WebServices\MokoSuiteBackup + + + mokosuitebackup.php + services + src + + + + language/en-GB/plg_webservices_mokosuitebackup.ini + language/en-GB/plg_webservices_mokosuitebackup.sys.ini + + diff --git a/services/index.html b/services/index.html new file mode 100644 index 0000000..2efb97f --- /dev/null +++ b/services/index.html @@ -0,0 +1 @@ + diff --git a/services/provider.php b/services/provider.php new file mode 100644 index 0000000..18e5ea9 --- /dev/null +++ b/services/provider.php @@ -0,0 +1,37 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + */ + +defined('_JEXEC') or die; + +use Joomla\CMS\Extension\PluginInterface; +use Joomla\CMS\Factory; +use Joomla\CMS\Plugin\PluginHelper; +use Joomla\DI\Container; +use Joomla\DI\ServiceProviderInterface; +use Joomla\Event\DispatcherInterface; +use Joomla\Plugin\WebServices\MokoSuiteBackup\Extension\MokoSuiteBackupWebServices; + +return new class () implements ServiceProviderInterface { + public function register(Container $container): void + { + $container->set( + PluginInterface::class, + function (Container $container) { + $plugin = new MokoSuiteBackupWebServices( + $container->get(DispatcherInterface::class), + (array) PluginHelper::getPlugin('webservices', 'mokosuitebackup') + ); + $plugin->setApplication(Factory::getApplication()); + + return $plugin; + } + ); + } +}; diff --git a/src/Extension/MokoSuiteBackupWebServices.php b/src/Extension/MokoSuiteBackupWebServices.php new file mode 100644 index 0000000..b56c0d9 --- /dev/null +++ b/src/Extension/MokoSuiteBackupWebServices.php @@ -0,0 +1,98 @@ + + * @copyright Copyright (C) 2026 Moko Consulting. All rights reserved. + * @license GNU General Public License version 3 or later; see LICENSE + * + * REST API endpoints — wire-compatible with the mcp_mokosuitebackup MCP server. + * + * Akeeba-compatible routes: + * POST /api/index.php/v1/mokosuitebackup/backup — Start backup + * GET /api/index.php/v1/mokosuitebackup/backups — List records + * DELETE /api/index.php/v1/mokosuitebackup/backup/:id — Delete record + * GET /api/index.php/v1/mokosuitebackup/backup/:id/download — Download archive + * GET /api/index.php/v1/mokosuitebackup/profiles — List profiles + */ + +namespace Joomla\Plugin\WebServices\MokoSuiteBackup\Extension; + +defined('_JEXEC') or die; + +use Joomla\CMS\Plugin\CMSPlugin; +use Joomla\CMS\Router\ApiRouter; +use Joomla\Event\Event; +use Joomla\Event\SubscriberInterface; +use Joomla\Router\Route; + +final class MokoSuiteBackupWebServices extends CMSPlugin implements SubscriberInterface +{ + protected $autoloadLanguage = true; + + public static function getSubscribedEvents(): array + { + return [ + 'onBeforeApiRoute' => 'onBeforeApiRoute', + ]; + } + + public function onBeforeApiRoute(Event $event): void + { + /** @var ApiRouter $router */ + [$router] = array_values($event->getArguments()); + + $defaults = [ + 'component' => 'com_mokosuitebackup', + 'public' => false, + ]; + + // Standard CRUD for backup records + $router->createCRUDRoutes('v1/mokosuitebackup/backups', 'backups', $defaults); + + // Start a backup (POST) + $router->addRoute( + new Route( + ['POST'], + 'v1/mokosuitebackup/backup', + 'backups.backup', + [], + $defaults + ) + ); + + // Delete a backup (DELETE) + $router->addRoute( + new Route( + ['DELETE'], + 'v1/mokosuitebackup/backup/:id', + 'backups.delete', + ['id' => '(\d+)'], + $defaults + ) + ); + + // Download a backup archive (GET) + $router->addRoute( + new Route( + ['GET'], + 'v1/mokosuitebackup/backup/:id/download', + 'backups.download', + ['id' => '(\d+)'], + $defaults + ) + ); + + // List backup profiles (GET) + $router->addRoute( + new Route( + ['GET'], + 'v1/mokosuitebackup/profiles', + 'backups.profiles', + [], + $defaults + ) + ); + } +} diff --git a/src/Extension/index.html b/src/Extension/index.html new file mode 100644 index 0000000..2efb97f --- /dev/null +++ b/src/Extension/index.html @@ -0,0 +1 @@ + diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..2efb97f --- /dev/null +++ b/src/index.html @@ -0,0 +1 @@ +